1<?php 2 3namespace OAuth\OAuth1\Service; 4 5use OAuth\Common\Consumer\CredentialsInterface; 6use OAuth\Common\Storage\TokenStorageInterface; 7use OAuth\Common\Http\Exception\TokenResponseException; 8use OAuth\Common\Http\Client\ClientInterface; 9use OAuth\Common\Http\Uri\UriInterface; 10use OAuth\OAuth1\Signature\SignatureInterface; 11use OAuth\OAuth1\Token\TokenInterface; 12use OAuth\OAuth1\Token\StdOAuth1Token; 13use OAuth\Common\Service\AbstractService as BaseAbstractService; 14 15abstract class AbstractService extends BaseAbstractService implements ServiceInterface 16{ 17 /** @const OAUTH_VERSION */ 18 const OAUTH_VERSION = 1; 19 20 /** @var SignatureInterface */ 21 protected $signature; 22 23 /** @var UriInterface|null */ 24 protected $baseApiUri; 25 26 /** 27 * {@inheritDoc} 28 */ 29 public function __construct( 30 CredentialsInterface $credentials, 31 ClientInterface $httpClient, 32 TokenStorageInterface $storage, 33 SignatureInterface $signature, 34 UriInterface $baseApiUri = null 35 ) { 36 parent::__construct($credentials, $httpClient, $storage); 37 38 $this->signature = $signature; 39 $this->baseApiUri = $baseApiUri; 40 41 $this->signature->setHashingAlgorithm($this->getSignatureMethod()); 42 } 43 44 /** 45 * {@inheritDoc} 46 */ 47 public function requestRequestToken() 48 { 49 $authorizationHeader = array('Authorization' => $this->buildAuthorizationHeaderForTokenRequest()); 50 $headers = array_merge($authorizationHeader, $this->getExtraOAuthHeaders()); 51 52 $responseBody = $this->httpClient->retrieveResponse($this->getRequestTokenEndpoint(), array(), $headers); 53 54 $token = $this->parseRequestTokenResponse($responseBody); 55 $this->storage->storeAccessToken($this->service(), $token); 56 57 return $token; 58 } 59 60 /** 61 * {@inheritdoc} 62 */ 63 public function getAuthorizationUri(array $additionalParameters = array()) 64 { 65 // Build the url 66 $url = clone $this->getAuthorizationEndpoint(); 67 foreach ($additionalParameters as $key => $val) { 68 $url->addToQuery($key, $val); 69 } 70 71 return $url; 72 } 73 74 /** 75 * {@inheritDoc} 76 */ 77 public function requestAccessToken($token, $verifier, $tokenSecret = null) 78 { 79 if (is_null($tokenSecret)) { 80 $storedRequestToken = $this->storage->retrieveAccessToken($this->service()); 81 $tokenSecret = $storedRequestToken->getRequestTokenSecret(); 82 } 83 $this->signature->setTokenSecret($tokenSecret); 84 85 $bodyParams = array( 86 'oauth_verifier' => $verifier, 87 ); 88 89 $authorizationHeader = array( 90 'Authorization' => $this->buildAuthorizationHeaderForAPIRequest( 91 'POST', 92 $this->getAccessTokenEndpoint(), 93 $this->storage->retrieveAccessToken($this->service()), 94 $bodyParams 95 ) 96 ); 97 98 $headers = array_merge($authorizationHeader, $this->getExtraOAuthHeaders()); 99 100 $responseBody = $this->httpClient->retrieveResponse($this->getAccessTokenEndpoint(), $bodyParams, $headers); 101 102 $token = $this->parseAccessTokenResponse($responseBody); 103 $this->storage->storeAccessToken($this->service(), $token); 104 105 return $token; 106 } 107 108 /** 109 * Refreshes an OAuth1 access token 110 * @param TokenInterface $token 111 * @return TokenInterface $token 112 */ 113 public function refreshAccessToken(TokenInterface $token) 114 { 115 } 116 117 /** 118 * Sends an authenticated API request to the path provided. 119 * If the path provided is not an absolute URI, the base API Uri (must be passed into constructor) will be used. 120 * 121 * @param string|UriInterface $path 122 * @param string $method HTTP method 123 * @param array $body Request body if applicable (key/value pairs) 124 * @param array $extraHeaders Extra headers if applicable. 125 * These will override service-specific any defaults. 126 * 127 * @return string 128 */ 129 public function request($path, $method = 'GET', $body = null, array $extraHeaders = array()) 130 { 131 $uri = $this->determineRequestUriFromPath($path, $this->baseApiUri); 132 133 /** @var $token StdOAuth1Token */ 134 $token = $this->storage->retrieveAccessToken($this->service()); 135 $extraHeaders = array_merge($this->getExtraApiHeaders(), $extraHeaders); 136 $authorizationHeader = array( 137 'Authorization' => $this->buildAuthorizationHeaderForAPIRequest($method, $uri, $token, $body) 138 ); 139 $headers = array_merge($authorizationHeader, $extraHeaders); 140 141 return $this->httpClient->retrieveResponse($uri, $body, $headers, $method); 142 } 143 144 /** 145 * Return any additional headers always needed for this service implementation's OAuth calls. 146 * 147 * @return array 148 */ 149 protected function getExtraOAuthHeaders() 150 { 151 return array(); 152 } 153 154 /** 155 * Return any additional headers always needed for this service implementation's API calls. 156 * 157 * @return array 158 */ 159 protected function getExtraApiHeaders() 160 { 161 return array(); 162 } 163 164 /** 165 * Builds the authorization header for getting an access or request token. 166 * 167 * @param array $extraParameters 168 * 169 * @return string 170 */ 171 protected function buildAuthorizationHeaderForTokenRequest(array $extraParameters = array()) 172 { 173 $parameters = $this->getBasicAuthorizationHeaderInfo(); 174 $parameters = array_merge($parameters, $extraParameters); 175 $parameters['oauth_signature'] = $this->signature->getSignature( 176 $this->getRequestTokenEndpoint(), 177 $parameters, 178 'POST' 179 ); 180 181 $authorizationHeader = 'OAuth '; 182 $delimiter = ''; 183 foreach ($parameters as $key => $value) { 184 $authorizationHeader .= $delimiter . rawurlencode($key) . '="' . rawurlencode($value) . '"'; 185 186 $delimiter = ', '; 187 } 188 189 return $authorizationHeader; 190 } 191 192 /** 193 * Builds the authorization header for an authenticated API request 194 * 195 * @param string $method 196 * @param UriInterface $uri The uri the request is headed 197 * @param TokenInterface $token 198 * @param array $bodyParams Request body if applicable (key/value pairs) 199 * 200 * @return string 201 */ 202 protected function buildAuthorizationHeaderForAPIRequest( 203 $method, 204 UriInterface $uri, 205 TokenInterface $token, 206 $bodyParams = null 207 ) { 208 $this->signature->setTokenSecret($token->getAccessTokenSecret()); 209 $authParameters = $this->getBasicAuthorizationHeaderInfo(); 210 if (isset($authParameters['oauth_callback'])) { 211 unset($authParameters['oauth_callback']); 212 } 213 214 $authParameters = array_merge($authParameters, array('oauth_token' => $token->getAccessToken())); 215 216 $signatureParams = (is_array($bodyParams)) ? array_merge($authParameters, $bodyParams) : $authParameters; 217 $authParameters['oauth_signature'] = $this->signature->getSignature($uri, $signatureParams, $method); 218 219 if (is_array($bodyParams) && isset($bodyParams['oauth_session_handle'])) { 220 $authParameters['oauth_session_handle'] = $bodyParams['oauth_session_handle']; 221 unset($bodyParams['oauth_session_handle']); 222 } 223 224 $authorizationHeader = 'OAuth '; 225 $delimiter = ''; 226 227 foreach ($authParameters as $key => $value) { 228 $authorizationHeader .= $delimiter . rawurlencode($key) . '="' . rawurlencode($value) . '"'; 229 $delimiter = ', '; 230 } 231 232 return $authorizationHeader; 233 } 234 235 /** 236 * Builds the authorization header array. 237 * 238 * @return array 239 */ 240 protected function getBasicAuthorizationHeaderInfo() 241 { 242 $dateTime = new \DateTime(); 243 $headerParameters = array( 244 'oauth_callback' => $this->credentials->getCallbackUrl(), 245 'oauth_consumer_key' => $this->credentials->getConsumerId(), 246 'oauth_nonce' => $this->generateNonce(), 247 'oauth_signature_method' => $this->getSignatureMethod(), 248 'oauth_timestamp' => $dateTime->format('U'), 249 'oauth_version' => $this->getVersion(), 250 ); 251 252 return $headerParameters; 253 } 254 255 /** 256 * Pseudo random string generator used to build a unique string to sign each request 257 * 258 * @param int $length 259 * 260 * @return string 261 */ 262 protected function generateNonce($length = 32) 263 { 264 $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'; 265 266 $nonce = ''; 267 $maxRand = strlen($characters)-1; 268 for ($i = 0; $i < $length; $i++) { 269 $nonce.= $characters[rand(0, $maxRand)]; 270 } 271 272 return $nonce; 273 } 274 275 /** 276 * @return string 277 */ 278 protected function getSignatureMethod() 279 { 280 return 'HMAC-SHA1'; 281 } 282 283 /** 284 * This returns the version used in the authorization header of the requests 285 * 286 * @return string 287 */ 288 protected function getVersion() 289 { 290 return '1.0'; 291 } 292 293 /** 294 * Parses the request token response and returns a TokenInterface. 295 * This is only needed to verify the `oauth_callback_confirmed` parameter. The actual 296 * parsing logic is contained in the access token parser. 297 * 298 * @abstract 299 * 300 * @param string $responseBody 301 * 302 * @return TokenInterface 303 * 304 * @throws TokenResponseException 305 */ 306 abstract protected function parseRequestTokenResponse($responseBody); 307 308 /** 309 * Parses the access token response and returns a TokenInterface. 310 * 311 * @abstract 312 * 313 * @param string $responseBody 314 * 315 * @return TokenInterface 316 * 317 * @throws TokenResponseException 318 */ 319 abstract protected function parseAccessTokenResponse($responseBody); 320} 321