├── src ├── Credentials │ ├── TokenCredentials.php │ ├── CredentialsException.php │ ├── TemporaryCredentials.php │ ├── ClientCredentialsInterface.php │ ├── ClientCredentials.php │ ├── CredentialsInterface.php │ ├── Credentials.php │ └── RsaClientCredentials.php ├── Signature │ ├── PlainTextSignature.php │ ├── HmacSha1Signature.php │ ├── RsaSha1Signature.php │ ├── SignatureInterface.php │ ├── Signature.php │ └── EncodesUrl.php └── Server │ ├── Bitbucket.php │ ├── Xing.php │ ├── User.php │ ├── Tumblr.php │ ├── Uservoice.php │ ├── X.php │ ├── Twitter.php │ ├── Magento.php │ ├── Trello.php │ └── Server.php ├── LICENSE ├── composer.json └── README.md /src/Credentials/TokenCredentials.php: -------------------------------------------------------------------------------- 1 | key(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Credentials/ClientCredentialsInterface.php: -------------------------------------------------------------------------------- 1 | callbackUri; 20 | } 21 | 22 | /** 23 | * @inheritDoc 24 | */ 25 | public function setCallbackUri($callbackUri) 26 | { 27 | $this->callbackUri = $callbackUri; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Credentials/CredentialsInterface.php: -------------------------------------------------------------------------------- 1 | createUrl($uri); 23 | 24 | $baseString = $this->baseString($url, $method, $parameters); 25 | 26 | return base64_encode($this->hash($baseString)); 27 | } 28 | 29 | /** 30 | * Hashes a string with the signature's key. 31 | * 32 | * @param string $string 33 | * 34 | * @return string 35 | */ 36 | protected function hash($string) 37 | { 38 | return hash_hmac('sha1', $string, $this->key(), true); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Signature/RsaSha1Signature.php: -------------------------------------------------------------------------------- 1 | createUrl($uri); 25 | $baseString = $this->baseString($url, $method, $parameters); 26 | 27 | /** @var RsaClientCredentials $clientCredentials */ 28 | $clientCredentials = $this->clientCredentials; 29 | $privateKey = $clientCredentials->getRsaPrivateKey(); 30 | 31 | openssl_sign($baseString, $signature, $privateKey); 32 | 33 | return base64_encode($signature); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Credentials/Credentials.php: -------------------------------------------------------------------------------- 1 | identifier; 27 | } 28 | 29 | /** 30 | * @inheritDoc 31 | */ 32 | public function setIdentifier($identifier) 33 | { 34 | $this->identifier = $identifier; 35 | } 36 | 37 | /** 38 | * @inheritDoc 39 | */ 40 | public function getSecret() 41 | { 42 | return $this->secret; 43 | } 44 | 45 | /** 46 | * @inheritDoc 47 | */ 48 | public function setSecret($secret) 49 | { 50 | $this->secret = $secret; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Ben Corlett 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Signature/SignatureInterface.php: -------------------------------------------------------------------------------- 1 | clientCredentials = $clientCredentials; 30 | } 31 | 32 | /** 33 | * @inheritDoc 34 | */ 35 | public function setCredentials(CredentialsInterface $credentials) 36 | { 37 | $this->credentials = $credentials; 38 | } 39 | 40 | /** 41 | * Generate a signing key. 42 | * 43 | * @return string 44 | */ 45 | protected function key() 46 | { 47 | $key = rawurlencode($this->clientCredentials->getSecret()) . '&'; 48 | 49 | if ($this->credentials !== null) { 50 | $key .= rawurlencode($this->credentials->getSecret()); 51 | } 52 | 53 | return $key; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "league/oauth1-client", 3 | "description": "OAuth 1.0 Client Library", 4 | "license": "MIT", 5 | "require": { 6 | "php": ">=7.1||>=8.0", 7 | "ext-json": "*", 8 | "ext-openssl": "*", 9 | "guzzlehttp/guzzle": "^6.0|^7.0", 10 | "guzzlehttp/psr7": "^1.7|^2.0" 11 | }, 12 | "require-dev": { 13 | "ext-simplexml": "*", 14 | "phpunit/phpunit": "^7.5||9.5", 15 | "mockery/mockery": "^1.3.3", 16 | "phpstan/phpstan": "^0.12.42", 17 | "friendsofphp/php-cs-fixer": "^2.17" 18 | }, 19 | "scripts": { 20 | "analyze": "vendor/bin/phpstan analyse -l 6 src/", 21 | "php-cs-fixer:lint": "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix --verbose --dry-run", 22 | "php-cs-fixer:format": "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix", 23 | "test:unit": "vendor/bin/phpunit --coverage-text --coverage-clover coverage.xml" 24 | }, 25 | "suggest": { 26 | "ext-simplexml": "For decoding XML-based responses." 27 | }, 28 | "keywords": [ 29 | "oauth", 30 | "oauth1", 31 | "authorization", 32 | "authentication", 33 | "idp", 34 | "identity", 35 | "sso", 36 | "single sign on", 37 | "bitbucket", 38 | "trello", 39 | "tumblr", 40 | "twitter" 41 | ], 42 | "authors": [ 43 | { 44 | "name": "Ben Corlett", 45 | "email": "bencorlett@me.com", 46 | "homepage": "http://www.webcomm.com.au", 47 | "role": "Developer" 48 | } 49 | ], 50 | "autoload": { 51 | "psr-4": { 52 | "League\\OAuth1\\Client\\": "src/" 53 | } 54 | }, 55 | "autoload-dev": { 56 | "psr-4": { 57 | "League\\OAuth1\\Client\\Tests\\": "tests/" 58 | } 59 | }, 60 | "extra": { 61 | "branch-alias": { 62 | "dev-master": "1.0-dev", 63 | "dev-develop": "2.0-dev" 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Server/Bitbucket.php: -------------------------------------------------------------------------------- 1 | uid = $data['user']['username']; 49 | $user->nickname = $data['user']['username']; 50 | $user->name = $data['user']['display_name']; 51 | $user->firstName = $data['user']['first_name']; 52 | $user->lastName = $data['user']['last_name']; 53 | $user->imageUrl = $data['user']['avatar']; 54 | 55 | $used = ['username', 'display_name', 'avatar']; 56 | 57 | foreach ($data as $key => $value) { 58 | if (strpos($key, 'url') !== false) { 59 | if ( ! in_array($key, $used)) { 60 | $used[] = $key; 61 | } 62 | 63 | $user->urls[$key] = $value; 64 | } 65 | } 66 | 67 | // Save all extra data 68 | $user->extra = array_diff_key($data, array_flip($used)); 69 | 70 | return $user; 71 | } 72 | 73 | /** 74 | * @inheritDoc 75 | */ 76 | public function userUid($data, TokenCredentials $tokenCredentials) 77 | { 78 | return $data['user']['username']; 79 | } 80 | 81 | /** 82 | * @inheritDoc 83 | */ 84 | public function userEmail($data, TokenCredentials $tokenCredentials) 85 | { 86 | return null; 87 | } 88 | 89 | /** 90 | * @inheritDoc 91 | */ 92 | public function userScreenName($data, TokenCredentials $tokenCredentials) 93 | { 94 | return $data['user']['display_name']; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Server/Xing.php: -------------------------------------------------------------------------------- 1 | uid = $data['id']; 51 | $user->nickname = $data['display_name']; 52 | $user->name = $data['display_name']; 53 | $user->firstName = $data['first_name']; 54 | $user->lastName = $data['last_name']; 55 | $user->location = $data['private_address']['country']; 56 | 57 | if ($user->location == '') { 58 | $user->location = $data['business_address']['country']; 59 | } 60 | $user->description = $data['employment_status']; 61 | $user->imageUrl = $data['photo_urls']['maxi_thumb']; 62 | $user->email = $data['active_email']; 63 | 64 | $user->urls['permalink'] = $data['permalink']; 65 | 66 | return $user; 67 | } 68 | /** 69 | * @inheritDoc 70 | */ 71 | public function userUid($data, TokenCredentials $tokenCredentials) 72 | { 73 | $data = $data['users'][0]; 74 | 75 | return $data['id']; 76 | } 77 | /** 78 | * @inheritDoc 79 | */ 80 | public function userEmail($data, TokenCredentials $tokenCredentials) 81 | { 82 | $data = $data['users'][0]; 83 | 84 | return $data['active_email']; 85 | } 86 | /** 87 | * @inheritDoc 88 | */ 89 | public function userScreenName($data, TokenCredentials $tokenCredentials) 90 | { 91 | $data = $data['users'][0]; 92 | 93 | return $data['display_name']; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Server/User.php: -------------------------------------------------------------------------------- 1 | {$key})) { 97 | $this->{$key} = $value; 98 | } 99 | } 100 | 101 | /** 102 | * Tells if a property is set. 103 | * 104 | * @param string $key 105 | * 106 | * @return bool 107 | */ 108 | public function __isset($key) 109 | { 110 | return isset($this->{$key}); 111 | } 112 | 113 | /** 114 | * Get a property from the user. 115 | * 116 | * @param string $key 117 | * 118 | * @return mixed 119 | */ 120 | public function __get($key) 121 | { 122 | if (isset($this->{$key})) { 123 | return $this->{$key}; 124 | } 125 | } 126 | 127 | /** 128 | * @inheritDoc 129 | */ 130 | #[\ReturnTypeWillChange] 131 | public function getIterator() 132 | { 133 | return new ArrayIterator(get_object_vars($this)); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Server/Tumblr.php: -------------------------------------------------------------------------------- 1 | nickname = $data['name']; 58 | 59 | // Save all extra data 60 | $used = ['name']; 61 | $user->extra = array_diff_key($data, array_flip($used)); 62 | 63 | return $user; 64 | } 65 | 66 | /** 67 | * @inheritDoc 68 | */ 69 | public function userUid($data, TokenCredentials $tokenCredentials) 70 | { 71 | if ( ! isset($data['response']['user']) || ! is_array($data['response']['user'])) { 72 | throw new LogicException('Not possible to get user UUID'); 73 | } 74 | 75 | $data = $data['response']['user']; 76 | 77 | return $data['name']; 78 | } 79 | 80 | /** 81 | * @inheritDoc 82 | */ 83 | public function userEmail($data, TokenCredentials $tokenCredentials) 84 | { 85 | return null; 86 | } 87 | 88 | /** 89 | * @inheritDoc 90 | */ 91 | public function userScreenName($data, TokenCredentials $tokenCredentials) 92 | { 93 | if ( ! isset($data['response']['user']) || ! is_array($data['response']['user'])) { 94 | throw new LogicException('Not possible to get user screen name'); 95 | } 96 | 97 | $data = $data['response']['user']; 98 | 99 | return $data['name']; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Credentials/RsaClientCredentials.php: -------------------------------------------------------------------------------- 1 | rsaPublicKeyFile = $filename; 39 | $this->rsaPublicKey = null; 40 | 41 | return $this; 42 | } 43 | 44 | /** 45 | * Sets the path to the RSA private key. 46 | * 47 | * @param string $filename 48 | * 49 | * @return self 50 | */ 51 | public function setRsaPrivateKey($filename) 52 | { 53 | $this->rsaPrivateKeyFile = $filename; 54 | $this->rsaPrivateKey = null; 55 | 56 | return $this; 57 | } 58 | 59 | /** 60 | * Gets the RSA public key. 61 | * 62 | * @throws CredentialsException when the key could not be loaded. 63 | * 64 | * @return resource|OpenSSLAsymmetricKey 65 | */ 66 | public function getRsaPublicKey() 67 | { 68 | if ($this->rsaPublicKey) { 69 | return $this->rsaPublicKey; 70 | } 71 | 72 | if ( ! file_exists($this->rsaPublicKeyFile)) { 73 | throw new CredentialsException('Could not read the public key file.'); 74 | } 75 | 76 | $this->rsaPublicKey = openssl_get_publickey(file_get_contents($this->rsaPublicKeyFile)); 77 | 78 | if ( ! $this->rsaPublicKey) { 79 | throw new CredentialsException('Cannot access public key for signing'); 80 | } 81 | 82 | return $this->rsaPublicKey; 83 | } 84 | 85 | /** 86 | * Gets the RSA private key. 87 | * 88 | * @throws CredentialsException when the key could not be loaded. 89 | * 90 | * @return resource|OpenSSLAsymmetricKey 91 | */ 92 | public function getRsaPrivateKey() 93 | { 94 | if ($this->rsaPrivateKey) { 95 | return $this->rsaPrivateKey; 96 | } 97 | 98 | if ( ! file_exists($this->rsaPrivateKeyFile)) { 99 | throw new CredentialsException('Could not read the private key file.'); 100 | } 101 | 102 | $this->rsaPrivateKey = openssl_pkey_get_private(file_get_contents($this->rsaPrivateKeyFile)); 103 | 104 | if ( ! $this->rsaPrivateKey) { 105 | throw new CredentialsException('Cannot access private key for signing'); 106 | } 107 | 108 | return $this->rsaPrivateKey; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Server/Uservoice.php: -------------------------------------------------------------------------------- 1 | parseConfigurationArray($clientCredentials); 27 | } 28 | } 29 | 30 | /** 31 | * @inheritDoc 32 | */ 33 | public function urlTemporaryCredentials() 34 | { 35 | return $this->base . '/oauth/request_token'; 36 | } 37 | 38 | /** 39 | * @inheritDoc 40 | */ 41 | public function urlAuthorization() 42 | { 43 | return $this->base . '/oauth/authorize'; 44 | } 45 | 46 | /** 47 | * @inheritDoc 48 | */ 49 | public function urlTokenCredentials() 50 | { 51 | return $this->base . '/oauth/access_token'; 52 | } 53 | 54 | /** 55 | * @inheritDoc 56 | */ 57 | public function urlUserDetails() 58 | { 59 | return $this->base . '/api/v1/users/current.json'; 60 | } 61 | 62 | /** 63 | * @inheritDoc 64 | */ 65 | public function userDetails($data, TokenCredentials $tokenCredentials) 66 | { 67 | $user = new User(); 68 | 69 | $user->uid = $data['user']['id']; 70 | $user->name = $data['user']['name']; 71 | $user->imageUrl = $data['user']['avatar_url']; 72 | $user->email = $data['user']['email']; 73 | 74 | if ($data['user']['name']) { 75 | $parts = explode(' ', $data['user']['name'], 2); 76 | 77 | $user->firstName = $parts[0]; 78 | 79 | if (2 === count($parts)) { 80 | $user->lastName = $parts[1]; 81 | } 82 | } 83 | 84 | $user->urls[] = $data['user']['url']; 85 | 86 | return $user; 87 | } 88 | 89 | /** 90 | * @inheritDoc 91 | */ 92 | public function userUid($data, TokenCredentials $tokenCredentials) 93 | { 94 | return $data['user']['id']; 95 | } 96 | 97 | /** 98 | * @inheritDoc 99 | */ 100 | public function userEmail($data, TokenCredentials $tokenCredentials) 101 | { 102 | return $data['user']['email']; 103 | } 104 | 105 | /** 106 | * @inheritDoc 107 | */ 108 | public function userScreenName($data, TokenCredentials $tokenCredentials) 109 | { 110 | return $data['user']['name']; 111 | } 112 | 113 | /** 114 | * Parse configuration array to set attributes. 115 | * 116 | * @param array $configuration 117 | * 118 | * @return void 119 | * 120 | * @throws InvalidArgumentException 121 | */ 122 | private function parseConfigurationArray(array $configuration = []) 123 | { 124 | if (isset($configuration['host'])) { 125 | throw new InvalidArgumentException('Missing host'); 126 | } 127 | 128 | $this->base = trim($configuration['host'], '/'); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Signature/EncodesUrl.php: -------------------------------------------------------------------------------- 1 | $url->getScheme(), 39 | 'host' => $url->getHost(), 40 | 'port' => $url->getPort(), 41 | 'path' => $url->getPath(), 42 | ]); 43 | 44 | $baseString .= rawurlencode($schemeHostPath) . '&'; 45 | 46 | parse_str($url->getQuery(), $query); 47 | $data = array_merge($query, $parameters); 48 | 49 | // normalize data key/values 50 | $data = $this->normalizeArray($data); 51 | ksort($data); 52 | 53 | $baseString .= $this->queryStringFromData($data); 54 | 55 | return $baseString; 56 | } 57 | 58 | /** 59 | * Return a copy of the given array with all keys and values rawurlencoded. 60 | * 61 | * @param array $array Array to normalize 62 | * 63 | * @return array Normalized array 64 | */ 65 | protected function normalizeArray(array $array = []) 66 | { 67 | $normalizedArray = []; 68 | 69 | foreach ($array as $key => $value) { 70 | $key = rawurlencode(rawurldecode($key)); 71 | 72 | if (is_array($value)) { 73 | $normalizedArray[$key] = $this->normalizeArray($value); 74 | } else { 75 | $normalizedArray[$key] = rawurlencode(rawurldecode($value)); 76 | } 77 | } 78 | 79 | return $normalizedArray; 80 | } 81 | 82 | /** 83 | * Creates an array of rawurlencoded strings out of each array key/value pair 84 | * Handles multi-dimensional arrays recursively. 85 | * 86 | * @param array $data Array of parameters to convert. 87 | * @param array|null $queryParams Array to extend. False by default. 88 | * @param string $prevKey Optional Array key to append 89 | * 90 | * @return string rawurlencoded string version of data 91 | */ 92 | protected function queryStringFromData($data, $queryParams = null, $prevKey = '') 93 | { 94 | if ($initial = (null === $queryParams)) { 95 | $queryParams = []; 96 | } 97 | 98 | foreach ($data as $key => $value) { 99 | if ($prevKey) { 100 | $key = $prevKey . '[' . $key . ']'; // Handle multi-dimensional array 101 | } 102 | if (is_array($value)) { 103 | $queryParams = $this->queryStringFromData($value, $queryParams, $key); 104 | } else { 105 | $queryParams[] = rawurlencode($key . '=' . $value); // join with equals sign 106 | } 107 | } 108 | 109 | if ($initial) { 110 | return implode('%26', $queryParams); // join with ampersand 111 | } 112 | 113 | return $queryParams; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Server/X.php: -------------------------------------------------------------------------------- 1 | parseConfiguration($clientCredentials); 26 | } 27 | } 28 | 29 | /** 30 | * Set the application scope. 31 | * 32 | * @param ?string $applicationScope 33 | * 34 | * @return Twitter 35 | */ 36 | public function setApplicationScope($applicationScope) 37 | { 38 | $this->applicationScope = $applicationScope; 39 | 40 | return $this; 41 | } 42 | 43 | /** 44 | * Get application scope. 45 | * 46 | * @return ?string 47 | */ 48 | public function getApplicationScope() 49 | { 50 | return $this->applicationScope; 51 | } 52 | 53 | /** 54 | * @inheritDoc 55 | */ 56 | public function urlTemporaryCredentials() 57 | { 58 | $url = 'https://api.x.com/oauth/request_token'; 59 | $queryParams = $this->temporaryCredentialsQueryParameters(); 60 | 61 | return empty($queryParams) ? $url : $url . '?' . $queryParams; 62 | } 63 | 64 | /** 65 | * @inheritDoc 66 | */ 67 | public function urlAuthorization() 68 | { 69 | return 'https://api.x.com/oauth/authenticate'; 70 | } 71 | 72 | /** 73 | * @inheritDoc 74 | */ 75 | public function urlTokenCredentials() 76 | { 77 | return 'https://api.x.com/oauth/access_token'; 78 | } 79 | 80 | /** 81 | * @inheritDoc 82 | */ 83 | public function urlUserDetails() 84 | { 85 | return 'https://api.x.com/1.1/account/verify_credentials.json?include_email=true'; 86 | } 87 | 88 | /** 89 | * @inheritDoc 90 | */ 91 | public function userDetails($data, TokenCredentials $tokenCredentials) 92 | { 93 | $user = new User(); 94 | 95 | $user->uid = $data['id_str']; 96 | $user->nickname = $data['screen_name']; 97 | $user->name = $data['name']; 98 | $user->location = $data['location']; 99 | $user->description = $data['description']; 100 | $user->imageUrl = $data['profile_image_url']; 101 | 102 | if (isset($data['email'])) { 103 | $user->email = $data['email']; 104 | } 105 | 106 | $used = ['id', 'screen_name', 'name', 'location', 'description', 'profile_image_url', 'email']; 107 | 108 | foreach ($data as $key => $value) { 109 | if (strpos($key, 'url') !== false) { 110 | if ( ! in_array($key, $used)) { 111 | $used[] = $key; 112 | } 113 | 114 | $user->urls[$key] = $value; 115 | } 116 | } 117 | 118 | // Save all extra data 119 | $user->extra = array_diff_key($data, array_flip($used)); 120 | 121 | return $user; 122 | } 123 | 124 | /** 125 | * @inheritDoc 126 | */ 127 | public function userUid($data, TokenCredentials $tokenCredentials) 128 | { 129 | return $data['id']; 130 | } 131 | 132 | /** 133 | * @inheritDoc 134 | */ 135 | public function userEmail($data, TokenCredentials $tokenCredentials) 136 | { 137 | return null; 138 | } 139 | 140 | /** 141 | * @inheritDoc 142 | */ 143 | public function userScreenName($data, TokenCredentials $tokenCredentials) 144 | { 145 | return $data['name']; 146 | } 147 | 148 | /** 149 | * Query parameters for a Twitter OAuth request to get temporary credentials. 150 | * 151 | * @return string 152 | */ 153 | protected function temporaryCredentialsQueryParameters() 154 | { 155 | $queryParams = []; 156 | 157 | if ($scope = $this->getApplicationScope()) { 158 | $queryParams['x_auth_access_type'] = $scope; 159 | } 160 | 161 | return http_build_query($queryParams); 162 | } 163 | 164 | /** 165 | * Parse configuration array to set attributes. 166 | * 167 | * @param array $configuration 168 | * 169 | * @return void 170 | */ 171 | private function parseConfiguration(array $configuration = []) 172 | { 173 | $configToPropertyMap = [ 174 | 'scope' => 'applicationScope', 175 | ]; 176 | 177 | foreach ($configToPropertyMap as $config => $property) { 178 | if (isset($configuration[$config])) { 179 | $this->$property = $configuration[$config]; 180 | } 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/Server/Twitter.php: -------------------------------------------------------------------------------- 1 | parseConfiguration($clientCredentials); 26 | } 27 | } 28 | 29 | /** 30 | * Set the application scope. 31 | * 32 | * @param ?string $applicationScope 33 | * 34 | * @return Twitter 35 | */ 36 | public function setApplicationScope($applicationScope) 37 | { 38 | $this->applicationScope = $applicationScope; 39 | 40 | return $this; 41 | } 42 | 43 | /** 44 | * Get application scope. 45 | * 46 | * @return ?string 47 | */ 48 | public function getApplicationScope() 49 | { 50 | return $this->applicationScope; 51 | } 52 | 53 | /** 54 | * @inheritDoc 55 | */ 56 | public function urlTemporaryCredentials() 57 | { 58 | $url = 'https://api.twitter.com/oauth/request_token'; 59 | $queryParams = $this->temporaryCredentialsQueryParameters(); 60 | 61 | return empty($queryParams) ? $url : $url . '?' . $queryParams; 62 | } 63 | 64 | /** 65 | * @inheritDoc 66 | */ 67 | public function urlAuthorization() 68 | { 69 | return 'https://api.twitter.com/oauth/authenticate'; 70 | } 71 | 72 | /** 73 | * @inheritDoc 74 | */ 75 | public function urlTokenCredentials() 76 | { 77 | return 'https://api.twitter.com/oauth/access_token'; 78 | } 79 | 80 | /** 81 | * @inheritDoc 82 | */ 83 | public function urlUserDetails() 84 | { 85 | return 'https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true'; 86 | } 87 | 88 | /** 89 | * @inheritDoc 90 | */ 91 | public function userDetails($data, TokenCredentials $tokenCredentials) 92 | { 93 | $user = new User(); 94 | 95 | $user->uid = $data['id_str']; 96 | $user->nickname = $data['screen_name']; 97 | $user->name = $data['name']; 98 | $user->location = $data['location']; 99 | $user->description = $data['description']; 100 | $user->imageUrl = $data['profile_image_url']; 101 | 102 | if (isset($data['email'])) { 103 | $user->email = $data['email']; 104 | } 105 | 106 | $used = ['id', 'screen_name', 'name', 'location', 'description', 'profile_image_url', 'email']; 107 | 108 | foreach ($data as $key => $value) { 109 | if (strpos($key, 'url') !== false) { 110 | if ( ! in_array($key, $used)) { 111 | $used[] = $key; 112 | } 113 | 114 | $user->urls[$key] = $value; 115 | } 116 | } 117 | 118 | // Save all extra data 119 | $user->extra = array_diff_key($data, array_flip($used)); 120 | 121 | return $user; 122 | } 123 | 124 | /** 125 | * @inheritDoc 126 | */ 127 | public function userUid($data, TokenCredentials $tokenCredentials) 128 | { 129 | return $data['id']; 130 | } 131 | 132 | /** 133 | * @inheritDoc 134 | */ 135 | public function userEmail($data, TokenCredentials $tokenCredentials) 136 | { 137 | return null; 138 | } 139 | 140 | /** 141 | * @inheritDoc 142 | */ 143 | public function userScreenName($data, TokenCredentials $tokenCredentials) 144 | { 145 | return $data['name']; 146 | } 147 | 148 | /** 149 | * Query parameters for a Twitter OAuth request to get temporary credentials. 150 | * 151 | * @return string 152 | */ 153 | protected function temporaryCredentialsQueryParameters() 154 | { 155 | $queryParams = []; 156 | 157 | if ($scope = $this->getApplicationScope()) { 158 | $queryParams['x_auth_access_type'] = $scope; 159 | } 160 | 161 | return http_build_query($queryParams); 162 | } 163 | 164 | /** 165 | * Parse configuration array to set attributes. 166 | * 167 | * @param array $configuration 168 | * 169 | * @return void 170 | */ 171 | private function parseConfiguration(array $configuration = []) 172 | { 173 | $configToPropertyMap = [ 174 | 'scope' => 'applicationScope', 175 | ]; 176 | 177 | foreach ($configToPropertyMap as $config => $property) { 178 | if (isset($configuration[$config])) { 179 | $this->$property = $configuration[$config]; 180 | } 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/Server/Magento.php: -------------------------------------------------------------------------------- 1 | parseConfigurationArray($clientCredentials); 59 | } 60 | } 61 | 62 | /** 63 | * @inheritDoc 64 | */ 65 | public function urlTemporaryCredentials() 66 | { 67 | return $this->baseUri . '/oauth/initiate'; 68 | } 69 | 70 | /** 71 | * @inheritDoc 72 | */ 73 | public function urlAuthorization() 74 | { 75 | return $this->isAdmin 76 | ? $this->adminUrl 77 | : $this->baseUri . '/oauth/authorize'; 78 | } 79 | 80 | /** 81 | * @inheritDoc 82 | */ 83 | public function urlTokenCredentials() 84 | { 85 | return $this->baseUri . '/oauth/token'; 86 | } 87 | 88 | /** 89 | * @inheritDoc 90 | */ 91 | public function urlUserDetails() 92 | { 93 | return $this->baseUri . '/api/rest/customers'; 94 | } 95 | 96 | /** 97 | * @inheritDoc 98 | */ 99 | public function userDetails($data, TokenCredentials $tokenCredentials) 100 | { 101 | if ( ! is_array($data) || ! count($data)) { 102 | throw new \Exception('Not possible to get user info'); 103 | } 104 | 105 | $id = key($data); 106 | $data = current($data); 107 | 108 | $user = new User(); 109 | $user->uid = $id; 110 | 111 | $mapping = [ 112 | 'email' => 'email', 113 | 'firstName' => 'firstname', 114 | 'lastName' => 'lastname', 115 | ]; 116 | foreach ($mapping as $userKey => $dataKey) { 117 | if ( ! isset($data[$dataKey])) { 118 | continue; 119 | } 120 | $user->{$userKey} = $data[$dataKey]; 121 | } 122 | 123 | $user->extra = array_diff_key($data, array_flip($mapping)); 124 | 125 | return $user; 126 | } 127 | 128 | /** 129 | * @inheritDoc 130 | */ 131 | public function userUid($data, TokenCredentials $tokenCredentials) 132 | { 133 | return key($data); 134 | } 135 | 136 | /** 137 | * @inheritDoc 138 | */ 139 | public function userEmail($data, TokenCredentials $tokenCredentials) 140 | { 141 | $data = current($data); 142 | 143 | if ( ! isset($data['email'])) { 144 | return null; 145 | } 146 | 147 | return $data['email']; 148 | } 149 | 150 | /** 151 | * @inheritDoc 152 | */ 153 | public function userScreenName($data, TokenCredentials $tokenCredentials) 154 | { 155 | return null; 156 | } 157 | 158 | /** 159 | * @inheritDoc 160 | */ 161 | public function getTokenCredentials(TemporaryCredentials $temporaryCredentials, $temporaryIdentifier, $verifier) 162 | { 163 | $this->verifier = $verifier; 164 | 165 | return parent::getTokenCredentials($temporaryCredentials, $temporaryIdentifier, $verifier); 166 | } 167 | 168 | /** 169 | * @inheritDoc 170 | */ 171 | protected function additionalProtocolParameters() 172 | { 173 | return [ 174 | 'oauth_verifier' => $this->verifier, 175 | ]; 176 | } 177 | 178 | protected function getHttpClientDefaultHeaders() 179 | { 180 | $defaultHeaders = parent::getHttpClientDefaultHeaders(); 181 | // Accept header is required, @see Mage_Api2_Model_Renderer::factory 182 | $defaultHeaders['Accept'] = 'application/json'; 183 | 184 | return $defaultHeaders; 185 | } 186 | 187 | /** 188 | * Parse configuration array to set attributes. 189 | * 190 | * @param array $configuration 191 | * 192 | * @return void 193 | * 194 | * @throws \Exception 195 | */ 196 | private function parseConfigurationArray(array $configuration = []) 197 | { 198 | if ( ! isset($configuration['host'])) { 199 | throw new \Exception('Missing Magento Host'); 200 | } 201 | $url = parse_url($configuration['host']); 202 | $this->baseUri = sprintf('%s://%s', $url['scheme'], $url['host']); 203 | 204 | if (isset($url['port'])) { 205 | $this->baseUri .= ':' . $url['port']; 206 | } 207 | 208 | if (isset($url['path'])) { 209 | $this->baseUri .= '/' . trim($url['path'], '/'); 210 | } 211 | $this->isAdmin = ! empty($configuration['admin']); 212 | if ( ! empty($configuration['adminUrl'])) { 213 | $this->adminUrl = $configuration['adminUrl'] . '/oauth_authorize'; 214 | } else { 215 | $this->adminUrl = $this->baseUri . '/admin/oauth_authorize'; 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/Server/Trello.php: -------------------------------------------------------------------------------- 1 | parseConfiguration($clientCredentials); 54 | } 55 | } 56 | 57 | /** 58 | * Set the access token. 59 | * 60 | * @param string $accessToken 61 | * 62 | * @return Trello 63 | */ 64 | public function setAccessToken($accessToken) 65 | { 66 | $this->accessToken = $accessToken; 67 | 68 | return $this; 69 | } 70 | 71 | /** 72 | * Set the application expiration. 73 | * 74 | * @param string $applicationExpiration 75 | * 76 | * @return Trello 77 | */ 78 | public function setApplicationExpiration($applicationExpiration) 79 | { 80 | $this->applicationExpiration = $applicationExpiration; 81 | 82 | return $this; 83 | } 84 | 85 | /** 86 | * Get application expiration. 87 | * 88 | * @return string 89 | */ 90 | public function getApplicationExpiration() 91 | { 92 | return $this->applicationExpiration ?: '1day'; 93 | } 94 | 95 | /** 96 | * Set the application name. 97 | * 98 | * @param string $applicationName 99 | * 100 | * @return Trello 101 | */ 102 | public function setApplicationName($applicationName) 103 | { 104 | $this->applicationName = $applicationName; 105 | 106 | return $this; 107 | } 108 | 109 | /** 110 | * Get application name. 111 | * 112 | * @return string|null 113 | */ 114 | public function getApplicationName() 115 | { 116 | return $this->applicationName ?: null; 117 | } 118 | 119 | /** 120 | * Set the application scope. 121 | * 122 | * @param string $applicationScope 123 | * 124 | * @return Trello 125 | */ 126 | public function setApplicationScope($applicationScope) 127 | { 128 | $this->applicationScope = $applicationScope; 129 | 130 | return $this; 131 | } 132 | 133 | /** 134 | * Get application scope. 135 | * 136 | * @return string 137 | */ 138 | public function getApplicationScope() 139 | { 140 | return $this->applicationScope ?: 'read'; 141 | } 142 | 143 | /** 144 | * @inheritDoc 145 | */ 146 | public function urlTemporaryCredentials() 147 | { 148 | return 'https://trello.com/1/OAuthGetRequestToken'; 149 | } 150 | 151 | /** 152 | * @inheritDoc 153 | */ 154 | public function urlAuthorization() 155 | { 156 | return 'https://trello.com/1/OAuthAuthorizeToken?' . 157 | $this->buildAuthorizationQueryParameters(); 158 | } 159 | 160 | /** 161 | * @inheritDoc 162 | */ 163 | public function urlTokenCredentials() 164 | { 165 | return 'https://trello.com/1/OAuthGetAccessToken'; 166 | } 167 | 168 | /** 169 | * @inheritDoc 170 | */ 171 | public function urlUserDetails() 172 | { 173 | return 'https://trello.com/1/members/me?key=' . $this->applicationKey . '&token=' . $this->accessToken; 174 | } 175 | 176 | /** 177 | * @inheritDoc 178 | */ 179 | public function userDetails($data, TokenCredentials $tokenCredentials) 180 | { 181 | $user = new User(); 182 | 183 | $user->nickname = $data['username']; 184 | $user->name = $data['fullName']; 185 | 186 | $user->extra = (array) $data; 187 | 188 | return $user; 189 | } 190 | 191 | /** 192 | * @inheritDoc 193 | */ 194 | public function userUid($data, TokenCredentials $tokenCredentials) 195 | { 196 | return $data['id']; 197 | } 198 | 199 | /** 200 | * @inheritDoc 201 | */ 202 | public function userEmail($data, TokenCredentials $tokenCredentials) 203 | { 204 | return null; 205 | } 206 | 207 | /** 208 | * @inheritDoc 209 | */ 210 | public function userScreenName($data, TokenCredentials $tokenCredentials) 211 | { 212 | return $data['username']; 213 | } 214 | 215 | /** 216 | * Build authorization query parameters. 217 | * 218 | * @return string 219 | */ 220 | private function buildAuthorizationQueryParameters() 221 | { 222 | $params = [ 223 | 'response_type' => 'fragment', 224 | 'scope' => $this->getApplicationScope(), 225 | 'expiration' => $this->getApplicationExpiration(), 226 | 'name' => $this->getApplicationName(), 227 | ]; 228 | 229 | return http_build_query($params); 230 | } 231 | 232 | /** 233 | * Parse configuration array to set attributes. 234 | * 235 | * @param array $configuration 236 | * 237 | * @return void 238 | */ 239 | private function parseConfiguration(array $configuration = []) 240 | { 241 | $configToPropertyMap = [ 242 | 'identifier' => 'applicationKey', 243 | 'expiration' => 'applicationExpiration', 244 | 'name' => 'applicationName', 245 | 'scope' => 'applicationScope', 246 | ]; 247 | 248 | foreach ($configToPropertyMap as $config => $property) { 249 | if (isset($configuration[$config])) { 250 | $this->$property = $configuration[$config]; 251 | } 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OAuth 1.0 Client 2 | 3 | [![Latest Stable Version](https://img.shields.io/github/release/thephpleague/oauth1-client.svg?style=flat-square)](https://github.com/thephpleague/oauth1-client/releases) 4 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 5 | [![Build Status](https://img.shields.io/travis/thephpleague/oauth1-client/master.svg?style=flat-square&1)](https://travis-ci.org/thephpleague/oauth1-client) 6 | [![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/oauth1-client.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/oauth1-client/code-structure) 7 | [![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/oauth1-client.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/oauth1-client) 8 | [![Total Downloads](https://img.shields.io/packagist/dt/league/oauth1-client.svg?style=flat-square)](https://packagist.org/packages/thephpleague/oauth1-client) 9 | 10 | OAuth 1 Client is an OAuth [RFC 5849 standards-compliant](http://tools.ietf.org/html/rfc5849) library for authenticating against OAuth 1 servers. 11 | 12 | It has built in support for: 13 | 14 | - Bitbucket 15 | - Magento 16 | - Trello 17 | - Tumblr 18 | - Twitter 19 | - Uservoice 20 | - Xing 21 | 22 | Adding support for other providers is trivial. The library requires PHP 7.1+ and is PSR-2 compatible. 23 | 24 | ### Third-Party Providers 25 | 26 | If you would like to support other providers, please make them available as a Composer package, then link to them 27 | below. 28 | 29 | These providers allow integration with other providers not supported by `oauth1-client`. They may require an older version 30 | so please help them out with a pull request if you notice this. 31 | 32 | - [Intuit](https://packagist.org/packages/wheniwork/oauth1-intuit) 33 | - [500px](https://packagist.org/packages/mechant/oauth1-500px) 34 | - [Etsy](https://packagist.org/packages/y0lk/oauth1-etsy) 35 | - [Xero](https://packagist.org/packages/Invoiced/oauth1-xero) 36 | - [Garmin](https://packagist.org/packages/techgyani/garmin-wellness) 37 | - [Goodreads](https://packagist.org/packages/netgalley/oauth1-goodreads) 38 | 39 | #### Terminology (as per the RFC 5849 specification): 40 | 41 | client 42 | An HTTP client (per [RFC2616]) capable of making OAuth- 43 | authenticated requests (Section 3). 44 | 45 | server 46 | An HTTP server (per [RFC2616]) capable of accepting OAuth- 47 | authenticated requests (Section 3). 48 | 49 | protected resource 50 | An access-restricted resource that can be obtained from the 51 | server using an OAuth-authenticated request (Section 3). 52 | 53 | resource owner 54 | An entity capable of accessing and controlling protected 55 | resources by using credentials to authenticate with the server. 56 | 57 | credentials 58 | Credentials are a pair of a unique identifier and a matching 59 | shared secret. OAuth defines three classes of credentials: 60 | client, temporary, and token, used to identify and authenticate 61 | the client making the request, the authorization request, and 62 | the access grant, respectively. 63 | 64 | token 65 | A unique identifier issued by the server and used by the client 66 | to associate authenticated requests with the resource owner 67 | whose authorization is requested or has been obtained by the 68 | client. Tokens have a matching shared-secret that is used by 69 | the client to establish its ownership of the token, and its 70 | authority to represent the resource owner. 71 | 72 | The original community specification used a somewhat different 73 | terminology that maps to this specifications as follows (original 74 | community terms provided on left): 75 | 76 | Consumer: client 77 | 78 | Service Provider: server 79 | 80 | User: resource owner 81 | 82 | Consumer Key and Secret: client credentials 83 | 84 | Request Token and Secret: temporary credentials 85 | 86 | Access Token and Secret: token credentials 87 | 88 | 89 | ## Install 90 | 91 | Via Composer 92 | 93 | ```shell 94 | $ composer require league/oauth1-client 95 | ``` 96 | 97 | 98 | ## Usage 99 | 100 | ### Bitbucket 101 | 102 | ```php 103 | $server = new League\OAuth1\Client\Server\Bitbucket([ 104 | 'identifier' => 'your-identifier', 105 | 'secret' => 'your-secret', 106 | 'callback_uri' => "http://your-callback-uri/", 107 | ]); 108 | ``` 109 | 110 | ### Trello 111 | 112 | ```php 113 | $server = new League\OAuth1\Client\Server\Trello([ 114 | 'identifier' => 'your-identifier', 115 | 'secret' => 'your-secret', 116 | 'callback_uri' => 'http://your-callback-uri/', 117 | 'name' => 'your-application-name', // optional, defaults to null 118 | 'expiration' => 'your-application-expiration', // optional ('never', '1day', '2days'), defaults to '1day' 119 | 'scope' => 'your-application-scope' // optional ('read', 'read,write'), defaults to 'read' 120 | ]); 121 | ``` 122 | 123 | ### Tumblr 124 | 125 | ```php 126 | $server = new League\OAuth1\Client\Server\Tumblr([ 127 | 'identifier' => 'your-identifier', 128 | 'secret' => 'your-secret', 129 | 'callback_uri' => "http://your-callback-uri/", 130 | ]); 131 | ``` 132 | 133 | ### Twitter 134 | 135 | ```php 136 | $server = new League\OAuth1\Client\Server\Twitter([ 137 | 'identifier' => 'your-identifier', 138 | 'secret' => 'your-secret', 139 | 'callback_uri' => "http://your-callback-uri/", 140 | 'scope' => 'your-application-scope' // optional ('read', 'write'), empty by default 141 | ]); 142 | ``` 143 | 144 | ### Xing 145 | 146 | ```php 147 | $server = new League\OAuth1\Client\Server\Xing([ 148 | 'identifier' => 'your-consumer-key', 149 | 'secret' => 'your-consumer-secret', 150 | 'callback_uri' => "http://your-callback-uri/", 151 | ]); 152 | ``` 153 | 154 | ### Showing a Login Button 155 | 156 | To begin, it's advisable that you include a login button on your website. Most servers (Twitter, Tumblr etc) have resources available for making buttons that are familiar to users. Some servers actually require you use their buttons as part of their terms. 157 | 158 | ```html 159 | Login With Twitter 160 | ``` 161 | 162 | ### Retrieving Temporary Credentials 163 | 164 | The first step to authenticating with OAuth 1 is to retrieve temporary credentials. These have been referred to as **request tokens** in earlier versions of OAuth 1. 165 | 166 | To do this, we'll retrieve and store temporary credentials in the session, and redirect the user to the server: 167 | 168 | ```php 169 | // Retrieve temporary credentials 170 | $temporaryCredentials = $server->getTemporaryCredentials(); 171 | 172 | // Store credentials in the session, we'll need them later 173 | $_SESSION['temporary_credentials'] = serialize($temporaryCredentials); 174 | session_write_close(); 175 | 176 | // Second part of OAuth 1.0 authentication is to redirect the 177 | // resource owner to the login screen on the server. 178 | $server->authorize($temporaryCredentials); 179 | ``` 180 | 181 | The user will be redirected to the familiar login screen on the server, where they will login to their account and authorise your app to access their data. 182 | 183 | ### Retrieving Token Credentials 184 | 185 | Once the user has authenticated (or denied) your application, they will be redirected to the `callback_uri` which you specified when creating the server. 186 | 187 | > Note, some servers (such as Twitter) require that the callback URI you specify when authenticating matches what you registered with their app. This is to stop a potential third party impersonating you. This is actually part of the protocol however some servers choose to ignore this. 188 | > 189 | > Because of this, we actually require you specify a callback URI for all servers, regardless of whether the server requires it or not. This is good practice. 190 | 191 | You'll need to handle when the user is redirected back. This will involve retrieving token credentials, which you may then use to make calls to the server on behalf of the user. These have been referred to as **access tokens** in earlier versions of OAuth 1. 192 | 193 | ```php 194 | if (isset($_GET['oauth_token']) && isset($_GET['oauth_verifier'])) { 195 | // Retrieve the temporary credentials we saved before 196 | $temporaryCredentials = unserialize($_SESSION['temporary_credentials']); 197 | 198 | // We will now retrieve token credentials from the server 199 | $tokenCredentials = $server->getTokenCredentials($temporaryCredentials, $_GET['oauth_token'], $_GET['oauth_verifier']); 200 | } 201 | ``` 202 | 203 | Now, you may choose to do what you need with the token credentials. You may store them in a database, in the session, or use them as one-off and then forget about them. 204 | 205 | All credentials, (`client credentials`, `temporary credentials` and `token credentials`) all implement `League\OAuth1\Client\Credentials\CredentialsInterface` and have two sets of setters and getters exposed: 206 | 207 | ```php 208 | var_dump($tokenCredentials->getIdentifier()); 209 | var_dump($tokenCredentials->getSecret()); 210 | ``` 211 | 212 | In earlier versions of OAuth 1, the token credentials identifier and token credentials secret were referred to as **access token** and **access token secret**. Don't be scared by the new terminology here - they are the same. This package is using the exact terminology in the RFC 5849 OAuth 1 standard. 213 | 214 | > Twitter will send back an error message in the `denied` query string parameter, allowing you to provide feedback. Some servers do not send back an error message, but rather do not provide the successful `oauth_token` and `oauth_verifier` parameters. 215 | 216 | ### Accessing User Information 217 | 218 | Now you have token credentials stored somewhere, you may use them to make calls against the server, as an authenticated user. 219 | 220 | While this package is not intended to be a wrapper for every server's API, it does include basic methods that you may use to retrieve limited information. An example of where this may be useful is if you are using social logins, you only need limited information to confirm who the user is. 221 | 222 | The four exposed methods are: 223 | 224 | ```php 225 | // User is an instance of League\OAuth1\Client\Server\User 226 | $user = $server->getUserDetails($tokenCredentials); 227 | 228 | // UID is a string / integer unique representation of the user 229 | $uid = $server->getUserUid($tokenCredentials); 230 | 231 | // Email is either a string or null (as some providers do not supply this data) 232 | $email = $server->getUserEmail($tokenCredentials); 233 | 234 | // Screen name is also known as a username (Twitter handle etc) 235 | $screenName = $server->getUserScreenName($tokenCredentials); 236 | ``` 237 | 238 | > `League\OAuth1\Client\Server\User` exposes a number of default public properties and also stores any additional data in an extra array - `$user->extra`. You may also iterate over a user's properties as if it was an array, `foreach ($user as $key => $value)`. 239 | 240 | ## Examples 241 | 242 | Examples may be found under the [resources/examples](https://github.com/thephpleague/oauth1-client/tree/master/resources/examples) directory, which take the usage instructions here and go into a bit more depth. They are working examples that would only you substitute in your client credentials to have working. 243 | 244 | ## Testing 245 | 246 | ``` bash 247 | $ phpunit 248 | ``` 249 | 250 | 251 | ## Contributing 252 | 253 | Please see [CONTRIBUTING](https://github.com/thephpleague/oauth1-client/blob/master/CONTRIBUTING.md) for details. 254 | 255 | 256 | ## Credits 257 | 258 | - [Ben Corlett](https://github.com/bencorlett) 259 | - [Steven Maguire](https://github.com/stevenmaguire) 260 | - [All Contributors](https://github.com/thephpleague/oauth1-client/contributors) 261 | 262 | 263 | ## License 264 | 265 | The MIT License (MIT). Please see [License File](https://github.com/thephpleague/oauth1-client/blob/master/LICENSE) for more information. 266 | -------------------------------------------------------------------------------- /src/Server/Server.php: -------------------------------------------------------------------------------- 1 | createClientCredentials($clientCredentials); 68 | } elseif ( ! $clientCredentials instanceof ClientCredentialsInterface) { 69 | throw new \InvalidArgumentException('Client credentials must be an array or valid object.'); 70 | } 71 | 72 | $this->clientCredentials = $clientCredentials; 73 | 74 | if ( ! $signature && $clientCredentials instanceof RsaClientCredentials) { 75 | $signature = new RsaSha1Signature($clientCredentials); 76 | } 77 | $this->signature = $signature ?: new HmacSha1Signature($clientCredentials); 78 | } 79 | 80 | /** 81 | * Gets temporary credentials by performing a request to 82 | * the server. 83 | * 84 | * @return TemporaryCredentials 85 | * 86 | * @throws CredentialsException 87 | */ 88 | public function getTemporaryCredentials() 89 | { 90 | $uri = $this->urlTemporaryCredentials(); 91 | 92 | $client = $this->createHttpClient(); 93 | 94 | $header = $this->temporaryCredentialsProtocolHeader($uri); 95 | $authorizationHeader = ['Authorization' => $header]; 96 | $headers = $this->buildHttpClientHeaders($authorizationHeader); 97 | 98 | try { 99 | $response = $client->post($uri, [ 100 | 'headers' => $headers, 101 | ]); 102 | 103 | return $this->createTemporaryCredentials((string) $response->getBody()); 104 | } catch (BadResponseException $e) { 105 | $this->handleTemporaryCredentialsBadResponse($e); 106 | } 107 | 108 | throw new CredentialsException('Failed to get temporary credentials'); 109 | } 110 | 111 | /** 112 | * Get the authorization URL by passing in the temporary credentials 113 | * identifier or an object instance. 114 | * 115 | * @param TemporaryCredentials|string $temporaryIdentifier 116 | * @param array $options 117 | * 118 | * @return string 119 | */ 120 | public function getAuthorizationUrl($temporaryIdentifier, array $options = []) 121 | { 122 | // Somebody can pass through an instance of temporary 123 | // credentials and we'll extract the identifier from there. 124 | if ($temporaryIdentifier instanceof TemporaryCredentials) { 125 | $temporaryIdentifier = $temporaryIdentifier->getIdentifier(); 126 | } 127 | 128 | $parameters = array_merge($options, ['oauth_token' => $temporaryIdentifier]); 129 | 130 | $url = $this->urlAuthorization(); 131 | $queryString = http_build_query($parameters); 132 | 133 | return $this->buildUrl($url, $queryString); 134 | } 135 | 136 | /** 137 | * Redirect the client to the authorization URL. 138 | * 139 | * @param TemporaryCredentials|string $temporaryIdentifier 140 | * 141 | * @return void 142 | */ 143 | public function authorize($temporaryIdentifier) 144 | { 145 | $url = $this->getAuthorizationUrl($temporaryIdentifier); 146 | 147 | header('Location: ' . $url); 148 | } 149 | 150 | /** 151 | * Retrieves token credentials by passing in the temporary credentials, 152 | * the temporary credentials identifier as passed back by the server 153 | * and finally the verifier code. 154 | * 155 | * @param TemporaryCredentials $temporaryCredentials 156 | * @param string $temporaryIdentifier 157 | * @param string $verifier 158 | * 159 | * @return TokenCredentials 160 | * 161 | * @throws CredentialsException 162 | */ 163 | public function getTokenCredentials(TemporaryCredentials $temporaryCredentials, $temporaryIdentifier, $verifier) 164 | { 165 | if ($temporaryIdentifier !== $temporaryCredentials->getIdentifier()) { 166 | throw new \InvalidArgumentException( 167 | 'Temporary identifier passed back by server does not match that of stored temporary credentials. 168 | Potential man-in-the-middle.' 169 | ); 170 | } 171 | 172 | $uri = $this->urlTokenCredentials(); 173 | $bodyParameters = ['oauth_verifier' => $verifier]; 174 | 175 | $client = $this->createHttpClient(); 176 | 177 | $headers = $this->getHeaders($temporaryCredentials, 'POST', $uri, $bodyParameters); 178 | 179 | try { 180 | $response = $client->post($uri, [ 181 | 'headers' => $headers, 182 | 'form_params' => $bodyParameters, 183 | ]); 184 | 185 | return $this->createTokenCredentials((string) $response->getBody()); 186 | } catch (BadResponseException $e) { 187 | $this->handleTokenCredentialsBadResponse($e); 188 | } 189 | 190 | throw new CredentialsException('Failed to get token credentials.'); 191 | } 192 | 193 | /** 194 | * Get user details by providing valid token credentials. 195 | * 196 | * @param TokenCredentials $tokenCredentials 197 | * @param bool $force 198 | * 199 | * @return \League\OAuth1\Client\Server\User 200 | */ 201 | public function getUserDetails(TokenCredentials $tokenCredentials, $force = false) 202 | { 203 | $data = $this->fetchUserDetails($tokenCredentials, $force); 204 | 205 | return $this->userDetails($data, $tokenCredentials); 206 | } 207 | 208 | /** 209 | * Get the user's unique identifier (primary key). 210 | * 211 | * @param TokenCredentials $tokenCredentials 212 | * @param bool $force 213 | * 214 | * @return string|int 215 | */ 216 | public function getUserUid(TokenCredentials $tokenCredentials, $force = false) 217 | { 218 | $data = $this->fetchUserDetails($tokenCredentials, $force); 219 | 220 | return $this->userUid($data, $tokenCredentials); 221 | } 222 | 223 | /** 224 | * Get the user's email, if available. 225 | * 226 | * @param TokenCredentials $tokenCredentials 227 | * @param bool $force 228 | * 229 | * @return string|null 230 | */ 231 | public function getUserEmail(TokenCredentials $tokenCredentials, $force = false) 232 | { 233 | $data = $this->fetchUserDetails($tokenCredentials, $force); 234 | 235 | return $this->userEmail($data, $tokenCredentials); 236 | } 237 | 238 | /** 239 | * Get the user's screen name (username), if available. 240 | * 241 | * @param TokenCredentials $tokenCredentials 242 | * @param bool $force 243 | * 244 | * @return string 245 | */ 246 | public function getUserScreenName(TokenCredentials $tokenCredentials, $force = false) 247 | { 248 | $data = $this->fetchUserDetails($tokenCredentials, $force); 249 | 250 | return $this->userScreenName($data, $tokenCredentials); 251 | } 252 | 253 | /** 254 | * Fetch user details from the remote service. 255 | * 256 | * @param TokenCredentials $tokenCredentials 257 | * @param bool $force 258 | * 259 | * @return array HTTP client response 260 | */ 261 | protected function fetchUserDetails(TokenCredentials $tokenCredentials, $force = true) 262 | { 263 | if ( ! $this->cachedUserDetailsResponse || $force) { 264 | $url = $this->urlUserDetails(); 265 | 266 | $client = $this->createHttpClient(); 267 | 268 | $headers = $this->getHeaders($tokenCredentials, 'GET', $url); 269 | 270 | try { 271 | $response = $client->get($url, [ 272 | 'headers' => $headers, 273 | ]); 274 | } catch (BadResponseException $e) { 275 | $response = $e->getResponse(); 276 | $body = $response->getBody(); 277 | $statusCode = $response->getStatusCode(); 278 | 279 | throw new \Exception( 280 | "Received error [$body] with status code [$statusCode] when retrieving token credentials." 281 | ); 282 | } 283 | switch ($this->responseType) { 284 | case 'json': 285 | $this->cachedUserDetailsResponse = json_decode((string) $response->getBody(), true); 286 | 287 | break; 288 | 289 | case 'xml': 290 | $this->cachedUserDetailsResponse = simplexml_load_string((string) $response->getBody()); 291 | 292 | break; 293 | 294 | case 'string': 295 | parse_str((string) $response->getBody(), $this->cachedUserDetailsResponse); 296 | 297 | break; 298 | 299 | default: 300 | throw new \InvalidArgumentException("Invalid response type [{$this->responseType}]."); 301 | } 302 | } 303 | 304 | return $this->cachedUserDetailsResponse; 305 | } 306 | 307 | /** 308 | * Get the client credentials associated with the server. 309 | * 310 | * @return ClientCredentialsInterface 311 | */ 312 | public function getClientCredentials() 313 | { 314 | return $this->clientCredentials; 315 | } 316 | 317 | /** 318 | * Get the signature associated with the server. 319 | * 320 | * @return SignatureInterface 321 | */ 322 | public function getSignature() 323 | { 324 | return $this->signature; 325 | } 326 | 327 | /** 328 | * Creates a Guzzle HTTP client for the given URL. 329 | * 330 | * @return GuzzleHttpClient 331 | */ 332 | public function createHttpClient() 333 | { 334 | return new GuzzleHttpClient(); 335 | } 336 | 337 | /** 338 | * Set the user agent value. 339 | * 340 | * @param string $userAgent 341 | * 342 | * @return Server 343 | */ 344 | public function setUserAgent($userAgent = null) 345 | { 346 | $this->userAgent = $userAgent; 347 | 348 | return $this; 349 | } 350 | 351 | /** 352 | * Get all headers required to created an authenticated request. 353 | * 354 | * @param CredentialsInterface $credentials 355 | * @param string $method 356 | * @param string $url 357 | * @param array $bodyParameters 358 | * 359 | * @return array 360 | */ 361 | public function getHeaders(CredentialsInterface $credentials, $method, $url, array $bodyParameters = []) 362 | { 363 | $header = $this->protocolHeader(strtoupper($method), $url, $credentials, $bodyParameters); 364 | $authorizationHeader = ['Authorization' => $header]; 365 | $headers = $this->buildHttpClientHeaders($authorizationHeader); 366 | 367 | return $headers; 368 | } 369 | 370 | /** 371 | * Get Guzzle HTTP client default headers. 372 | * 373 | * @return array 374 | */ 375 | protected function getHttpClientDefaultHeaders() 376 | { 377 | $defaultHeaders = []; 378 | if ( ! empty($this->userAgent)) { 379 | $defaultHeaders['User-Agent'] = $this->userAgent; 380 | } 381 | 382 | return $defaultHeaders; 383 | } 384 | 385 | /** 386 | * Build Guzzle HTTP client headers. 387 | * 388 | * @param array $headers 389 | * 390 | * @return array 391 | */ 392 | protected function buildHttpClientHeaders($headers = []) 393 | { 394 | $defaultHeaders = $this->getHttpClientDefaultHeaders(); 395 | 396 | return array_merge($headers, $defaultHeaders); 397 | } 398 | 399 | /** 400 | * Creates a client credentials instance from an array of credentials. 401 | * 402 | * @param array $clientCredentials 403 | * 404 | * @return ClientCredentials 405 | */ 406 | protected function createClientCredentials(array $clientCredentials) 407 | { 408 | $keys = ['identifier', 'secret']; 409 | 410 | foreach ($keys as $key) { 411 | if ( ! isset($clientCredentials[$key])) { 412 | throw new \InvalidArgumentException("Missing client credentials key [$key] from options."); 413 | } 414 | } 415 | 416 | if (isset($clientCredentials['rsa_private_key']) && isset($clientCredentials['rsa_public_key'])) { 417 | $_clientCredentials = new RsaClientCredentials(); 418 | $_clientCredentials->setRsaPrivateKey($clientCredentials['rsa_private_key']); 419 | $_clientCredentials->setRsaPublicKey($clientCredentials['rsa_public_key']); 420 | } else { 421 | $_clientCredentials = new ClientCredentials(); 422 | } 423 | 424 | $_clientCredentials->setIdentifier($clientCredentials['identifier']); 425 | $_clientCredentials->setSecret($clientCredentials['secret']); 426 | 427 | if (isset($clientCredentials['callback_uri'])) { 428 | $_clientCredentials->setCallbackUri($clientCredentials['callback_uri']); 429 | } 430 | 431 | return $_clientCredentials; 432 | } 433 | 434 | /** 435 | * Handle a bad response coming back when getting temporary credentials. 436 | * 437 | * @param BadResponseException $e 438 | * 439 | * @return void 440 | * 441 | * @throws CredentialsException 442 | */ 443 | protected function handleTemporaryCredentialsBadResponse(BadResponseException $e) 444 | { 445 | $response = $e->getResponse(); 446 | $body = $response->getBody(); 447 | $statusCode = $response->getStatusCode(); 448 | 449 | throw new CredentialsException( 450 | "Received HTTP status code [$statusCode] with message \"$body\" when getting temporary credentials." 451 | ); 452 | } 453 | 454 | /** 455 | * Creates temporary credentials from the body response. 456 | * 457 | * @param string $body 458 | * 459 | * @return TemporaryCredentials 460 | */ 461 | protected function createTemporaryCredentials($body) 462 | { 463 | parse_str($body, $data); 464 | 465 | if ( ! $data || ! is_array($data)) { 466 | throw new CredentialsException('Unable to parse temporary credentials response.'); 467 | } 468 | 469 | if ( ! isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] != 'true') { 470 | throw new CredentialsException('Error in retrieving temporary credentials.'); 471 | } 472 | 473 | $temporaryCredentials = new TemporaryCredentials(); 474 | $temporaryCredentials->setIdentifier($data['oauth_token']); 475 | $temporaryCredentials->setSecret($data['oauth_token_secret']); 476 | 477 | return $temporaryCredentials; 478 | } 479 | 480 | /** 481 | * Handle a bad response coming back when getting token credentials. 482 | * 483 | * @param BadResponseException $e 484 | * 485 | * @return void 486 | * 487 | * @throws CredentialsException 488 | */ 489 | protected function handleTokenCredentialsBadResponse(BadResponseException $e) 490 | { 491 | $response = $e->getResponse(); 492 | $body = $response->getBody(); 493 | $statusCode = $response->getStatusCode(); 494 | 495 | throw new CredentialsException( 496 | "Received HTTP status code [$statusCode] with message \"$body\" when getting token credentials." 497 | ); 498 | } 499 | 500 | /** 501 | * Creates token credentials from the body response. 502 | * 503 | * @param string $body 504 | * 505 | * @return TokenCredentials 506 | */ 507 | protected function createTokenCredentials($body) 508 | { 509 | parse_str($body, $data); 510 | 511 | if ( ! $data || ! is_array($data)) { 512 | throw new CredentialsException('Unable to parse token credentials response.'); 513 | } 514 | 515 | if (isset($data['error'])) { 516 | throw new CredentialsException("Error [{$data['error']}] in retrieving token credentials."); 517 | } 518 | 519 | $tokenCredentials = new TokenCredentials(); 520 | $tokenCredentials->setIdentifier($data['oauth_token']); 521 | $tokenCredentials->setSecret($data['oauth_token_secret']); 522 | 523 | return $tokenCredentials; 524 | } 525 | 526 | /** 527 | * Get the base protocol parameters for an OAuth request. 528 | * Each request builds on these parameters. 529 | * 530 | * @return array 531 | * 532 | * @see OAuth 1.0 RFC 5849 Section 3.1 533 | */ 534 | protected function baseProtocolParameters() 535 | { 536 | $dateTime = new \DateTime(); 537 | 538 | return [ 539 | 'oauth_consumer_key' => $this->clientCredentials->getIdentifier(), 540 | 'oauth_nonce' => $this->nonce(), 541 | 'oauth_signature_method' => $this->signature->method(), 542 | 'oauth_timestamp' => $dateTime->format('U'), 543 | 'oauth_version' => '1.0', 544 | ]; 545 | } 546 | 547 | /** 548 | * Any additional required protocol parameters for an 549 | * OAuth request. 550 | * 551 | * @return array 552 | */ 553 | protected function additionalProtocolParameters() 554 | { 555 | return []; 556 | } 557 | 558 | /** 559 | * Generate the OAuth protocol header for a temporary credentials 560 | * request, based on the URI. 561 | * 562 | * @param string $uri 563 | * 564 | * @return string 565 | */ 566 | protected function temporaryCredentialsProtocolHeader($uri) 567 | { 568 | $parameters = array_merge($this->baseProtocolParameters(), [ 569 | 'oauth_callback' => $this->clientCredentials->getCallbackUri(), 570 | ]); 571 | 572 | $parameters['oauth_signature'] = $this->signature->sign($uri, $parameters, 'POST'); 573 | 574 | return $this->normalizeProtocolParameters($parameters); 575 | } 576 | 577 | /** 578 | * Generate the OAuth protocol header for requests other than temporary 579 | * credentials, based on the URI, method, given credentials & body query 580 | * string. 581 | * 582 | * @param string $method 583 | * @param string $uri 584 | * @param CredentialsInterface $credentials 585 | * @param array $bodyParameters 586 | * 587 | * @return string 588 | */ 589 | protected function protocolHeader($method, $uri, CredentialsInterface $credentials, array $bodyParameters = []) 590 | { 591 | $parameters = array_merge( 592 | $this->baseProtocolParameters(), 593 | $this->additionalProtocolParameters(), 594 | [ 595 | 'oauth_token' => $credentials->getIdentifier(), 596 | ] 597 | ); 598 | 599 | $this->signature->setCredentials($credentials); 600 | 601 | $parameters['oauth_signature'] = $this->signature->sign( 602 | $uri, 603 | array_merge($parameters, $bodyParameters), 604 | $method 605 | ); 606 | 607 | return $this->normalizeProtocolParameters($parameters); 608 | } 609 | 610 | /** 611 | * Takes an array of protocol parameters and normalizes them 612 | * to be used as a HTTP header. 613 | * 614 | * @param array $parameters 615 | * 616 | * @return string 617 | */ 618 | protected function normalizeProtocolParameters(array $parameters) 619 | { 620 | array_walk($parameters, function (&$value, $key) { 621 | $value = rawurlencode($key) . '="' . rawurlencode($value) . '"'; 622 | }); 623 | 624 | return 'OAuth ' . implode(', ', $parameters); 625 | } 626 | 627 | /** 628 | * Generate a random string. 629 | * 630 | * @param int $length 631 | * 632 | * @return string 633 | * 634 | * @see OAuth 1.0 RFC 5849 Section 3.3 635 | */ 636 | protected function nonce($length = 32) 637 | { 638 | $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 639 | 640 | return substr(str_shuffle(str_repeat($pool, 5)), 0, $length); 641 | } 642 | 643 | /** 644 | * Build a url by combining hostname and query string after checking for 645 | * exisiting '?' character in host. 646 | * 647 | * @param string $host 648 | * @param string $queryString 649 | * 650 | * @return string 651 | */ 652 | protected function buildUrl($host, $queryString) 653 | { 654 | return $host . (strpos($host, '?') !== false ? '&' : '?') . $queryString; 655 | } 656 | 657 | /** 658 | * Get the URL for retrieving temporary credentials. 659 | * 660 | * @return string 661 | */ 662 | abstract public function urlTemporaryCredentials(); 663 | 664 | /** 665 | * Get the URL for redirecting the resource owner to authorize the client. 666 | * 667 | * @return string 668 | */ 669 | abstract public function urlAuthorization(); 670 | 671 | /** 672 | * Get the URL retrieving token credentials. 673 | * 674 | * @return string 675 | */ 676 | abstract public function urlTokenCredentials(); 677 | 678 | /** 679 | * Get the URL for retrieving user details. 680 | * 681 | * @return string 682 | */ 683 | abstract public function urlUserDetails(); 684 | 685 | /** 686 | * Take the decoded data from the user details URL and convert 687 | * it to a User object. 688 | * 689 | * @param mixed $data 690 | * @param TokenCredentials $tokenCredentials 691 | * 692 | * @return User 693 | */ 694 | abstract public function userDetails($data, TokenCredentials $tokenCredentials); 695 | 696 | /** 697 | * Take the decoded data from the user details URL and extract 698 | * the user's UID. 699 | * 700 | * @param mixed $data 701 | * @param TokenCredentials $tokenCredentials 702 | * 703 | * @return string|int 704 | */ 705 | abstract public function userUid($data, TokenCredentials $tokenCredentials); 706 | 707 | /** 708 | * Take the decoded data from the user details URL and extract 709 | * the user's email. 710 | * 711 | * @param mixed $data 712 | * @param TokenCredentials $tokenCredentials 713 | * 714 | * @return string|null 715 | */ 716 | abstract public function userEmail($data, TokenCredentials $tokenCredentials); 717 | 718 | /** 719 | * Take the decoded data from the user details URL and extract 720 | * the user's screen name. 721 | * 722 | * @param mixed $data 723 | * @param TokenCredentials $tokenCredentials 724 | * 725 | * @return string|null 726 | */ 727 | abstract public function userScreenName($data, TokenCredentials $tokenCredentials); 728 | } 729 | --------------------------------------------------------------------------------