Callback.php (6117B)
1 <?php 2 3 /** 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU Lesser General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU Lesser General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 namespace fkooman\OAuth\Client; 19 20 use fkooman\OAuth\Client\Exception\CallbackException; 21 22 // FIXME: replace AuthorizeException with CallbackException? 23 use fkooman\OAuth\Client\Exception\AuthorizeException; 24 use Guzzle\Http\Client; 25 26 class Callback 27 { 28 private $clientConfigId; 29 private $clientConfig; 30 private $tokenStorage; 31 private $httpClient; 32 33 public function __construct( 34 $clientConfigId, 35 ClientConfigInterface $clientConfig, 36 StorageInterface $tokenStorage, 37 Client $httpClient 38 ) { 39 $this->setClientConfigId($clientConfigId); 40 $this->setClientConfig($clientConfig); 41 $this->setTokenStorage($tokenStorage); 42 $this->setHttpClient($httpClient); 43 } 44 45 public function setClientConfigId($clientConfigId) 46 { 47 if (!is_string($clientConfigId) || 0 >= strlen($clientConfigId)) { 48 throw new CallbackException("clientConfigId should be a non-empty string"); 49 } 50 $this->clientConfigId = $clientConfigId; 51 } 52 53 public function getClientConfigId() 54 { 55 return $this->clientConfigId; 56 } 57 58 public function setClientConfig(ClientConfigInterface $clientConfig) 59 { 60 $this->clientConfig = $clientConfig; 61 } 62 63 public function getClientConfig() 64 { 65 return $this->clientConfig; 66 } 67 68 public function setTokenStorage(StorageInterface $tokenStorage) 69 { 70 $this->tokenStorage = $tokenStorage; 71 } 72 73 public function getTokenStorage() 74 { 75 return $this->tokenStorage; 76 } 77 78 public function setHttpClient(Client $httpClient) 79 { 80 $this->httpClient = $httpClient; 81 } 82 83 public function getHttpClient() 84 { 85 return $this->httpClient; 86 } 87 88 public function handleCallback(array $query) 89 { 90 $queryState = isset($query['state']) ? $query['state'] : null; 91 $queryCode = isset($query['code']) ? $query['code'] : null; 92 $queryError = isset($query['error']) ? $query['error'] : null; 93 $queryErrorDescription = isset($query['error_description']) ? $query['error_description'] : null; 94 95 if (null === $queryState) { 96 throw new CallbackException("state parameter missing"); 97 } 98 $state = $this->tokenStorage->getState($this->clientConfigId, $queryState); 99 if (false === $state) { 100 throw new CallbackException("state not found"); 101 } 102 103 // avoid race condition for state by really needing a confirmation 104 // that it was deleted 105 if (false === $this->tokenStorage->deleteState($state)) { 106 throw new CallbackException("state already used"); 107 } 108 109 if (null === $queryCode && null === $queryError) { 110 throw new CallbackException("both code and error parameter missing"); 111 } 112 113 if (null !== $queryError) { 114 // FIXME: this should probably be CallbackException? 115 throw new AuthorizeException($queryError, $queryErrorDescription); 116 } 117 118 if (null !== $queryCode) { 119 $t = new TokenRequest($this->httpClient, $this->clientConfig); 120 $tokenResponse = $t->withAuthorizationCode($queryCode); 121 if (false === $tokenResponse) { 122 throw new CallbackException("unable to fetch access token with authorization code"); 123 } 124 125 if (null === $tokenResponse->getScope()) { 126 // no scope in response, we assume we got the initially requested scope 127 $scope = $state->getScope(); 128 } else { 129 // the scope we got should be a superset of what we requested 130 $scope = $tokenResponse->getScope(); 131 if (!$scope->hasScope($state->getScope())) { 132 // we didn't get the scope we requested, stop for now 133 // FIXME: we need to implement a way to request certain 134 // scope as being optional, while others need to be 135 // required 136 throw new CallbackException("requested scope not obtained"); 137 } 138 } 139 140 // store the access token 141 $accessToken = new AccessToken( 142 array( 143 "client_config_id" => $this->clientConfigId, 144 "user_id" => $state->getUserId(), 145 "scope" => $scope, 146 "access_token" => $tokenResponse->getAccessToken(), 147 "token_type" => $tokenResponse->getTokenType(), 148 "issue_time" => time(), 149 "expires_in" => $tokenResponse->getExpiresIn(), 150 ) 151 ); 152 $this->tokenStorage->storeAccessToken($accessToken); 153 154 // if we also got a refresh token in the response, store that as 155 // well 156 if (null !== $tokenResponse->getRefreshToken()) { 157 $refreshToken = new RefreshToken( 158 array( 159 "client_config_id" => $this->clientConfigId, 160 "user_id" => $state->getUserId(), 161 "scope" => $scope, 162 "refresh_token" => $tokenResponse->getRefreshToken(), 163 "issue_time" => time(), 164 ) 165 ); 166 $this->tokenStorage->storeRefreshToken($refreshToken); 167 } 168 169 return $accessToken; 170 } 171 } 172 }