├── .gitignore ├── README.md ├── changelog.txt ├── cpaneluapi.class.php ├── examples └── mysql.php ├── license.txt └── otphp ├── LICENCE ├── README.markdown ├── lib ├── hotp.php ├── otp.php ├── otphp.php └── totp.php ├── tests ├── HOTPTest.php ├── OTPTest.php ├── TOTPTest.php └── TestTest.php └── vendor ├── base32.php └── libs.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .idea/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cPanel UAPI and API2 PHP class 2 | === 3 | [![GitHub release](https://img.shields.io/github/release/N1ghteyes/cpanel-UAPI-php-class.svg?style=flat-square)](https://github.com/N1ghteyes/cpanel-UAPI-php-class/releases) 4 | 5 | PHP class to provide an easy-to-use interface with cPanel's UAPI and API2. 6 | Uses PHP magic functions to provide a simple and powerful interface. 7 | 8 | v2.0 is not backwards compatible with v1.x, and will likley undergo a few more changes. See the [changelog](changelog.txt) for details. 9 | The class has been renamed to `cpanelAPI`. 10 | Some more testing is required. 11 | 12 | - Note while this class is not depricated, there is a new Agnostic API class available which will do everything this class does (except 2FA) and works with any RESTful / HTTP API - basically anything thats not SOAP. https://github.com/N1ghteyes/apicore 13 | 14 | ## Usage 15 | 16 | If you choose to use this class, please Star it in Github. This gives me a better idea of the number of users and those effected when changes are made. 17 | 18 | See the example files, but typical usage takes the form of: 19 | 20 | ### Instantiate the class 21 | ```php 22 | $cPanel = new cpanelAPI('user', 'password', 'cpanel.example.com'); 23 | ``` 24 | The API we want to use and the Module (also called Scope) are now protected and are set by `__get()`. 25 | 26 | The request layout looks like this: `$cPanel->api->method->Module->request(args[])` 27 | 28 | The `->method` part should be replaced with `->get` for GET requests and `->post` for POST requests, or omitted to default to GET requests. 29 | 30 | As an example, suppose we want to use the UAPI to call the [Mysql::get_server_information](https://documentation.cpanel.net/display/SDK/UAPI+Functions+-+Mysql%3A%3Aget_server_information) function: 31 | 32 | ```php 33 | $response = $cPanel->uapi->Mysql->get_server_information(); 34 | var_dump($response); 35 | ``` 36 | 37 | Now that we have set both the API *and* the Module, we can call other functions within this API and Module without specifying them again: 38 | 39 | ```php 40 | $response = $cPanel->create_database(['name' => $cPanel->user.'_MyDatabase']); 41 | var_dump($response); 42 | ``` 43 | 44 | We can also change the Module scope without respecifying the API. Note that the Module call is case-sensitive. 45 | 46 | ```php 47 | $response = $cPanel->SSL->list_certs(); 48 | ``` 49 | 50 | #### File upload example 51 | 52 | ```php 53 | $cPanel = new cpanelAPI($username, $password, $hostname); 54 | $cPanel->uapi->post->Fileman 55 | ->upload_files(['dir' => REMOTE_PATH_RELATIVE_TO_HOME, 56 | 'file-1' => new CURLFile(LOCAL_PATH_TO_FILE) 57 | ]); 58 | ``` 59 | 60 | ### API2 61 | 62 | API2 is used in exactly the same way as the UAPI 63 | 64 | ```php 65 | $cPanel = new cpanelAPI('user', 'password', 'cpanel.example.com'); 66 | ``` 67 | 68 | For example, suppose we want to use the API2 to add a subdomain: 69 | 70 | ```php 71 | $response = $cPanel->api2->SubDomain->addsubdomain(['rootdomain' => 'domain.com', 'domain' => 'sub']); 72 | var_dump($response); 73 | ``` 74 | 75 | ### Two-Factor Authentication 76 | 77 | To use this class on a cPanel instance with two-factor authentication (2FA), you need to pass the secret into the class constructor: 78 | 79 | ```php 80 | $cPanel = new cpanelAPI('user', 'password', 'cpanel.example.com', 'secret'); 81 | ``` 82 | 83 | The secret can be found on the 2FA setup page. See [Two-Factor Authentication for cPanel – Configure two-factor authentication](https://documentation.cpanel.net/display/ALD/Two-Factor+Authentication+for+cPanel#Two-FactorAuthenticationforcPanel-Configure2FA) for details. 84 | -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | Changelog for the Cpanel UAPI PHP Class 5 | 6 | version 2.0 7 | ============= 8 | 9 | New: 10 | - Chaining support added 11 | - __get() added to support setting api and scope with an unbroken chain 12 | - made cURL request errors available 13 | 14 | Changes / Improvements: 15 | - Removed backwards compatability for older systems using php base path and safe mode 16 | - Removed now redundant additional classes for UAPI and API 2. These are now called by chaining ->api2 or ->uapi. 17 | - Added a setScope() function to set the scoupe outside of the chain. 18 | - cleaned up the code a little 19 | 20 | Warning: 21 | - This is not backwards compatable with past versions in a consistant way. Developers using v1.1 will need to change the class calls for the api version for example. 22 | 23 | version 1.1 24 | ============= 25 | 26 | New: 27 | - Generalised the class to now support API2 as well as UAPI. 28 | $uapi = new cpanelUAPI(); 29 | $api = new cpanelAPI2(); 30 | 31 | The usage is the same, the main difference is in the internal processing, hence the separation. 32 | - Added the ability to get the last request URL for debugging / logging. 33 | 34 | Changes / Improvements: 35 | - Added error checking 36 | - Added more and better commenting 37 | - Renamed a few veriables. 38 | 39 | Bugs: 40 | - Added maxredirect, set default to 0 for issue #2 41 | 42 | version 1.0.2 43 | ============= 44 | 45 | # Renamed cpaneluapi.class to cpaneluapi.class.php to avoid issues with IDEs 46 | # Added new examples file for the mysql module 47 | # Added php file comments and a gitignore file to ignore phpstorm project files. 48 | 49 | version 1.0.1 50 | ============= 51 | 52 | # Fixed local reference to user and pass vars 53 | # some commenting 54 | # other fixes. 55 | 56 | Version 1.0 57 | =========== 58 | 59 | # Initial commit 60 | -------------------------------------------------------------------------------- /cpaneluapi.class.php: -------------------------------------------------------------------------------- 1 | user = $user; 58 | $this->pass = $pass; 59 | $this->server = $server; 60 | if ($secret) { 61 | $this->secret = $secret; 62 | $this->set2Fa(); 63 | } 64 | } 65 | 66 | /** 67 | * Include the ot class so we can generate a valid token for 2FA 68 | */ 69 | protected function set2Fa() 70 | { 71 | require 'otphp/lib/otphp.php'; 72 | $totp = new \OTPHP\TOTP($this->secret); 73 | $this->token = $totp->now(); 74 | } 75 | 76 | public function __get($name) 77 | { 78 | switch (strtolower($name)) { 79 | case 'get': 80 | $this->httpMethod = 'GET'; 81 | break; 82 | case 'post': 83 | $this->httpMethod = 'POST'; 84 | break; 85 | case 'api2': 86 | $this->setApi('api2'); 87 | break; 88 | case 'uapi': 89 | $this->setApi('uapi'); 90 | break; 91 | default: 92 | $this->scope = $name; 93 | } 94 | return $this; 95 | } 96 | 97 | /** 98 | * Set the api to use for connections. 99 | * @param $api 100 | * @return $this 101 | * @throws Exception 102 | */ 103 | protected function setApi($api) 104 | { 105 | $this->api = $api; 106 | $this->setMethod(); 107 | return $this; 108 | } 109 | 110 | /** 111 | * Function to set the method used to communicate with the chosen api. 112 | * @return $this 113 | * @throws Exception 114 | */ 115 | protected function setMethod() 116 | { 117 | switch ($this->api) { 118 | case 'uapi': 119 | $this->method = '/execute/'; 120 | break; 121 | case 'api2': 122 | $this->method = '/json-api/cpanel/'; 123 | break; 124 | default: 125 | throw new Exception('$this->api is not set or is incorrectly set. The only available options are \'uapi\' or \'api2\''); 126 | } 127 | return $this; 128 | } 129 | 130 | /** 131 | * Magic __toSting() method, allows us to return the result as raw json 132 | * @return mixed 133 | */ 134 | public function __toString() 135 | { 136 | return $this->json; 137 | } 138 | 139 | /** 140 | * Magic __call method, will translate all function calls to object to API requests 141 | * @param $name - name of the function 142 | * @param $arguments - an array of arguments 143 | * @return mixed 144 | * @throws Exception 145 | */ 146 | public function __call($name, $arguments) 147 | { 148 | if (count($arguments) < 1 || !is_array($arguments[0])) 149 | $arguments[0] = []; 150 | $this->json = $this->APIcall($name, $arguments[0]); 151 | return json_decode($this->json); 152 | } 153 | 154 | /** 155 | * @param $name 156 | * @param $arguments 157 | * @return bool|mixed 158 | * @throws Exception 159 | */ 160 | protected function APIcall($name, $arguments) 161 | { 162 | $this->auth = base64_encode($this->user . ":" . $this->pass); 163 | $this->type = $this->ssl == 1 ? "https://" : "http://"; 164 | $this->requestUrl = $this->type . $this->server . ':' . $this->port . $this->method; 165 | switch ($this->api) { 166 | case 'uapi': 167 | $this->requestUrl .= ($this->scope != '' ? $this->scope . "/" : '') . $name . '?'; 168 | break; 169 | case 'api2': 170 | if ($this->scope == '') { 171 | throw new Exception('Scope must be set.'); 172 | } 173 | $this->requestUrl .= '?cpanel_jsonapi_user=' . $this->user . '&cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=' . $this->scope . '&cpanel_jsonapi_func=' . $name . '&'; 174 | break; 175 | default: 176 | throw new Exception('$this->api is not set or is incorrectly set. The only available options are \'uapi\' or \'api2\''); 177 | } 178 | if($this->httpMethod == 'GET') { 179 | $this->requestUrl .= http_build_query($arguments); 180 | } 181 | if($this->httpMethod == 'POST'){ 182 | $this->postData = $arguments; 183 | } 184 | 185 | return $this->curl_request($this->requestUrl); 186 | } 187 | 188 | /** 189 | * @param $url 190 | * @return bool|mixed 191 | */ 192 | protected function curl_request($url) 193 | { 194 | $httpHeaders = array("Authorization: Basic " . $this->auth); 195 | //If we have a token then send that with the request for 2FA. 196 | if ($this->token) { 197 | $httpHeaders[] = "X-CPANEL-OTP: " . $this->token; 198 | } 199 | $ch = curl_init(); 200 | if($this->httpMethod == 'POST'){ 201 | $httpHeaders[] = "Content-type: multipart/form-data"; 202 | curl_setopt($ch,CURLOPT_POSTFIELDS, $this->postData); 203 | } 204 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); 205 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); 206 | curl_setopt($ch, CURLOPT_HEADER, 0); 207 | curl_setopt($ch, CURLOPT_URL, $url); 208 | curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeaders); 209 | curl_setopt($ch, CURLOPT_TIMEOUT, 100020); 210 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); 211 | 212 | $content = $this->curl_exec_follow($ch, $this->maxredirect); 213 | $this->eno = curl_errno($ch); 214 | $this->emes = curl_error($ch); 215 | 216 | curl_close($ch); 217 | 218 | return $content; 219 | } 220 | 221 | /** 222 | * @param $ch 223 | * @param null $maxredirect 224 | * @return bool|mixed 225 | */ 226 | protected function curl_exec_follow($ch, &$maxredirect = null) 227 | { 228 | // we emulate a browser here since some websites detect 229 | // us as a bot and don't let us do our job 230 | $user_agent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5)" . 231 | " Gecko/20041107 Firefox/1.0"; 232 | curl_setopt($ch, CURLOPT_USERAGENT, $user_agent); 233 | 234 | $mr = $maxredirect === null ? 5 : intval($maxredirect); 235 | 236 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $mr > 0); 237 | curl_setopt($ch, CURLOPT_MAXREDIRS, $mr); 238 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); 239 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); 240 | return curl_exec($ch); 241 | } 242 | 243 | /** 244 | * Function to get the last request made 245 | * @return mixed 246 | */ 247 | public function getLastRequest() 248 | { 249 | return $this->requestUrl; 250 | } 251 | 252 | /** 253 | * Function to return the error if there was one, or FALSE if not. 254 | * @return array|bool 255 | */ 256 | public function getError() 257 | { 258 | if (!empty($this->eno)) { 259 | return ['no' => $this->eno, 'message' => $this->emes]; 260 | } 261 | return FALSE; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /examples/mysql.php: -------------------------------------------------------------------------------- 1 | 'database'). 32 | 33 | $uapi->uapi->Mysql->create_database(array('name' => $database)); //Create the database 34 | $uapi->uapi->Mysql->create_user(array('name' => $databaseuser, 'password' => $databasepass)); //create a user for the new database 35 | 36 | 37 | //After you create the user, you must use the set_privileges_on_database function call to grant access to the 38 | //user for a database. 39 | //add the user, set all privileges - add specific privileges by comma separation. e.g. 'DELETE,UPDATE,CREATE,ALTER' 40 | $uapi->uapi->Mysql->set_privileges_on_database(array('user' => $databaseuser, 'database' => $database, 'privileges' => 'ALL')); 41 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Toby New 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /otphp/LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Le Lag 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /otphp/README.markdown: -------------------------------------------------------------------------------- 1 | # OTPHP - A PHP One Time Password Library 2 | 3 | A php library for generating one time passwords according to [ RFC 4226 ](http://tools.ietf.org/html/rfc4226) and the [ HOTP RFC ](http://tools.ietf.org/html/draft-mraihi-totp-timebased-00) 4 | 5 | This is compatible with Google Authenticator apps available for Android and iPhone, and now in use on GMail 6 | 7 | This is a port of the rotp ruby library available at https://github.com/mdp/rotp 8 | 9 | Note : This project has not been updated since it was created. If you need a PHP OTP library, you should check out the great fork by Spomky-Labs at https://github.com/Spomky-Labs/otphp which has been improved and is currently maintained. 10 | 11 | 12 | ## Quick overview of using One Time Passwords on your phone 13 | 14 | * OTP's involve a shared secret, stored both on the phone and the server 15 | * OTP's can be generated on a phone without internet connectivity(AT&T mode) 16 | * OTP's should always be used as a second factor of authentication(if your phone is lost, you account is still secured with a password) 17 | * Google Authenticator allows you to store multiple OTP secrets and provision those using a QR Code(no more typing in the secret) 18 | 19 | ## Installation 20 | 21 | clone this repository and include lib/otphp.php in your project. 22 | 23 | ## Use 24 | 25 | ### Time based OTP's 26 | 27 | $totp = new \OTPHP\TOTP("base32secret3232"); 28 | $totp->now(); // => 492039 29 | 30 | // OTP verified for current time 31 | $totp->verify(492039); // => true 32 | //30s later 33 | $totp->verify(492039); // => false 34 | 35 | ### Counter based OTP's 36 | 37 | $hotp = new \OTPHP\HOTP("base32secretkey3232"); 38 | $hotp->at(0); // => 260182 39 | $hotp->at(1); // => 55283 40 | $hotp->at(1401); // => 316439 41 | 42 | // OTP verified with a counter 43 | $totp->verify(316439, 1401); // => true 44 | $totp->verify(316439, 1402); // => false 45 | 46 | ### Google Authenticator Compatible 47 | 48 | The library works with the Google Authenticator iPhone and Android app, and also 49 | includes the ability to generate provisioning URI's for use with the QR Code scanner 50 | built into the app. 51 | 52 | $totp->provisioning_uri(); // => 'otpauth://totp/alice@google.com?secret=JBSWY3DPEHPK3PXP' 53 | $hotp->provisioning_uri(); // => 'otpauth://hotp/alice@google.com?secret=JBSWY3DPEHPK3PXP&counter=0' 54 | 55 | This can then be rendered as a QR Code which can then be scanned and added to the users 56 | list of OTP credentials. 57 | 58 | #### Working example 59 | 60 | Scan the following barcode with your phone, using Google Authenticator 61 | 62 | ![QR Code for OTP](http://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=otpauth%3A%2F%2Ftotp%2Falice%40google.com%3Fsecret%3DJBSWY3DPEHPK3PXP) 63 | 64 | Now run the following and compare the output 65 | 66 | now(); 70 | 71 | ## Licence 72 | 73 | This software is release under MIT licence. 74 | -------------------------------------------------------------------------------- /otphp/lib/hotp.php: -------------------------------------------------------------------------------- 1 | generateOTP($count); 46 | } 47 | 48 | 49 | /** 50 | * Verify if a password is valid for a specific counter value 51 | * 52 | * @param integer $otp the one-time password 53 | * @param integer $counter the counter value 54 | * @return bool true if the counter is valid, false otherwise 55 | */ 56 | public function verify($otp, $counter) { 57 | return ($otp == $this->at($counter)); 58 | } 59 | 60 | /** 61 | * Returns the uri for a specific secret for hotp method. 62 | * Can be encoded as a image for simple configuration in 63 | * Google Authenticator. 64 | * 65 | * @param string $name the name of the account / profile 66 | * @param integer $initial_count the initial counter 67 | * @return string the uri for the hmac secret 68 | */ 69 | public function provisioning_uri($name, $initial_count) { 70 | return "otpauth://hotp/".urlencode($name)."?secret={$this->secret}&counter=$initial_count"; 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /otphp/lib/otp.php: -------------------------------------------------------------------------------- 1 | digits = isset($opt['digits']) ? $opt['digits'] : 6; 69 | $this->digest = isset($opt['digest']) ? $opt['digest'] : 'sha1'; 70 | $this->secret = $secret; 71 | } 72 | 73 | /** 74 | * Generate a one-time password 75 | * 76 | * @param integer $input : number used to seed the hmac hash function. 77 | * This number is usually a counter (HOTP) or calculated based on the current 78 | * timestamp (see TOTP class). 79 | * @return integer the one-time password 80 | */ 81 | public function generateOTP($input) { 82 | $hash = hash_hmac($this->digest, $this->intToBytestring($input), $this->byteSecret()); 83 | foreach(str_split($hash, 2) as $hex) { // stupid PHP has bin2hex but no hex2bin WTF 84 | $hmac[] = hexdec($hex); 85 | } 86 | $offset = $hmac[19] & 0xf; 87 | $code = ($hmac[$offset+0] & 0x7F) << 24 | 88 | ($hmac[$offset + 1] & 0xFF) << 16 | 89 | ($hmac[$offset + 2] & 0xFF) << 8 | 90 | ($hmac[$offset + 3] & 0xFF); 91 | return $code % pow(10, $this->digits); 92 | } 93 | 94 | /** 95 | * Returns the binary value of the base32 encoded secret 96 | * @access private 97 | * This method should be private but was left public for 98 | * phpunit tests to work. 99 | * @return binary secret key 100 | */ 101 | public function byteSecret() { 102 | return \Base32::decode($this->secret); 103 | } 104 | 105 | /** 106 | * Turns an integer in a OATH bytestring 107 | * @param integer $int 108 | * @access private 109 | * @return string bytestring 110 | */ 111 | public function intToBytestring($int) { 112 | $result = Array(); 113 | while($int != 0) { 114 | $result[] = chr($int & 0xFF); 115 | $int >>= 8; 116 | } 117 | return str_pad(join(array_reverse($result)), 8, "\000", STR_PAD_LEFT); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /otphp/lib/otphp.php: -------------------------------------------------------------------------------- 1 | interval = isset($opt['interval']) ? $opt['interval'] : 30; 47 | parent::__construct($s, $opt); 48 | } 49 | 50 | /** 51 | * Get the password for a specific timestamp value 52 | * 53 | * @param integer $timestamp the timestamp which is timecoded and 54 | * used to seed the hmac hash function. 55 | * @return integer the One Time Password 56 | */ 57 | public function at($timestamp) { 58 | return $this->generateOTP($this->timecode($timestamp)); 59 | } 60 | 61 | /** 62 | * Get the password for the current timestamp value 63 | * 64 | * @return integer the current One Time Password 65 | */ 66 | public function now() { 67 | return $this->generateOTP($this->timecode(time())); 68 | } 69 | 70 | /** 71 | * Verify if a password is valid for a specific counter value 72 | * 73 | * @param integer $otp the one-time password 74 | * @param integer $timestamp the timestamp for the a given time, defaults to current time. 75 | * @return bool true if the counter is valid, false otherwise 76 | */ 77 | public function verify($otp, $timestamp = null) { 78 | if($timestamp === null) 79 | $timestamp = time(); 80 | return ($otp == $this->at($timestamp)); 81 | } 82 | 83 | /** 84 | * Returns the uri for a specific secret for totp method. 85 | * Can be encoded as a image for simple configuration in 86 | * Google Authenticator. 87 | * 88 | * @param string $name the name of the account / profile 89 | * @return string the uri for the hmac secret 90 | */ 91 | public function provisioning_uri($name) { 92 | return "otpauth://totp/".urlencode($name)."?secret={$this->secret}"; 93 | } 94 | 95 | /** 96 | * Transform a timestamp in a counter based on specified internal 97 | * 98 | * @param integer $timestamp 99 | * @return integer the timecode 100 | */ 101 | protected function timecode($timestamp) { 102 | return (int)( (((int)$timestamp * 1000) / ($this->interval * 1000))); 103 | } 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /otphp/tests/HOTPTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(855783,$o->at(0)); 29 | $this->assertEquals(549607,$o->at(500)); 30 | $this->assertEquals(654666,$o->at(1500)); 31 | } 32 | 33 | public function test_it_verify_the_code() { 34 | $o = new \OTPHP\HOTP('JDDK4U6G3BJLEZ7Y'); 35 | $this->assertTrue($o->verify(855783, 0)); 36 | $this->assertTrue($o->verify(549607, 500)); 37 | $this->assertTrue($o->verify(654666, 1500)); 38 | } 39 | 40 | public function test_it_returns_the_provisioning_uri() { 41 | $o = new \OTPHP\HOTP('JDDK4U6G3BJLEZ7Y'); 42 | $this->assertEquals("otpauth://hotp/name?secret=JDDK4U6G3BJLEZ7Y&counter=0", 43 | $o->provisioning_uri('name', 0)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /otphp/tests/OTPTest.php: -------------------------------------------------------------------------------- 1 | assertEquals("H\306\256S\306\330R\262g\370", $o->byteSecret()); 30 | } 31 | 32 | public function test_it_turns_an_int_into_bytestring() { 33 | $o = new \OTPHP\OTP('JDDK4U6G3BJLEZ7Y'); 34 | $this->assertEquals("\000\000\000\000\000\000\000\000", $o->intToBytestring(0)); 35 | $this->assertEquals("\000\000\000\000\000\000\000\001", $o->intToBytestring(1)); 36 | $this->assertEquals("\000\000\000\000\000\000\001\364", $o->intToBytestring(500)); 37 | $this->assertEquals("\000\000\000\000\000\000\005\334", $o->intToBytestring(1500)); 38 | } 39 | 40 | public function test_it_generate_otp() { 41 | $o = new \OTPHP\OTP('JDDK4U6G3BJLEZ7Y'); 42 | $this->assertEquals(855783, $o->generateOTP(0)); 43 | $this->assertEquals(549607, $o->generateOTP(500)); 44 | $this->assertEquals(654666, $o->generateOTP(1500)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /otphp/tests/TOTPTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(30,$o->interval); 30 | $b = new \OTPHP\TOTP('JDDK4U6G3BJLEZ7Y', Array('interval'=>60)); 31 | $this->assertEquals(60,$b->interval); 32 | } 33 | 34 | public function test_it_gets_the_good_code_at_given_times() { 35 | $o = new \OTPHP\TOTP('JDDK4U6G3BJLEZ7Y'); 36 | $this->assertEquals(855783,$o->at(0)); 37 | $this->assertEquals(762124,$o->at(319690800)); 38 | $this->assertEquals(139664,$o->at(1301012137)); 39 | } 40 | 41 | public function test_it_verify_the_code() { 42 | $o = new \OTPHP\TOTP('JDDK4U6G3BJLEZ7Y'); 43 | $this->assertTrue($o->verify(855783, 0)); 44 | $this->assertTrue($o->verify(762124, 319690800)); 45 | $this->assertTrue($o->verify(139664, 1301012137)); 46 | } 47 | 48 | public function test_it_returns_the_provisioning_uri() { 49 | $o = new \OTPHP\TOTP('JDDK4U6G3BJLEZ7Y'); 50 | $this->assertEquals("otpauth://totp/name?secret=JDDK4U6G3BJLEZ7Y", 51 | $o->provisioning_uri('name')); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /otphp/tests/TestTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(1,1); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /otphp/vendor/base32.php: -------------------------------------------------------------------------------- 1 | '0', 'B'=>'1', 'C'=>'2', 'D'=>'3', 'E'=>'4', 'F'=>'5', 'G'=>'6', 'H'=>'7', 23 | 'I'=>'8', 'J'=>'9', 'K'=>'10', 'L'=>'11', 'M'=>'12', 'N'=>'13', 'O'=>'14', 'P'=>'15', 24 | 'Q'=>'16', 'R'=>'17', 'S'=>'18', 'T'=>'19', 'U'=>'20', 'V'=>'21', 'W'=>'22', 'X'=>'23', 25 | 'Y'=>'24', 'Z'=>'25', '2'=>'26', '3'=>'27', '4'=>'28', '5'=>'29', '6'=>'30', '7'=>'31' 26 | ); 27 | 28 | /** 29 | * Use padding false when encoding for urls 30 | * 31 | * @return base32 encoded string 32 | * @author Bryan Ruiz 33 | **/ 34 | public static function encode($input, $padding = true) { 35 | if(empty($input)) return ""; 36 | $input = str_split($input); 37 | $binaryString = ""; 38 | for($i = 0; $i < count($input); $i++) { 39 | $binaryString .= str_pad(base_convert(ord($input[$i]), 10, 2), 8, '0', STR_PAD_LEFT); 40 | } 41 | $fiveBitBinaryArray = str_split($binaryString, 5); 42 | $base32 = ""; 43 | $i=0; 44 | while($i < count($fiveBitBinaryArray)) { 45 | $base32 .= self::$map[base_convert(str_pad($fiveBitBinaryArray[$i], 5,'0'), 2, 10)]; 46 | $i++; 47 | } 48 | if($padding && ($x = strlen($binaryString) % 40) != 0) { 49 | if($x == 8) $base32 .= str_repeat(self::$map[32], 6); 50 | else if($x == 16) $base32 .= str_repeat(self::$map[32], 4); 51 | else if($x == 24) $base32 .= str_repeat(self::$map[32], 3); 52 | else if($x == 32) $base32 .= self::$map[32]; 53 | } 54 | return $base32; 55 | } 56 | 57 | public static function decode($input) { 58 | if(empty($input)) return; 59 | $paddingCharCount = substr_count($input, self::$map[32]); 60 | $allowedValues = array(6,4,3,1,0); 61 | if(!in_array($paddingCharCount, $allowedValues)) return false; 62 | for($i=0; $i<4; $i++){ 63 | if($paddingCharCount == $allowedValues[$i] && 64 | substr($input, -($allowedValues[$i])) != str_repeat(self::$map[32], $allowedValues[$i])) return false; 65 | } 66 | $input = str_replace('=','', $input); 67 | $input = str_split($input); 68 | $binaryString = ""; 69 | for($i=0; $i < count($input); $i = $i+8) { 70 | $x = ""; 71 | if(!in_array($input[$i], self::$map)) return false; 72 | for($j=0; $j < 8; $j++) { 73 | $x .= str_pad(base_convert(@self::$flippedMap[@$input[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT); 74 | } 75 | $eightBits = str_split($x, 8); 76 | for($z = 0; $z < count($eightBits); $z++) { 77 | $binaryString .= ( ($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48 ) ? $y:""; 78 | } 79 | } 80 | return $binaryString; 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /otphp/vendor/libs.php: -------------------------------------------------------------------------------- 1 |