You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

353 lines
9.6 KiB

<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient;
use yii\base\Exception;
use Yii;
/**
* OAuth1 serves as a client for the OAuth 1/1.0a flow.
*
* In oder to acquire access token perform following sequence:
*
* ~~~
* use yii\authclient\OAuth1;
*
* $oauthClient = new OAuth1();
* $requestToken = $oauthClient->fetchRequestToken(); // Get request token
* $url = $oauthClient->buildAuthUrl($requestToken); // Get authorization URL
* Yii::$app->getResponse()->redirect($url); // Redirect to authorization URL
* // After user returns at our site:
* $accessToken = $oauthClient->fetchAccessToken($requestToken); // Upgrade to access token
* ~~~
*
* @see http://oauth.net/
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class OAuth1 extends BaseOAuth
{
/**
* @var string protocol version.
*/
public $version = '1.0';
/**
* @var string OAuth consumer key.
*/
public $consumerKey = '';
/**
* @var string OAuth consumer secret.
*/
public $consumerSecret = '';
/**
* @var string OAuth request token URL.
*/
public $requestTokenUrl = '';
/**
* @var string request token HTTP method.
*/
public $requestTokenMethod = 'GET';
/**
* @var string OAuth access token URL.
*/
public $accessTokenUrl = '';
/**
* @var string access token HTTP method.
*/
public $accessTokenMethod = 'GET';
/**
* Fetches the OAuth request token.
* @param array $params additional request params.
* @return OAuthToken request token.
*/
public function fetchRequestToken(array $params = [])
{
$this->removeState('token');
$defaultParams = [
'oauth_consumer_key' => $this->consumerKey,
'oauth_callback' => $this->getReturnUrl(),
//'xoauth_displayname' => Yii::$app->name,
];
if (!empty($this->scope)) {
$defaultParams['scope'] = $this->scope;
}
$response = $this->sendSignedRequest($this->requestTokenMethod, $this->requestTokenUrl, array_merge($defaultParams, $params));
$token = $this->createToken([
'params' => $response
]);
$this->setState('requestToken', $token);
return $token;
}
/**
* Composes user authorization URL.
* @param OAuthToken $requestToken OAuth request token.
* @param array $params additional request params.
* @return string authorize URL
* @throws Exception on failure.
*/
public function buildAuthUrl(OAuthToken $requestToken = null, array $params = [])
{
if (!is_object($requestToken)) {
$requestToken = $this->getState('requestToken');
if (!is_object($requestToken)) {
throw new Exception('Request token is required to build authorize URL!');
}
}
$params['oauth_token'] = $requestToken->getToken();
return $this->composeUrl($this->authUrl, $params);
}
/**
* Fetches OAuth access token.
* @param OAuthToken $requestToken OAuth request token.
* @param string $oauthVerifier OAuth verifier.
* @param array $params additional request params.
* @return OAuthToken OAuth access token.
* @throws Exception on failure.
*/
public function fetchAccessToken(OAuthToken $requestToken = null, $oauthVerifier = null, array $params = [])
{
if (!is_object($requestToken)) {
$requestToken = $this->getState('requestToken');
if (!is_object($requestToken)) {
throw new Exception('Request token is required to fetch access token!');
}
}
$this->removeState('requestToken');
$defaultParams = [
'oauth_consumer_key' => $this->consumerKey,
'oauth_token' => $requestToken->getToken()
];
if ($oauthVerifier === null) {
if (isset($_REQUEST['oauth_verifier'])) {
$oauthVerifier = $_REQUEST['oauth_verifier'];
}
}
if (!empty($oauthVerifier)) {
$defaultParams['oauth_verifier'] = $oauthVerifier;
}
$response = $this->sendSignedRequest($this->accessTokenMethod, $this->accessTokenUrl, array_merge($defaultParams, $params));
$token = $this->createToken([
'params' => $response
]);
$this->setAccessToken($token);
return $token;
}
/**
* Sends HTTP request, signed by {@link signatureMethod}.
* @param string $method request type.
* @param string $url request URL.
* @param array $params request params.
* @return array response.
*/
protected function sendSignedRequest($method, $url, array $params = [])
{
$params = array_merge($params, $this->generateCommonRequestParams());
$params = $this->signRequest($method, $url, $params);
return $this->sendRequest($method, $url, $params);
}
/**
* Composes HTTP request CUrl options, which will be merged with the default ones.
* @param string $method request type.
* @param string $url request URL.
* @param array $params request params.
* @return array CUrl options.
* @throws Exception on failure.
*/
protected function composeRequestCurlOptions($method, $url, array $params)
{
$curlOptions = [];
switch ($method) {
case 'GET': {
$curlOptions[CURLOPT_URL] = $this->composeUrl($url, $params);
break;
}
case 'POST': {
$curlOptions[CURLOPT_POST] = true;
if (!empty($params)){
$curlOptions[CURLOPT_POSTFIELDS] = $params;
}
$authorizationHeader = $this->composeAuthorizationHeader($params);
if (!empty($authorizationHeader)/* && $this->curlAuthHeader*/) {
$curlOptions[CURLOPT_HTTPHEADER] = ['Content-Type: application/atom+xml', $authorizationHeader];
}
break;
}
case 'HEAD':
case 'PUT':
case 'DELETE': {
$curlOptions[CURLOPT_CUSTOMREQUEST] = $method;
if (!empty($params)) {
$curlOptions[CURLOPT_URL] = $this->composeUrl($url, $params);
}
break;
}
default: {
throw new Exception("Unknown request method '{$method}'.");
}
}
return $curlOptions;
}
/**
* Performs request to the OAuth API.
* @param OAuthToken $accessToken actual access token.
* @param string $url absolute API URL.
* @param string $method request method.
* @param array $params request parameters.
* @return array API response.
* @throws Exception on failure.
*/
protected function apiInternal($accessToken, $url, $method, array $params)
{
$params['oauth_consumer_key'] = $this->consumerKey;
$params['oauth_token'] = $accessToken->getToken();
$response = $this->sendSignedRequest($method, $url, $params);
return $response;
}
/**
* Gets new auth token to replace expired one.
* @param OAuthToken $token expired auth token.
* @return OAuthToken new auth token.
*/
public function refreshAccessToken(OAuthToken $token)
{
// @todo
return null;
}
/**
* Composes default {@link returnUrl} value.
* @return string return URL.
*/
protected function defaultReturnUrl()
{
$params = $_GET;
unset($params['oauth_token']);
return Yii::$app->getUrlManager()->createAbsoluteUrl(Yii::$app->controller->getRoute(), $params);
}
/**
* Generates nonce value.
* @return string nonce value.
*/
protected function generateNonce()
{
return md5(microtime() . mt_rand());
}
/**
* Generates timestamp.
* @return integer timestamp.
*/
protected function generateTimestamp()
{
return time();
}
/**
* Generate common request params like version, timestamp etc.
* @return array common request params.
*/
protected function generateCommonRequestParams()
{
$params = [
'oauth_version' => $this->version,
'oauth_nonce' => $this->generateNonce(),
'oauth_timestamp' => $this->generateTimestamp(),
];
return $params;
}
/**
* Sign request with {@link signatureMethod}.
* @param string $method request method.
* @param string $url request URL.
* @param array $params request params.
* @return array signed request params.
*/
protected function signRequest($method, $url, array $params)
{
$signatureMethod = $this->getSignatureMethod();
$params['oauth_signature_method'] = $signatureMethod->getName();
$signatureBaseString = $this->composeSignatureBaseString($method, $url, $params);
$signatureKey = $this->composeSignatureKey();
$params['oauth_signature'] = $signatureMethod->generateSignature($signatureBaseString, $signatureKey);
return $params;
}
/**
* Creates signature base string, which will be signed by {@link signatureMethod}.
* @param string $method request method.
* @param string $url request URL.
* @param array $params request params.
* @return string base signature string.
*/
protected function composeSignatureBaseString($method, $url, array $params)
{
unset($params['oauth_signature']);
$parts = [
strtoupper($method),
$url,
http_build_query($params, '', '&', PHP_QUERY_RFC3986)
];
$parts = array_map('rawurlencode', $parts);
return implode('&', $parts);
}
/**
* Composes request signature key.
* @return string signature key.
*/
protected function composeSignatureKey()
{
$signatureKeyParts = [
$this->consumerSecret
];
$accessToken = $this->getAccessToken();
if (is_object($accessToken)) {
$signatureKeyParts[] = $accessToken->getTokenSecret();
} else {
$signatureKeyParts[] = '';
}
$signatureKeyParts = array_map('rawurlencode', $signatureKeyParts);
return implode('&', $signatureKeyParts);
}
/**
* Composes authorization header content.
* @param array $params request params.
* @param string $realm authorization realm.
* @return string authorization header content.
*/
protected function composeAuthorizationHeader(array $params, $realm = '')
{
$header = 'Authorization: OAuth';
$headerParams = [];
if (!empty($realm)) {
$headerParams[] = 'realm="' . rawurlencode($realm) . '"';
}
foreach ($params as $key => $value) {
if (substr($key, 0, 5) != 'oauth') {
continue;
}
$headerParams[] = rawurlencode($key) . '="' . rawurlencode($value) . '"';
}
if (!empty($headerParams)) {
$header .= ' ' . implode(', ', $headerParams);
}
return $header;
}
}