Browse Source

Extension "authclient" added as draft.

tags/2.0.0-beta
Paul Klimov 11 years ago
parent
commit
cdae9b6046
  1. 32
      extensions/yii/authclient/LICENSE.md
  2. 30
      extensions/yii/authclient/README.md
  3. 28
      extensions/yii/authclient/composer.json
  4. 504
      extensions/yii/authclient/oauth/BaseClient.php
  5. 353
      extensions/yii/authclient/oauth/Client1.php
  6. 184
      extensions/yii/authclient/oauth/Client2.php
  7. 186
      extensions/yii/authclient/oauth/Token.php
  8. 51
      extensions/yii/authclient/oauth/signature/BaseMethod.php
  9. 47
      extensions/yii/authclient/oauth/signature/HmacSha1.php
  10. 33
      extensions/yii/authclient/oauth/signature/PlainText.php
  11. 168
      extensions/yii/authclient/oauth/signature/RsaSha1.php
  12. 23
      extensions/yii/authclient/openid/Client.php
  13. 33
      tests/unit/extensions/authclient/TestCase.php
  14. 251
      tests/unit/extensions/authclient/oauth/BaseClientTest.php
  15. 109
      tests/unit/extensions/authclient/oauth/Client1Test.php
  16. 33
      tests/unit/extensions/authclient/oauth/Client2Test.php
  17. 133
      tests/unit/extensions/authclient/oauth/TokenTest.php
  18. 50
      tests/unit/extensions/authclient/oauth/signature/BaseMethodTest.php
  19. 20
      tests/unit/extensions/authclient/oauth/signature/HmacSha1Test.php
  20. 20
      tests/unit/extensions/authclient/oauth/signature/PlainTextTest.php
  21. 110
      tests/unit/extensions/authclient/oauth/signature/RsaSha1Test.php

32
extensions/yii/authclient/LICENSE.md

@ -0,0 +1,32 @@
The Yii framework is free software. It is released under the terms of
the following BSD License.
Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Yii Software LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

30
extensions/yii/authclient/README.md

@ -0,0 +1,30 @@
AuthClient Extension for Yii 2
==============================
This extension adds [OpenID](http://openid.net/), [OAuth](http://oauth.net/) and [OAuth2](http://oauth.net/2/) consumers for the Yii 2 framework.
Installation
------------
The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
Either run
```
php composer.phar require yiisoft/yii2-authclient "*"
```
or add
```json
"yiisoft/yii2-authclient": "*"
```
to the require section of your composer.json.
Usage & Documentation
---------------------
This extension...

28
extensions/yii/authclient/composer.json

@ -0,0 +1,28 @@
{
"name": "yiisoft/yii2-authclient",
"description": "External authentication via OAuth and OpenID for the Yii framework",
"keywords": ["yii", "OAuth", "OpenID", "auth"],
"type": "yii2-extension",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2/issues?state=open",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2"
},
"authors": [
{
"name": "Paul Klimov",
"email": "klimov.paul@gmail.com"
}
],
"require": {
"yiisoft/yii2": "*",
"ext-curl": "*"
},
"autoload": {
"psr-0": { "yii\\authclient\\": "" }
},
"target-dir": "yii/authclient"
}

504
extensions/yii/authclient/oauth/BaseClient.php

@ -0,0 +1,504 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\oauth;
use yii\base\Component;
use yii\base\Exception;
use yii\base\InvalidParamException;
use Yii;
use yii\helpers\Json;
/**
* BaseClient is a base class for the OAuth clients.
*
* @see http://oauth.net/
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
abstract class BaseClient extends Component
{
const CONTENT_TYPE_JSON = 'json'; // JSON format
const CONTENT_TYPE_URLENCODED = 'urlencoded'; // urlencoded query string, like name1=value1&name2=value2
const CONTENT_TYPE_XML = 'xml'; // XML format
const CONTENT_TYPE_AUTO = 'auto'; // attempts to determine format automatically
/**
* @var string protocol version.
*/
public $version = '1.0';
/**
* @var string URL, which user will be redirected after authentication at the OAuth provider web site.
* Note: this should be absolute URL (with http:// or https:// leading).
* By default current URL will be used.
*/
private $_returnUrl = '';
/**
* @var string API base URL.
*/
public $apiBaseUrl = '';
/**
* @var string authorize URL.
*/
public $authUrl = '';
/**
* @var string auth request scope.
*/
public $scope = '';
/**
* @var array cURL request options. Option values from this field will overwrite corresponding
* values from {@link defaultCurlOptions()}.
*/
private $_curlOptions = [];
/**
* @var Token|array access token instance or its array configuration.
*/
private $_accessToken = null;
/**
* @var signature\BaseMethod|array signature method instance or its array configuration.
*/
private $_signatureMethod = [];
/**
* @param string $returnUrl return URL
*/
public function setReturnUrl($returnUrl)
{
$this->_returnUrl = $returnUrl;
}
/**
* @return string return URL.
*/
public function getReturnUrl()
{
if (empty($this->_returnUrl)) {
$this->_returnUrl = $this->defaultReturnUrl();
}
return $this->_returnUrl;
}
/**
* @param array $curlOptions cURL options.
*/
public function setCurlOptions(array $curlOptions)
{
$this->_curlOptions = $curlOptions;
}
/**
* @return array cURL options.
*/
public function getCurlOptions()
{
return $this->_curlOptions;
}
/**
* @param array|Token $token
*/
public function setAccessToken($token)
{
if (!is_object($token)) {
$token = $this->createToken($token);
}
$this->_accessToken = $token;
$this->saveAccessToken($token);
}
/**
* @return Token auth token instance.
*/
public function getAccessToken()
{
if (!is_object($this->_accessToken)) {
$this->_accessToken = $this->restoreAccessToken();
}
return $this->_accessToken;
}
/**
* @param array|signature\BaseMethod $signatureMethod signature method instance or its array configuration.
* @throws InvalidParamException on wrong argument.
*/
public function setSignatureMethod($signatureMethod)
{
if (!is_object($signatureMethod) && !is_array($signatureMethod)) {
throw new InvalidParamException('"'.get_class($this).'::signatureMethod" should be instance of "\yii\autclient\oauth\signature\BaseMethod" or its array configuration. "' . gettype($signatureMethod) . '" has been given.');
}
$this->_signatureMethod = $signatureMethod;
}
/**
* @return signature\BaseMethod signature method instance.
*/
public function getSignatureMethod()
{
if (!is_object($this->_signatureMethod)) {
$this->_signatureMethod = $this->createSignatureMethod($this->_signatureMethod);
}
return $this->_signatureMethod;
}
/**
* Composes default {@link returnUrl} value.
* @return string return URL.
*/
protected function defaultReturnUrl()
{
return Yii::$app->getRequest()->getAbsoluteUrl();
}
/**
* Sends HTTP request.
* @param string $method request type.
* @param string $url request URL.
* @param array $params request params.
* @return array response.
* @throws Exception on failure.
*/
protected function sendRequest($method, $url, array $params = [])
{
$curlOptions = $this->mergeCurlOptions(
$this->defaultCurlOptions(),
$this->getCurlOptions(),
array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_URL => $url,
),
$this->composeRequestCurlOptions(strtoupper($method), $url, $params)
);
$curlResource = curl_init();
foreach ($curlOptions as $option => $value) {
curl_setopt($curlResource, $option, $value);
}
$response = curl_exec($curlResource);
$responseHeaders = curl_getinfo($curlResource);
// check cURL error
$errorNumber = curl_errno($curlResource);
$errorMessage = curl_error($curlResource);
curl_close($curlResource);
if ($errorNumber > 0) {
throw new Exception('Curl error requesting "' . $url . '": #' . $errorNumber . ' - ' . $errorMessage);
}
if ($responseHeaders['http_code'] != 200) {
throw new Exception('Request failed with code: ' . $responseHeaders['http_code'] . ', message: ' . $response);
}
return $this->processResponse($response, $this->determineContentTypeByHeaders($responseHeaders));
}
/**
* Merge CUrl options.
* If each options array has an element with the same key value, the latter
* will overwrite the former.
* @param array $options1 options to be merged to.
* @param array $options2 options to be merged from. You can specify additional
* arrays via third argument, fourth argument etc.
* @return array merged options (the original options are not changed.)
*/
protected function mergeCurlOptions($options1, $options2)
{
$args = func_get_args();
$res = array_shift($args);
while (!empty($args)) {
$next = array_shift($args);
foreach ($next as $k => $v) {
$res[$k]=$v;
}
}
return $res;
}
/**
* Returns default cURL options.
* @return array cURL options.
*/
protected function defaultCurlOptions()
{
return [
CURLOPT_USERAGENT => Yii::$app->name . ' OAuth Client',
CURLOPT_CONNECTTIMEOUT => 30,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => false,
];
}
/**
* Processes raw response converting it to actual data.
* @param string $rawResponse raw response.
* @param string $contentType response content type.
* @throws Exception on failure.
* @return array actual response.
*/
protected function processResponse($rawResponse, $contentType = self::CONTENT_TYPE_AUTO)
{
if (empty($rawResponse)) {
return [];
}
switch ($contentType) {
case self::CONTENT_TYPE_AUTO: {
$contentType = $this->determineContentTypeByRaw($rawResponse);
if ($contentType == self::CONTENT_TYPE_AUTO) {
throw new Exception('Unable to determine response content type automatically.');
}
$response = $this->processResponse($rawResponse, $contentType);
break;
}
case self::CONTENT_TYPE_JSON: {
$response = Json::decode($rawResponse, true);
if (isset($response['error'])) {
throw new Exception('Response error: ' . $response['error']);
}
break;
}
case self::CONTENT_TYPE_URLENCODED: {
$response = [];
parse_url($rawResponse, $response);
break;
}
case self::CONTENT_TYPE_XML: {
$response = $this->convertXmlToArray($rawResponse);
break;
}
default: {
throw new Exception('Unknown response type "' . $contentType . '".');
}
}
return $response;
}
/**
* Converts XML document to array.
* @param string|\SimpleXMLElement $xml xml to process.
* @return array XML array representation.
*/
protected function convertXmlToArray($xml)
{
if (!is_object($xml)) {
$xml = simplexml_load_string($xml);
}
$result = (array)$xml;
foreach ($result as $key => $value) {
if (is_object($value)) {
$result[$key] = $this->convertXmlToArray($value);
}
}
return $result;
}
/**
* Attempts to determine HTTP request content type by headers.
* @param array $headers request headers.
* @return string content type.
*/
protected function determineContentTypeByHeaders(array $headers)
{
if (isset($headers['content_type'])) {
if (stripos($headers['content_type'], 'json') !== false) {
return self::CONTENT_TYPE_JSON;
}
if (stripos($headers['content_type'], 'urlencoded') !== false) {
return self::CONTENT_TYPE_URLENCODED;
}
if (stripos($headers['content_type'], 'xml') !== false) {
return self::CONTENT_TYPE_XML;
}
}
return self::CONTENT_TYPE_AUTO;
}
/**
* Attempts to determine the content type from raw content.
* @param string $rawContent raw response content.
* @return string response type.
*/
protected function determineContentTypeByRaw($rawContent)
{
if (preg_match('/^\\{.*\\}$/is', $rawContent)) {
return self::CONTENT_TYPE_JSON;
}
if (preg_match('/^[^=|^&]+=[^=|^&]+(&[^=|^&]+=[^=|^&]+)*$/is', $rawContent)) {
return self::CONTENT_TYPE_URLENCODED;
}
if (preg_match('/^<.*>$/is', $rawContent)) {
return self::CONTENT_TYPE_XML;
}
return self::CONTENT_TYPE_AUTO;
}
/**
* Creates signature method instance from its configuration.
* @param array $signatureMethodConfig signature method configuration.
* @return signature\BaseMethod signature method instance.
*/
protected function createSignatureMethod(array $signatureMethodConfig)
{
if (!array_key_exists('class', $signatureMethodConfig)) {
$signatureMethodConfig['class'] = signature\HmacSha1::className();
}
return Yii::createObject($signatureMethodConfig);
}
/**
* Creates token from its configuration.
* @param array $tokenConfig token configuration.
* @return Token token instance.
*/
protected function createToken(array $tokenConfig = [])
{
if (!array_key_exists('class', $tokenConfig)) {
$tokenConfig['class'] = Token::className();
}
return Yii::createObject($tokenConfig);
}
/**
* Composes URL from base URL and GET params.
* @param string $url base URL.
* @param array $params GET params.
* @return string composed URL.
*/
protected function composeUrl($url, array $params = [])
{
if (strpos($url, '?') === false) {
$url .= '?';
} else {
$url .= '&';
}
$url .= http_build_query($params, '', '&', PHP_QUERY_RFC3986);
return $url;
}
/**
* Saves token as persistent state.
* @param Token $token auth token
* @return static self reference.
*/
protected function saveAccessToken(Token $token)
{
return $this->setState('token', $token);
}
/**
* Restores access token.
* @return Token auth token.
*/
protected function restoreAccessToken()
{
$token = $this->getState('token');
if (is_object($token)) {
/* @var $token Token */
if ($token->getIsExpired()) {
$token = $this->refreshAccessToken($token);
}
}
return $token;
}
/**
* Sets persistent state.
* @param string $key state key.
* @param mixed $value state value
* @return static self reference.
*/
protected function setState($key, $value)
{
$session = Yii::$app->getSession();
$key = $this->getStateKeyPrefix() . $key;
$session->set($key, $value);
return $this;
}
/**
* Returns persistent state value.
* @param string $key state key.
* @return mixed state value.
*/
protected function getState($key)
{
$session = Yii::$app->getSession();
$key = $this->getStateKeyPrefix() . $key;
$value = $session->get($key);
return $value;
}
/**
* Removes persistent state value.
* @param string $key state key.
* @return boolean success.
*/
protected function removeState($key)
{
$session = Yii::$app->getSession();
$key = $this->getStateKeyPrefix() . $key;
$session->remove($key);
return true;
}
/**
* Returns session key prefix, which is used to store internal states.
* @return string session key prefix.
*/
protected function getStateKeyPrefix()
{
return get_class($this) . '_' . sha1($this->authUrl) . '_';
}
/**
* Performs request to the OAuth API.
* @param string $apiSubUrl API sub URL, which will be append to [[apiBaseUrl]], or absolute API URL.
* @param string $method request method.
* @param array $params request parameters.
* @return array API response
* @throws Exception on failure.
*/
public function api($apiSubUrl, $method = 'GET', array $params = [])
{
if (preg_match('/^https?:\\/\\//is', $apiSubUrl)) {
$url = $apiSubUrl;
} else {
$url = $this->apiBaseUrl . '/' . $apiSubUrl;
}
$accessToken = $this->getAccessToken();
if (!is_object($accessToken) || !$accessToken->getIsValid()) {
throw new Exception('Invalid access token.');
}
return $this->apiInternal($accessToken, $url, $method, $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.
*/
abstract protected function composeRequestCurlOptions($method, $url, array $params);
/**
* Gets new auth token to replace expired one.
* @param Token $token expired auth token.
* @return Token new auth token.
*/
abstract public function refreshAccessToken(Token $token);
/**
* Performs request to the OAuth API.
* @param Token $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.
*/
abstract protected function apiInternal($accessToken, $url, $method, array $params);
}

353
extensions/yii/authclient/oauth/Client1.php

@ -0,0 +1,353 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\oauth;
use yii\base\Exception;
use Yii;
/**
* Client1 serves as a client for the OAuth 1/1.0a flow.
*
* In oder to acquire access token perform following sequence:
*
* ~~~
* use yii\authclient\oauth\Client1;
*
* $oauthClient = new Client1();
* $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 Client1 extends BaseClient
{
/**
* @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 Token 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 Token $requestToken OAuth request token.
* @param array $params additional request params.
* @return string authorize URL
* @throws Exception on failure.
*/
public function buildAuthUrl(Token $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 Token $requestToken OAuth request token.
* @param string $oauthVerifier OAuth verifier.
* @param array $params additional request params.
* @return Token OAuth access token.
* @throws Exception on failure.
*/
public function fetchAccessToken(Token $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 Token $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 Token $token expired auth token.
* @return Token new auth token.
*/
public function refreshAccessToken(Token $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;
}
}

184
extensions/yii/authclient/oauth/Client2.php

@ -0,0 +1,184 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\oauth;
use Yii;
use yii\base\Exception;
/**
* Client2 serves as a client for the OAuth 2 flow.
*
* In oder to acquire access token perform following sequence:
*
* ~~~
* use yii\authclient\oauth\Client2;
*
* $oauthClient = new Client2();
* $url = $oauthClient->buildAuthUrl(); // Build authorization URL
* Yii::$app->getResponse()->redirect($url); // Redirect to authorization URL.
* // After user returns at our site:
* $code = $_GET['code'];
* $accessToken = $oauthClient->fetchAccessToken($code); // Get access token
* ~~~
*
* @see http://oauth.net/2/
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class Client2 extends BaseClient
{
/**
* @var string protocol version.
*/
public $version = '2.0';
/**
* @var string OAuth client ID.
*/
public $clientId = '';
/**
* @var string OAuth client secret.
*/
public $clientSecret = '';
/**
* @var string token request URL endpoint.
*/
public $tokenUrl = '';
/**
* Composes user authorization URL.
* @param array $params additional auth GET params.
* @return string authorization URL.
*/
public function buildAuthUrl(array $params = [])
{
$defaultParams = [
'client_id' => $this->clientId,
'response_type' => 'code',
'redirect_uri' => $this->getReturnUrl(),
'xoauth_displayname' => Yii::$app->name,
];
if (!empty($this->scope)) {
$defaultParams['scope'] = $this->scope;
}
return $this->composeUrl($this->authUrl, array_merge($defaultParams, $params));
}
/**
* Fetches access token from authorization code.
* @param string $authCode authorization code, usually comes at $_GET['code'].
* @param array $params additional request params.
* @return Token access token.
*/
public function fetchAccessToken($authCode, array $params = [])
{
$defaultParams = [
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'code' => $authCode,
'grant_type' => 'authorization_code',
'redirect_uri' => $this->getReturnUrl(),
];
$response = $this->sendRequest('POST', $this->tokenUrl, array_merge($defaultParams, $params));
$token = $this->createToken(['params' => $response]);
$this->setAccessToken($token);
return $token;
}
/**
* 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;
$curlOptions[CURLOPT_HTTPHEADER] = ['Content-type: application/x-www-form-urlencoded'];
$curlOptions[CURLOPT_POSTFIELDS] = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
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 Token $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['access_token'] = $accessToken->getToken();
return $this->sendRequest($method, $url, $params);
}
/**
* Gets new auth token to replace expired one.
* @param Token $token expired auth token.
* @return Token new auth token.
*/
public function refreshAccessToken(Token $token)
{
$params = [
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'grant_type' => 'refresh_token'
];
$params = array_merge($token->getParams(), $params);
$response = $this->sendRequest('POST', $this->tokenUrl, $params);
return $response;
}
/**
* Composes default {@link returnUrl} value.
* @return string return URL.
*/
protected function defaultReturnUrl()
{
$params = $_GET;
unset($params['code']);
return Yii::$app->getUrlManager()->createAbsoluteUrl(Yii::$app->controller->getRoute(), $params);
}
/**
* Creates token from its configuration.
* @param array $tokenConfig token configuration.
* @return Token token instance.
*/
protected function createToken(array $tokenConfig = [])
{
$tokenConfig['tokenParamKey'] = 'access_token';
return parent::createToken($tokenConfig);
}
}

186
extensions/yii/authclient/oauth/Token.php

@ -0,0 +1,186 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\oauth;
use yii\base\Object;
/**
* Token represents OAuth token.
*
* @property array $params token parameters.
* @property string $token token value.
* @property string $tokenSecret token secret value.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class Token extends Object
{
/**
* @var string key in {@link _params} array, which stores token key.
*/
public $tokenParamKey = 'oauth_token';
/**
* @var string key in {@link _params} array, which stores token secret key.
*/
public $tokenSecretParamKey = 'oauth_token_secret';
/**
* @var string key in {@link _params} array, which stores token expiration duration.
* If not set will attempt to fetch its value automatically.
*/
private $_expireDurationParamKey;
/**
* @var array token parameters.
*/
private $_params = [];
/**
* @var integer object creation timestamp.
*/
public $createTimestamp;
public function init()
{
if ($this->createTimestamp === null) {
$this->createTimestamp = time();
}
}
/**
* @param string $expireDurationParamKey expire duration param key.
*/
public function setExpireDurationParamKey($expireDurationParamKey) {
$this->_expireDurationParamKey = $expireDurationParamKey;
}
/**
* @return string expire duration param key.
*/
public function getExpireDurationParamKey() {
if ($this->_expireDurationParamKey === null) {
$this->_expireDurationParamKey = $this->defaultExpireDurationParamKey();
}
return $this->_expireDurationParamKey;
}
/**
* @return array
*/
public function getParams() {
return $this->_params;
}
/**
* @param array $params
*/
public function setParams(array $params) {
$this->_params = $params;
}
/**
* Sets param by name.
* @param string $name param name.
* @param mixed $value param value,
*/
public function setParam($name, $value) {
$this->_params[$name] = $value;
}
/**
* Returns param by name.
* @param string $name param name.
* @return mixed param value.
*/
public function getParam($name) {
return isset($this->_params[$name]) ? $this->_params[$name] : null;
}
/**
* Sets token value.
* @param string $token token value.
* @return static self reference.
*/
public function setToken($token) {
$this->setParam($this->tokenParamKey, $token);
}
/**
* Returns token value.
* @return string token value.
*/
public function getToken() {
return $this->getParam($this->tokenParamKey);
}
/**
* Sets the token secret value.
* @param string $tokenSecret token secret.
*/
public function setTokenSecret($tokenSecret) {
$this->setParam($this->tokenSecretParamKey, $tokenSecret);
}
/**
* Returns the token secret value.
* @return string token secret value.
*/
public function getTokenSecret() {
return $this->getParam($this->tokenSecretParamKey);
}
/**
* Sets token expire duration.
* @param string $expireDuration token expiration duration.
*/
public function setExpireDuration($expireDuration) {
$this->setParam($this->getExpireDurationParamKey(), $expireDuration);
}
/**
* Returns the token expiration duration.
* @return integer token expiration duration.
*/
public function getExpireDuration() {
return $this->getParam($this->getExpireDurationParamKey());
}
/**
* Fetches default expire duration param key.
* @return string expire duration param key.
*/
protected function defaultExpireDurationParamKey() {
$expireDurationParamKey = 'expires_in';
foreach ($this->getParams() as $name => $value) {
if (strpos($name, 'expir') !== false) {
$expireDurationParamKey = $name;
break;
}
}
return $expireDurationParamKey;
}
/**
* Checks if token has expired.
* @return boolean is token expired.
*/
public function getIsExpired() {
$expirationDuration = $this->getExpireDuration();
if (empty($expirationDuration)) {
return false;
}
return (time() >= ($this->createTimestamp + $expirationDuration));
}
/**
* Checks if token is valid.
* @return boolean is token valid.
*/
public function getIsValid() {
$token = $this->getToken();
return (!empty($token) && !$this->getIsExpired());
}
}

51
extensions/yii/authclient/oauth/signature/BaseMethod.php

@ -0,0 +1,51 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\oauth\signature;
use yii\base\Object;
/**
* BaseMethod is a base class for the OAuth signature methods.
*
* @property string $name method canonical name. This property is read-only.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
abstract class BaseMethod extends Object
{
/**
* Return the canonical name of the Signature Method.
* @return string method name.
*/
abstract public function getName();
/**
* Generates OAuth request signature.
* @param string $baseString signature base string.
* @param string $key signature key.
* @return string signature string.
*/
abstract public function generateSignature($baseString, $key);
/**
* Verifies given OAuth request.
* @param string $signature signature to be verified.
* @param string $baseString signature base string.
* @param string $key signature key.
* @return boolean success.
*/
public function verify($signature, $baseString, $key)
{
$expectedSignature = $this->generateSignature($baseString, $key);
if (empty($signature) || empty($expectedSignature)) {
return false;
}
return (strcmp($expectedSignature, $signature) === 0);
}
}

47
extensions/yii/authclient/oauth/signature/HmacSha1.php

@ -0,0 +1,47 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\oauth\signature;
use yii\base\NotSupportedException;
/**
* HmacSha1 represents 'HMAC-SHA1' signature method.
*
* Note: This class require PHP "Hash" extension({@link http://php.net/manual/en/book.hash.php}).
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class HmacSha1 extends BaseMethod
{
/**
* @inheritdoc
*/
public function init()
{
if (!function_exists('hash_hmac')) {
throw new NotSupportedException('PHP "Hash" extension is required.');
}
}
/**
* @inheritdoc
*/
public function getName()
{
return 'HMAC-SHA1';
}
/**
* @inheritdoc
*/
public function generateSignature($baseString, $key)
{
return base64_encode(hash_hmac('sha1', $baseString, $key, true));
}
}

33
extensions/yii/authclient/oauth/signature/PlainText.php

@ -0,0 +1,33 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\oauth\signature;
/**
* PlainText represents 'PLAINTEXT' signature method.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class PlainText extends BaseMethod
{
/**
* @inheritdoc
*/
public function getName()
{
return 'PLAINTEXT';
}
/**
* @inheritdoc
*/
public function generateSignature($baseString, $key)
{
return $key;
}
}

168
extensions/yii/authclient/oauth/signature/RsaSha1.php

@ -0,0 +1,168 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\oauth\signature;
use yii\base\InvalidConfigException;
use yii\base\NotSupportedException;
/**
* RsaSha1 represents 'RSA-SHA1' signature method.
*
* Note: This class require PHP "OpenSSL" extension({@link http://php.net/manual/en/book.openssl.php}).
*
* @property string $privateCertificate OpenSSL private key certificate content.
* @property string $publicCertificate OpenSSL public key certificate content.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class RsaSha1 extends BaseMethod
{
/**
* @var string OpenSSL private key certificate content.
* This value can be fetched from file specified by {@link privateCertificateFile}.
*/
protected $_privateCertificate;
/**
* @var string OpenSSL public key certificate content.
* This value can be fetched from file specified by {@link publicCertificateFile}.
*/
protected $_publicCertificate;
/**
* @var string path to the file, which holds private key certificate.
*/
public $privateCertificateFile = '';
/**
* @var string path to the file, which holds public key certificate.
*/
public $publicCertificateFile = '';
/**
* @inheritdoc
*/
public function init()
{
if (!function_exists('openssl_sign')) {
throw new NotSupportedException('PHP "OpenSSL" extension is required.');
}
}
/**
* @param string $publicCertificate public key certificate content.
*/
public function setPublicCertificate($publicCertificate)
{
$this->_publicCertificate = $publicCertificate;
}
/**
* @return string public key certificate content.
*/
public function getPublicCertificate()
{
if ($this->_publicCertificate === null) {
$this->_publicCertificate = $this->initPublicCertificate();
}
return $this->_publicCertificate;
}
/**
* @param string $privateCertificate private key certificate content.
*/
public function setPrivateCertificate($privateCertificate)
{
$this->_privateCertificate = $privateCertificate;
}
/**
* @return string private key certificate content.
*/
public function getPrivateCertificate()
{
if ($this->_privateCertificate === null) {
$this->_privateCertificate = $this->initPrivateCertificate();
}
return $this->_privateCertificate;
}
/**
* @inheritdoc
*/
public function getName()
{
return 'RSA-SHA1';
}
/**
* Creates initial value for {@link publicCertificate}.
* This method will attempt to fetch the certificate value from {@link publicCertificateFile} file.
* @throws InvalidConfigException on failure.
* @return string public certificate content.
*/
protected function initPublicCertificate()
{
if (!empty($this->publicCertificateFile)) {
if (!file_exists($this->publicCertificateFile)) {
throw new InvalidConfigException("Public certificate file '{$this->publicCertificateFile}' does not exist!");
}
return file_get_contents($this->publicCertificateFile);
} else {
return '';
}
}
/**
* Creates initial value for {@link privateCertificate}.
* This method will attempt to fetch the certificate value from {@link privateCertificateFile} file.
* @throws InvalidConfigException on failure.
* @return string private certificate content.
*/
protected function initPrivateCertificate()
{
if (!empty($this->privateCertificateFile)) {
if (!file_exists($this->privateCertificateFile)) {
throw new InvalidConfigException("Private certificate file '{$this->privateCertificateFile}' does not exist!");
}
return file_get_contents($this->privateCertificateFile);
} else {
return '';
}
}
/**
* @inheritdoc
*/
public function generateSignature($baseString, $key)
{
$privateCertificateContent = $this->getPrivateCertificate();
// Pull the private key ID from the certificate
$privateKeyId = openssl_pkey_get_private($privateCertificateContent);
// Sign using the key
openssl_sign($baseString, $signature, $privateKeyId);
// Release the key resource
openssl_free_key($privateKeyId);
return base64_encode($signature);
}
/**
* @inheritdoc
*/
public function verify($signature, $baseString, $key)
{
$decodedSignature = base64_decode($signature);
// Fetch the public key cert based on the request
$publicCertificate = $this->getPublicCertificate();
// Pull the public key ID from the certificate
$publicKeyId = openssl_pkey_get_public($publicCertificate);
// Check the computed signature against the one passed in the query
$verificationResult = openssl_verify($baseString, $decodedSignature, $publicKeyId);
// Release the key resource
openssl_free_key($publicKeyId);
return ($verificationResult == 1);
}
}

23
extensions/yii/authclient/openid/Client.php

@ -0,0 +1,23 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\openid;
use yii\base\Component;
/**
* Class Client
*
* @see http://openid.net/
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class Client extends Component
{
}

33
tests/unit/extensions/authclient/TestCase.php

@ -0,0 +1,33 @@
<?php
namespace yiiunit\extensions\authclient;
use yii\helpers\FileHelper;
use Yii;
/**
* TestCase for "authclient" extension.
*/
class TestCase extends \yiiunit\TestCase
{
public static function setUpBeforeClass()
{
static::loadClassMap();
}
/**
* Adds sphinx extension files to [[Yii::$classPath]],
* avoiding the necessity of usage Composer autoloader.
*/
protected static function loadClassMap()
{
$baseNameSpace = 'yii/authclient';
$basePath = realpath(__DIR__. '/../../../../extensions/yii/authclient');
$files = FileHelper::findFiles($basePath);
foreach ($files as $file) {
$classRelativePath = str_replace($basePath, '', $file);
$classFullName = str_replace(['/', '.php'], ['\\', ''], $baseNameSpace . $classRelativePath);
Yii::$classMap[$classFullName] = $file;
}
}
}

251
tests/unit/extensions/authclient/oauth/BaseClientTest.php

@ -0,0 +1,251 @@
<?php
namespace yiiunit\extensions\authclient\oauth;
use yii\authclient\oauth\signature\PlainText;
use yii\authclient\oauth\Token;
use yiiunit\extensions\authclient\TestCase;
use yii\authclient\oauth\BaseClient;
class BaseClientTest extends TestCase
{
/**
* Creates test OAuth client instance.
* @return BaseClient oauth client.
*/
protected function createOAuthClient()
{
$oauthClient = $this->getMock(BaseClient::className(), ['setState', 'getState', 'composeRequestCurlOptions', 'refreshAccessToken', 'apiInternal']);
$oauthClient->expects($this->any())->method('setState')->will($this->returnValue($oauthClient));
$oauthClient->expects($this->any())->method('getState')->will($this->returnValue(null));
return $oauthClient;
}
/**
* Invokes the OAuth client method even if it is protected.
* @param BaseClient $oauthClient OAuth client instance.
* @param string $methodName name of the method to be invoked.
* @param array $arguments method arguments.
* @return mixed method invoke result.
*/
protected function invokeOAuthClientMethod($oauthClient, $methodName, array $arguments = [])
{
$classReflection = new \ReflectionClass(get_class($oauthClient));
$methodReflection = $classReflection->getMethod($methodName);
$methodReflection->setAccessible(true);
$result = $methodReflection->invokeArgs($oauthClient, $arguments);
$methodReflection->setAccessible(false);
return $result;
}
// Tests :
public function testSetGet()
{
$oauthClient = $this->createOAuthClient();
$returnUrl = 'http://test.return.url';
$oauthClient->setReturnUrl($returnUrl);
$this->assertEquals($returnUrl, $oauthClient->getReturnUrl(), 'Unable to setup return URL!');
$curlOptions = [
'option1' => 'value1',
'option2' => 'value2',
];
$oauthClient->setCurlOptions($curlOptions);
$this->assertEquals($curlOptions, $oauthClient->getCurlOptions(), 'Unable to setup cURL options!');
}
public function testSetupComponents()
{
$oauthClient = $this->createOAuthClient();
$oauthToken = new Token();
$oauthClient->setAccessToken($oauthToken);
$this->assertEquals($oauthToken, $oauthClient->getAccessToken(), 'Unable to setup token!');
$oauthSignatureMethod = new PlainText();
$oauthClient->setSignatureMethod($oauthSignatureMethod);
$this->assertEquals($oauthSignatureMethod, $oauthClient->getSignatureMethod(), 'Unable to setup signature method!');
}
/**
* @depends testSetupComponents
*/
public function testSetupComponentsByConfig()
{
$oauthClient = $this->createOAuthClient();
$oauthToken = [
'token' => 'test_token',
'tokenSecret' => 'test_token_secret',
];
$oauthClient->setAccessToken($oauthToken);
$this->assertEquals($oauthToken['token'], $oauthClient->getAccessToken()->getToken(), 'Unable to setup token as config!');
$oauthSignatureMethod = [
'class' => 'yii\authclient\oauth\signature\PlainText'
];
$oauthClient->setSignatureMethod($oauthSignatureMethod);
$returnedSignatureMethod = $oauthClient->getSignatureMethod();
$this->assertEquals($oauthSignatureMethod['class'], get_class($returnedSignatureMethod), 'Unable to setup signature method as config!');
}
/**
* Data provider for [[testComposeUrl()]].
* @return array test data.
*/
public function composeUrlDataProvider()
{
return [
[
'http://test.url',
[
'param1' => 'value1',
'param2' => 'value2',
],
'http://test.url?param1=value1&param2=value2',
],
[
'http://test.url?with=some',
[
'param1' => 'value1',
'param2' => 'value2',
],
'http://test.url?with=some&param1=value1&param2=value2',
],
];
}
/**
* @dataProvider composeUrlDataProvider
*
* @param string $url request URL.
* @param array $params request params
* @param string $expectedUrl expected composed URL.
*/
public function testComposeUrl($url, array $params, $expectedUrl)
{
$oauthClient = $this->createOAuthClient();
$composedUrl = $this->invokeOAuthClientMethod($oauthClient, 'composeUrl', [$url, $params]);
$this->assertEquals($expectedUrl, $composedUrl);
}
/**
* Data provider for {@link testDetermineContentTypeByHeaders}.
* @return array test data.
*/
public function determineContentTypeByHeadersDataProvider()
{
return [
[
['content_type' => 'application/json'],
'json'
],
[
['content_type' => 'application/x-www-form-urlencoded'],
'urlencoded'
],
[
['content_type' => 'application/xml'],
'xml'
],
[
['some_header' => 'some_header_value'],
'auto'
],
[
['content_type' => 'unknown'],
'auto'
],
];
}
/**
* @dataProvider determineContentTypeByHeadersDataProvider
*
* @param array $headers request headers.
* @param string $expectedResponseType expected response type.
*/
public function testDetermineContentTypeByHeaders(array $headers, $expectedResponseType)
{
$oauthClient = $this->createOAuthClient();
$responseType = $this->invokeOAuthClientMethod($oauthClient, 'determineContentTypeByHeaders', [$headers]);
$this->assertEquals($expectedResponseType, $responseType);
}
/**
* Data provider for [[testDetermineContentTypeByRaw]].
* @return array test data.
*/
public function determineContentTypeByRawDataProvider()
{
return array(
['{name: value}', 'json'],
['name=value', 'urlencoded'],
['name1=value1&name2=value2', 'urlencoded'],
['<?xml version="1.0" encoding="UTF-8"?><tag>Value</tag>', 'xml'],
['<tag>Value</tag>', 'xml'],
);
}
/**
* @dataProvider determineContentTypeByRawDataProvider
*
* @param string $rawResponse raw response content.
* @param string $expectedResponseType expected response type.
*/
public function testDetermineContentTypeByRaw($rawResponse, $expectedResponseType)
{
$oauthClient = $this->createOAuthClient();
$responseType = $this->invokeOAuthClientMethod($oauthClient, 'determineContentTypeByRaw', [$rawResponse]);
$this->assertEquals($expectedResponseType, $responseType);
}
/**
* Data provider for [[testApiUrl]].
* @return array test data.
*/
public function apiUrlDataProvider()
{
return [
[
'http://api.base.url',
'sub/url',
'http://api.base.url/sub/url',
],
[
'http://api.base.url',
'http://api.base.url/sub/url',
'http://api.base.url/sub/url',
],
[
'http://api.base.url',
'https://api.base.url/sub/url',
'https://api.base.url/sub/url',
],
];
}
/**
* @dataProvider apiUrlDataProvider
*
* @param $apiBaseUrl
* @param $apiSubUrl
* @param $expectedApiFullUrl
*/
public function testApiUrl($apiBaseUrl, $apiSubUrl, $expectedApiFullUrl)
{
$oauthClient = $this->createOAuthClient();
$oauthClient->expects($this->any())->method('apiInternal')->will($this->returnArgument(1));
$accessToken = new Token();
$accessToken->setToken('test_access_token');
$accessToken->setExpireDuration(1000);
$oauthClient->setAccessToken($accessToken);
$oauthClient->apiBaseUrl = $apiBaseUrl;
$this->assertEquals($expectedApiFullUrl, $oauthClient->api($apiSubUrl));
}
}

109
tests/unit/extensions/authclient/oauth/Client1Test.php

@ -0,0 +1,109 @@
<?php
namespace yiiunit\extensions\authclient\oauth;
use yii\authclient\oauth\Client1;
use yii\authclient\oauth\signature\PlainText;
use yii\authclient\oauth\Token;
use yiiunit\extensions\authclient\TestCase;
class Client1Test extends TestCase
{
protected function setUp()
{
$this->mockApplication([], '\yii\web\Application');
}
/**
* Invokes the OAuth client method even if it is protected.
* @param Client1 $oauthClient OAuth client instance.
* @param string $methodName name of the method to be invoked.
* @param array $arguments method arguments.
* @return mixed method invoke result.
*/
protected function invokeOAuthClientMethod($oauthClient, $methodName, array $arguments = [])
{
$classReflection = new \ReflectionClass(get_class($oauthClient));
$methodReflection = $classReflection->getMethod($methodName);
$methodReflection->setAccessible(true);
$result = $methodReflection->invokeArgs($oauthClient, $arguments);
$methodReflection->setAccessible(false);
return $result;
}
// Tests :
public function testSignRequest()
{
$oauthClient = new Client1();
$oauthSignatureMethod = new PlainText();
$oauthClient->setSignatureMethod($oauthSignatureMethod);
$signedParams = $this->invokeOAuthClientMethod($oauthClient, 'signRequest', ['GET', 'http://test.url', []]);
$this->assertNotEmpty($signedParams['oauth_signature'], 'Unable to sign request!');
}
/**
* Data provider for [[testComposeAuthorizationHeader()]].
* @return array test data.
*/
public function composeAuthorizationHeaderDataProvider()
{
return [
[
'',
[
'oauth_test_name_1' => 'oauth_test_value_1',
'oauth_test_name_2' => 'oauth_test_value_2',
],
'Authorization: OAuth oauth_test_name_1="oauth_test_value_1", oauth_test_name_2="oauth_test_value_2"'
],
[
'test_realm',
[
'oauth_test_name_1' => 'oauth_test_value_1',
'oauth_test_name_2' => 'oauth_test_value_2',
],
'Authorization: OAuth realm="test_realm", oauth_test_name_1="oauth_test_value_1", oauth_test_name_2="oauth_test_value_2"'
],
[
'',
[
'oauth_test_name_1' => 'oauth_test_value_1',
'test_name_2' => 'test_value_2',
],
'Authorization: OAuth oauth_test_name_1="oauth_test_value_1"'
],
];
}
/**
* @dataProvider composeAuthorizationHeaderDataProvider
*
* @param string $realm authorization realm.
* @param array $params request params.
* @param string $expectedAuthorizationHeader expected authorization header.
*/
public function testComposeAuthorizationHeader($realm, array $params, $expectedAuthorizationHeader)
{
$oauthClient = new Client1();
$authorizationHeader = $this->invokeOAuthClientMethod($oauthClient, 'composeAuthorizationHeader', [$params, $realm]);
$this->assertEquals($expectedAuthorizationHeader, $authorizationHeader);
}
public function testBuildAuthUrl() {
$oauthClient = new Client1();
$authUrl = 'http://test.auth.url';
$oauthClient->authUrl = $authUrl;
$requestTokenToken = 'test_request_token';
$requestToken = new Token();
$requestToken->setToken($requestTokenToken);
$builtAuthUrl = $oauthClient->buildAuthUrl($requestToken);
$this->assertContains($authUrl, $builtAuthUrl, 'No auth URL present!');
$this->assertContains($requestTokenToken, $builtAuthUrl, 'No token present!');
}
}

33
tests/unit/extensions/authclient/oauth/Client2Test.php

@ -0,0 +1,33 @@
<?php
namespace yiiunit\extensions\authclient\oauth;
use yii\authclient\oauth\Client2;
use yiiunit\extensions\authclient\TestCase;
class Client2Test extends TestCase
{
protected function setUp()
{
$this->mockApplication([], '\yii\web\Application');
}
// Tests :
public function testBuildAuthUrl()
{
$oauthClient = new Client2();
$authUrl = 'http://test.auth.url';
$oauthClient->authUrl = $authUrl;
$clientId = 'test_client_id';
$oauthClient->clientId = $clientId;
$returnUrl = 'http://test.return.url';
$oauthClient->setReturnUrl($returnUrl);
$builtAuthUrl = $oauthClient->buildAuthUrl();
$this->assertContains($authUrl, $builtAuthUrl, 'No auth URL present!');
$this->assertContains($clientId, $builtAuthUrl, 'No client id present!');
$this->assertContains(rawurlencode($returnUrl), $builtAuthUrl, 'No return URL present!');
}
}

133
tests/unit/extensions/authclient/oauth/TokenTest.php

@ -0,0 +1,133 @@
<?php
namespace yiiunit\extensions\authclient\oauth;
use yii\authclient\oauth\Token;
use yiiunit\extensions\authclient\TestCase;
class TokenTest extends TestCase
{
public function testCreate()
{
$config = [
'tokenParamKey' => 'test_token_param_key',
'tokenSecretParamKey' => 'test_token_secret_param_key',
];
$oauthToken = new Token($config);
$this->assertTrue(is_object($oauthToken), 'Unable to create access token!');
foreach ($config as $name => $value) {
$this->assertEquals($value, $oauthToken->$name, 'Unable to setup attributes by constructor!');
}
$this->assertTrue($oauthToken->createTimestamp > 0, 'Unable to fill create timestamp!');
}
public function testSetupParams()
{
$oauthToken = new Token();
$params = [
'name_1' => 'value_1',
'name_2' => 'value_2',
];
$oauthToken->setParams($params);
$this->assertEquals($params, $oauthToken->getParams(), 'Unable to setup params!');
$newParamName = 'new_param_name';
$newParamValue = 'new_param_value';
$oauthToken->setParam($newParamName, $newParamValue);
$this->assertEquals($newParamValue, $oauthToken->getParam($newParamName), 'Unable to setup param by name!');
}
/**
* @depends testSetupParams
*/
public function testSetupParamsShortcuts()
{
$oauthToken = new Token();
$token = 'test_token_value';
$oauthToken->setToken($token);
$this->assertEquals($token, $oauthToken->getToken(), 'Unable to setup token!');
$tokenSecret = 'test_token_secret';
$oauthToken->setTokenSecret($tokenSecret);
$this->assertEquals($tokenSecret, $oauthToken->getTokenSecret(), 'Unable to setup token secret!');
$tokenExpireDuration = rand(1000, 2000);
$oauthToken->setExpireDuration($tokenExpireDuration);
$this->assertEquals($tokenExpireDuration, $oauthToken->getExpireDuration(), 'Unable to setup expire duration!');
}
/**
* Data provider for {@link testAutoFetchExpireDuration}.
* @return array test data.
*/
public function autoFetchExpireDurationDataProvider()
{
return [
[
['expire_in' => 123345],
123345
],
[
['expire' => 233456],
233456
],
[
['expiry_in' => 34567],
34567
],
[
['expiry' => 45678],
45678
],
];
}
/**
* @depends testSetupParamsShortcuts
* @dataProvider autoFetchExpireDurationDataProvider
*
* @param array $params
* @param $expectedExpireDuration
*/
public function testAutoFetchExpireDuration(array $params, $expectedExpireDuration)
{
$oauthToken = new Token();
$oauthToken->setParams($params);
$this->assertEquals($expectedExpireDuration, $oauthToken->getExpireDuration());
}
/**
* @depends testSetupParamsShortcuts
*/
public function testGetIsExpired()
{
$oauthToken = new Token();
$expireDuration = 3600;
$oauthToken->setExpireDuration($expireDuration);
$this->assertFalse($oauthToken->getIsExpired(), 'Not expired token check fails!');
$oauthToken->createTimestamp = $oauthToken->createTimestamp - ($expireDuration +1);
$this->assertTrue($oauthToken->getIsExpired(), 'Expired token check fails!');
}
/**
* @depends testGetIsExpired
*/
public function testGetIsValid()
{
$oauthToken = new Token();
$expireDuration = 3600;
$oauthToken->setExpireDuration($expireDuration);
$this->assertFalse($oauthToken->getIsValid(), 'Empty token is valid!');
$oauthToken->setToken('test_token');
$this->assertTrue($oauthToken->getIsValid(), 'Filled up token is invalid!');
$oauthToken->createTimestamp = $oauthToken->createTimestamp - ($expireDuration +1);
$this->assertFalse($oauthToken->getIsValid(), 'Expired token is valid!');
}
}

50
tests/unit/extensions/authclient/oauth/signature/BaseMethodTest.php

@ -0,0 +1,50 @@
<?php
namespace yiiunit\extensions\authclient\oauth\signature;
use yiiunit\extensions\authclient\TestCase;
class BaseMethodTest extends TestCase
{
/**
* Creates test signature method instance.
* @return \yii\authclient\oauth\signature\BaseMethod
*/
protected function createTestSignatureMethod()
{
$signatureMethod = $this->getMock('\yii\authclient\oauth\signature\BaseMethod', ['getName', 'generateSignature']);
$signatureMethod->expects($this->any())->method('getName')->will($this->returnValue('testMethodName'));
$signatureMethod->expects($this->any())->method('generateSignature')->will($this->returnValue('testSignature'));
return $signatureMethod;
}
// Tests :
public function testGenerateSignature()
{
$signatureMethod = $this->createTestSignatureMethod();
$baseString = 'test_base_string';
$key = 'test_key';
$signature = $signatureMethod->generateSignature($baseString, $key);
$this->assertNotEmpty($signature, 'Unable to generate signature!');
}
/**
* @depends testGenerateSignature
*/
public function testVerify()
{
$signatureMethod = $this->createTestSignatureMethod();
$baseString = 'test_base_string';
$key = 'test_key';
$signature = 'unsigned';
$this->assertFalse($signatureMethod->verify($signature, $baseString, $key), 'Unsigned signature is valid!');
$generatedSignature = $signatureMethod->generateSignature($baseString, $key);
$this->assertTrue($signatureMethod->verify($generatedSignature, $baseString, $key), 'Generated signature is invalid!');
}
}

20
tests/unit/extensions/authclient/oauth/signature/HmacSha1Test.php

@ -0,0 +1,20 @@
<?php
namespace yiiunit\extensions\authclient\oauth\signature;
use yii\authclient\oauth\signature\HmacSha1;
use yiiunit\extensions\authclient\TestCase;
class HmacSha1Test extends TestCase
{
public function testGenerateSignature()
{
$signatureMethod = new HmacSha1();
$baseString = 'test_base_string';
$key = 'test_key';
$signature = $signatureMethod->generateSignature($baseString, $key);
$this->assertNotEmpty($signature, 'Unable to generate signature!');
}
}

20
tests/unit/extensions/authclient/oauth/signature/PlainTextTest.php

@ -0,0 +1,20 @@
<?php
namespace yiiunit\extensions\authclient\oauth\signature;
use yii\authclient\oauth\signature\PlainText;
use yiiunit\extensions\authclient\TestCase;
class PlainTextTest extends TestCase
{
public function testGenerateSignature()
{
$signatureMethod = new PlainText();
$baseString = 'test_base_string';
$key = 'test_key';
$signature = $signatureMethod->generateSignature($baseString, $key);
$this->assertNotEmpty($signature, 'Unable to generate signature!');
}
}

110
tests/unit/extensions/authclient/oauth/signature/RsaSha1Test.php

@ -0,0 +1,110 @@
<?php
namespace yiiunit\extensions\authclient\oauth\signature;
use yii\authclient\oauth\signature\RsaSha1;
use yiiunit\extensions\authclient\TestCase;
class RsaSha1Test extends TestCase
{
/**
* Returns test public certificate string.
* @return string public certificate string.
*/
protected function getTestPublicCertificate()
{
return '-----BEGIN CERTIFICATE-----
MIIDJDCCAo2gAwIBAgIJALCFAl3nj1ibMA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD
VQQGEwJOTDESMBAGA1UECAwJQW1zdGVyZGFtMRIwEAYDVQQHDAlBbXN0ZXJkYW0x
DzANBgNVBAoMBlBpbVRpbTEPMA0GA1UECwwGUGltVGltMSswKQYDVQQDDCJkZXY1
My5xdWFydHNvZnQuY29tL3BrbGltb3YvcGltdGltMSQwIgYJKoZIhvcNAQkBFhVw
a2xpbW92QHF1YXJ0c29mdC5jb20wHhcNMTIxMTA2MTQxNjUzWhcNMTMxMTA2MTQx
NjUzWjCBqjELMAkGA1UEBhMCTkwxEjAQBgNVBAgMCUFtc3RlcmRhbTESMBAGA1UE
BwwJQW1zdGVyZGFtMQ8wDQYDVQQKDAZQaW1UaW0xDzANBgNVBAsMBlBpbVRpbTEr
MCkGA1UEAwwiZGV2NTMucXVhcnRzb2Z0LmNvbS9wa2xpbW92L3BpbXRpbTEkMCIG
CSqGSIb3DQEJARYVcGtsaW1vdkBxdWFydHNvZnQuY29tMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQDE0d63YwpBLxzxQAW887JALcGruAHkHu7Ui1oc7bCIMy+u
d6rPgNmbFLw3GoGzQ8xhMmksZHsS07IfWRTDeisPHAqfgcApOZbyMyZUAL6+1ko4
xAIPnQSia7l8M4nWgtgqifDCbFKAoPXuWSrYDOFtgSkBLH5xYyFPRc04nnHpoQID
AQABo1AwTjAdBgNVHQ4EFgQUE2oxXYDFRNtgvn8tyXldepRFWzYwHwYDVR0jBBgw
FoAUE2oxXYDFRNtgvn8tyXldepRFWzYwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B
AQUFAAOBgQB1/S46dWBECaOs4byCysFhzXw8qx8znJkSZcIdDilmg1kkfusXKi2S
DiiFw5gDrc6Qp6WtPmVhxHUWl6O5bOG8lG0Dcppeed9454CGvBShmYdwC6vk0s7/
gVdK2V4fYsUeT6u49ONshvJ/8xhHz2gGXeLWaqHwtK3Dl3S6TIDuoQ==
-----END CERTIFICATE-----';
}
/**
* Returns test private certificate string.
* @return string private certificate string.
*/
protected function getTestPrivateCertificate()
{
return '-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDE0d63YwpBLxzxQAW887JALcGruAHkHu7Ui1oc7bCIMy+ud6rP
gNmbFLw3GoGzQ8xhMmksZHsS07IfWRTDeisPHAqfgcApOZbyMyZUAL6+1ko4xAIP
nQSia7l8M4nWgtgqifDCbFKAoPXuWSrYDOFtgSkBLH5xYyFPRc04nnHpoQIDAQAB
AoGAPm1e2gYE86Xw5ShsaYFWcXrR6hiEKQoSsMG+hFxz2M97eTglqolw+/p4tHWo
2+ZORioKJ/V6//67iavkpRfz3dloUlNE9ZzlvqvPjHePt3BI22GI8D84dcqnxWW5
4okEAfDfXk2B4UNOpVNU5FZjg4XvBEbbhRVrsBWAPMduDX0CQQDtFgLLLWr50F3z
lGuFy68Y1d01sZsyf7xUPaLcDWbrnVMIjZIs60BbLg9PZ6sYcwV2RwL/WaJU0Ap/
KKjHW51zAkEA1IWBicQtt6yGaUqydq+ifX8/odFjIrlZklwckLl65cImyxqDYMnA
m+QdbZznSH96BHjduhJAEAtfYx5CVMrXmwJAHKiWedzpm3z2fmUoginW5pejf8QS
UI5kQ4KX1yW/lSeVS+lhDBD73Im6zAxqADCXLm7zC87X8oybWDef/0kxxQJAebRX
AalKMSRo+QVg/F0Kpenoa+f4aNtSc2GyriK6QbeU9b0iPZxsZBoXzD0NqlPucX8y
IyvuagHJR379p4dePwJBAMCkYSATGdhYbeDfySWUro5K0QAvBNj8FuNJQ4rqUxz8
8b+OXIyd5WlmuDRTDGJBTxAYeaioTuMCFWaZm4jG0I4=
-----END RSA PRIVATE KEY-----';
}
// Tests :
public function testGenerateSignature()
{
$signatureMethod = new RsaSha1();
$signatureMethod->setPrivateCertificate($this->getTestPrivateCertificate());
$signatureMethod->setPublicCertificate($this->getTestPublicCertificate());
$baseString = 'test_base_string';
$key = 'test_key';
$signature = $signatureMethod->generateSignature($baseString, $key);
$this->assertNotEmpty($signature, 'Unable to generate signature!');
}
/**
* @depends testGenerateSignature
*/
public function testVerify()
{
$signatureMethod = new RsaSha1();
$signatureMethod->setPrivateCertificate($this->getTestPrivateCertificate());
$signatureMethod->setPublicCertificate($this->getTestPublicCertificate());
$baseString = 'test_base_string';
$key = 'test_key';
$signature = 'unsigned';
$this->assertFalse($signatureMethod->verify($signature, $baseString, $key), 'Unsigned signature is valid!');
$generatedSignature = $signatureMethod->generateSignature($baseString, $key);
$this->assertTrue($signatureMethod->verify($generatedSignature, $baseString, $key), 'Generated signature is invalid!');
}
public function testInitPrivateCertificate()
{
$signatureMethod = new RsaSha1();
$certificateFileName = __FILE__;
$signatureMethod->privateCertificateFile = $certificateFileName;
$this->assertEquals(file_get_contents($certificateFileName), $signatureMethod->getPrivateCertificate(), 'Unable to fetch private certificate from file!');
}
public function testInitPublicCertificate()
{
$signatureMethod = new RsaSha1();
$certificateFileName = __FILE__;
$signatureMethod->publicCertificateFile = $certificateFileName;
$this->assertEquals(file_get_contents($certificateFileName), $signatureMethod->getPublicCertificate(), 'Unable to fetch public certificate from file!');
}
}
Loading…
Cancel
Save