Browse Source

Merge pull request #1620 from klimov-paul/authclient

Proposal for #66: auth client implementation
tags/2.0.0-beta
Qiang Xue 11 years ago
parent
commit
5c57533552
  1. 359
      extensions/yii/authclient/AuthAction.php
  2. 237
      extensions/yii/authclient/BaseClient.php
  3. 503
      extensions/yii/authclient/BaseOAuth.php
  4. 57
      extensions/yii/authclient/ClientInterface.php
  5. 107
      extensions/yii/authclient/Collection.php
  6. 32
      extensions/yii/authclient/LICENSE.md
  7. 354
      extensions/yii/authclient/OAuth1.php
  8. 184
      extensions/yii/authclient/OAuth2.php
  9. 186
      extensions/yii/authclient/OAuthToken.php
  10. 928
      extensions/yii/authclient/OpenId.php
  11. 83
      extensions/yii/authclient/README.md
  12. 82
      extensions/yii/authclient/clients/Facebook.php
  13. 76
      extensions/yii/authclient/clients/GitHub.php
  14. 92
      extensions/yii/authclient/clients/GoogleOAuth.php
  15. 90
      extensions/yii/authclient/clients/GoogleOpenId.php
  16. 167
      extensions/yii/authclient/clients/LinkedIn.php
  17. 90
      extensions/yii/authclient/clients/Twitter.php
  18. 90
      extensions/yii/authclient/clients/YandexOAuth.php
  19. 86
      extensions/yii/authclient/clients/YandexOpenId.php
  20. 28
      extensions/yii/authclient/composer.json
  21. 51
      extensions/yii/authclient/signature/BaseMethod.php
  22. 47
      extensions/yii/authclient/signature/HmacSha1.php
  23. 33
      extensions/yii/authclient/signature/PlainText.php
  24. 168
      extensions/yii/authclient/signature/RsaSha1.php
  25. 38
      extensions/yii/authclient/views/redirect.php
  26. 229
      extensions/yii/authclient/widgets/Choice.php
  27. 30
      extensions/yii/authclient/widgets/ChoiceAsset.php
  28. 82
      extensions/yii/authclient/widgets/assets/authchoice.css
  29. 68
      extensions/yii/authclient/widgets/assets/authchoice.js
  30. BIN
      extensions/yii/authclient/widgets/assets/authchoice.png
  31. 68
      tests/unit/extensions/authclient/AuthActionTest.php
  32. 82
      tests/unit/extensions/authclient/BaseClientTest.php
  33. 251
      tests/unit/extensions/authclient/BaseOAuthTest.php
  34. 85
      tests/unit/extensions/authclient/CollectionTest.php
  35. 109
      tests/unit/extensions/authclient/OAuth1Test.php
  36. 33
      tests/unit/extensions/authclient/OAuth2Test.php
  37. 61
      tests/unit/extensions/authclient/OpenIdTest.php
  38. 30
      tests/unit/extensions/authclient/TestCase.php
  39. 133
      tests/unit/extensions/authclient/TokenTest.php
  40. 50
      tests/unit/extensions/authclient/signature/BaseMethodTest.php
  41. 20
      tests/unit/extensions/authclient/signature/HmacSha1Test.php
  42. 20
      tests/unit/extensions/authclient/signature/PlainTextTest.php
  43. 110
      tests/unit/extensions/authclient/signature/RsaSha1Test.php

359
extensions/yii/authclient/AuthAction.php

@ -0,0 +1,359 @@
<?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\Action;
use yii\base\Exception;
use yii\base\InvalidConfigException;
use yii\base\NotSupportedException;
use yii\web\Response;
use yii\web\HttpException;
use yii\web\NotFoundHttpException;
use Yii;
/**
* AuthAction performs authentication via different auth clients.
* It supports [[OpenId]], [[OAuth1] and [[OAuth2]] client types.
*
* Usage:
* ~~~
* class SiteController extends Controller
* {
* public function actions()
* {
* return [
* 'auth' => [
* 'class' => 'yii\authclient\AuthAction',
* 'successCallback' => [$this, 'successCallback'],
* ],
* ]
* }
*
* public function successCallback($client)
* {
* $atributes = $client->getUserAttributes();
* // user login or signup comes here
* }
* }
* ~~~
*
* Usually authentication via external services is performed inside the popup window.
* This action handles the redirection and closing of popup window correctly.
*
* @see Collection
* @see \yii\authclient\widgets\Choice
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class AuthAction extends Action
{
/**
* @var string name of the auth client collection application component.
* It should point to [[Collection]] instance.
*/
public $clientCollection = 'authClientCollection';
/**
* @var string name of the GET param, which is used to passed auth client id to this action.
* Note: watch for the naming, make sure you do not choose name used in some auth protocol.
*/
public $clientIdGetParamName = 'authclient';
/**
* @var callable PHP callback, which should be triggered in case of successful authentication.
* This callback should accept [[ClientInterface]] instance as an argument.
* For example:
*
* ~~~
* public function onAuthSuccess($client)
* {
* $atributes = $client->getUserAttributes();
* // user login or signup comes here
* }
* ~~~
*
* If this callback returns [[Response]] instance, it will be used as action response,
* otherwise redirection to [[successUrl]] will be performed.
*
*/
public $successCallback;
/**
* @var string the redirect url after successful authorization.
*/
private $_successUrl = '';
/**
* @var string the redirect url after unsuccessful authorization (e.g. user canceled).
*/
private $_cancelUrl = '';
/**
* @var string name or alias of the view file, which should be rendered in order to perform redirection.
* If not set default one will be used.
*/
public $redirectView;
/**
* @param string $url successful URL.
*/
public function setSuccessUrl($url)
{
$this->_successUrl = $url;
}
/**
* @return string successful URL.
*/
public function getSuccessUrl()
{
if (empty($this->_successUrl)) {
$this->_successUrl = $this->defaultSuccessUrl();
}
return $this->_successUrl;
}
/**
* @param string $url cancel URL.
*/
public function setCancelUrl($url)
{
$this->_cancelUrl = $url;
}
/**
* @return string cancel URL.
*/
public function getCancelUrl()
{
if (empty($this->_cancelUrl)) {
$this->_cancelUrl = $this->defaultCancelUrl();
}
return $this->_cancelUrl;
}
/**
* Creates default {@link successUrl} value.
* @return string success URL value.
*/
protected function defaultSuccessUrl()
{
return Yii::$app->getUser()->getReturnUrl();
}
/**
* Creates default {@link cancelUrl} value.
* @return string cancel URL value.
*/
protected function defaultCancelUrl()
{
return Yii::$app->getRequest()->getAbsoluteUrl();
}
/**
* Runs the action.
*/
public function run()
{
if (!empty($_GET[$this->clientIdGetParamName])) {
$clientId = $_GET[$this->clientIdGetParamName];
/** @var \yii\authclient\Collection $collection */
$collection = Yii::$app->getComponent($this->clientCollection);
if (!$collection->hasClient($clientId)) {
throw new NotFoundHttpException("Unknown auth client '{$clientId}'");
}
$client = $collection->getClient($clientId);
return $this->auth($client);
} else {
throw new NotFoundHttpException();
}
}
/**
* @param mixed $client auth client instance.
* @return Response response instance.
* @throws \yii\base\NotSupportedException on invalid client.
*/
protected function auth($client)
{
if ($client instanceof OpenId) {
return $this->authOpenId($client);
} elseif ($client instanceof OAuth2) {
return $this->authOAuth2($client);
} elseif ($client instanceof OAuth1) {
return $this->authOAuth1($client);
} else {
throw new NotSupportedException('Provider "' . get_class($client) . '" is not supported.');
}
}
/**
* This method is invoked in case of successful authentication via auth client.
* @param ClientInterface $client auth client instance.
* @throws InvalidConfigException on invalid success callback.
* @return Response response instance.
*/
protected function authSuccess($client)
{
if (!is_callable($this->successCallback)) {
throw new InvalidConfigException('"' . get_class($this) . '::successCallback" should be a valid callback.');
}
$response = call_user_func($this->successCallback, $client);
if ($response instanceof Response) {
return $response;
}
return $this->redirectSuccess();
}
/**
* Redirect to the given URL or simply close the popup window.
* @param mixed $url URL to redirect, could be a string or array config to generate a valid URL.
* @param boolean $enforceRedirect indicates if redirect should be performed even in case of popup window.
* @return \yii\web\Response response instance.
*/
public function redirect($url, $enforceRedirect = true)
{
$viewFile = $this->redirectView;
if ($viewFile === null) {
$viewFile = __DIR__ . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . 'redirect.php';
} else {
$viewFile = Yii::getAlias($viewFile);
}
$viewData = [
'url' => $url,
'enforceRedirect' => $enforceRedirect,
];
$response = Yii::$app->getResponse();
$response->content = Yii::$app->getView()->renderFile($viewFile, $viewData);
return $response;
}
/**
* Redirect to the URL. If URL is null, {@link successUrl} will be used.
* @param string $url URL to redirect.
* @return \yii\web\Response response instance.
*/
public function redirectSuccess($url = null)
{
if ($url === null) {
$url = $this->getSuccessUrl();
}
return $this->redirect($url);
}
/**
* Redirect to the {@link cancelUrl} or simply close the popup window.
* @param string $url URL to redirect.
* @return \yii\web\Response response instance.
*/
public function redirectCancel($url = null)
{
if ($url === null) {
$url = $this->getCancelUrl();
}
return $this->redirect($url, false);
}
/**
* Performs OpenID auth flow.
* @param OpenId $client auth client instance.
* @return Response action response.
* @throws Exception on failure.
* @throws HttpException on failure.
*/
protected function authOpenId($client)
{
if (!empty($_REQUEST['openid_mode'])) {
switch ($_REQUEST['openid_mode']) {
case 'id_res':
if ($client->validate()) {
return $this->authSuccess($client);
} else {
throw new HttpException(400, 'Unable to complete the authentication because the required data was not received.');
}
break;
case 'cancel':
$this->redirectCancel();
break;
default:
throw new HttpException(400);
break;
}
} else {
$url = $client->buildAuthUrl();
return Yii::$app->getResponse()->redirect($url);
}
return $this->redirectCancel();
}
/**
* Performs OAuth1 auth flow.
* @param OAuth1 $client auth client instance.
* @return Response action response.
*/
protected function authOAuth1($client)
{
// user denied error
if (isset($_GET['denied'])) {
return $this->redirectCancel();
}
if (isset($_REQUEST['oauth_token'])) {
$oauthToken = $_REQUEST['oauth_token'];
}
if (!isset($oauthToken)) {
// Get request token.
$requestToken = $client->fetchRequestToken();
// Get authorization URL.
$url = $client->buildAuthUrl($requestToken);
// Redirect to authorization URL.
return Yii::$app->getResponse()->redirect($url);
} else {
// Upgrade to access token.
$accessToken = $client->fetchAccessToken();
return $this->authSuccess($client);
}
}
/**
* Performs OAuth2 auth flow.
* @param OAuth2 $client auth client instance.
* @return Response action response.
* @throws \yii\base\Exception on failure.
*/
protected function authOAuth2($client)
{
if (isset($_GET['error'])) {
if ($_GET['error'] == 'access_denied') {
// user denied error
return $this->redirectCancel();
} else {
// request error
if (isset($_GET['error_description'])) {
$errorMessage = $_GET['error_description'];
} elseif (isset($_GET['error_message'])) {
$errorMessage = $_GET['error_message'];
} else {
$errorMessage = http_build_query($_GET);
}
throw new Exception('Auth error: ' . $errorMessage);
}
}
// Get the access_token and save them to the session.
if (isset($_GET['code'])) {
$code = $_GET['code'];
$token = $client->fetchAccessToken($code);
if (!empty($token)) {
return $this->authSuccess($client);
} else {
return $this->redirectCancel();
}
} else {
$url = $client->buildAuthUrl();
return Yii::$app->getResponse()->redirect($url);
}
}
}

237
extensions/yii/authclient/BaseClient.php

@ -0,0 +1,237 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient;
use Yii;
use yii\base\Component;
use yii\base\NotSupportedException;
use yii\helpers\Inflector;
use yii\helpers\StringHelper;
/**
* BaseClient is a base Auth Client class.
*
* @see ClientInterface
*
* @property string $id auth service id.
* @property string $name auth service name.
* @property string $title auth service title.
* @property array $userAttributes authenticated user attributes.
* @property array $normalizeUserAttributeMap map used to normalize user attributes fetched from
* external auth service in format: rawAttributeName => normalizedAttributeName.
* @property array $viewOptions view options in format: optionName => optionValue.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
abstract class BaseClient extends Component implements ClientInterface
{
/**
* @var string auth service id.
* This value mainly used as HTTP request parameter.
*/
private $_id;
/**
* @var string auth service name.
* This value may be used in database records, CSS files and so on.
*/
private $_name;
/**
* @var string auth service title to display in views.
*/
private $_title;
/**
* @var array authenticated user attributes.
*/
private $_userAttributes;
/**
* @var array map used to normalize user attributes fetched from external auth service
* in format: rawAttributeName => normalizedAttributeName
*/
private $_normalizeUserAttributeMap;
/**
* @var array view options in format: optionName => optionValue
*/
private $_viewOptions;
/**
* @param string $id service id.
*/
public function setId($id)
{
$this->_id = $id;
}
/**
* @return string service id
*/
public function getId()
{
if (empty($this->_id)) {
$this->_id = $this->getName();
}
return $this->_id;
}
/**
* @param string $name service name.
*/
public function setName($name)
{
$this->_name = $name;
}
/**
* @return string service name.
*/
public function getName()
{
if ($this->_name === null) {
$this->_name = $this->defaultName();
}
return $this->_name;
}
/**
* @param string $title service title.
*/
public function setTitle($title)
{
$this->_title = $title;
}
/**
* @return string service title.
*/
public function getTitle()
{
if ($this->_title === null) {
$this->_title = $this->defaultTitle();
}
return $this->_title;
}
/**
* @param array $userAttributes list of user attributes
*/
public function setUserAttributes($userAttributes)
{
$this->_userAttributes = $this->normalizeUserAttributes($userAttributes);
}
/**
* @return array list of user attributes
*/
public function getUserAttributes()
{
if ($this->_userAttributes === null) {
$this->_userAttributes = $this->normalizeUserAttributes($this->initUserAttributes());
}
return $this->_userAttributes;
}
/**
* @param array $normalizeUserAttributeMap normalize user attribute map.
*/
public function setNormalizeUserAttributeMap($normalizeUserAttributeMap)
{
$this->_normalizeUserAttributeMap = $normalizeUserAttributeMap;
}
/**
* @return array normalize user attribute map.
*/
public function getNormalizeUserAttributeMap()
{
if ($this->_normalizeUserAttributeMap === null) {
$this->_normalizeUserAttributeMap = $this->defaultNormalizeUserAttributeMap();
}
return $this->_normalizeUserAttributeMap;
}
/**
* @param array $viewOptions view options in format: optionName => optionValue
*/
public function setViewOptions($viewOptions)
{
$this->_viewOptions = $viewOptions;
}
/**
* @return array view options in format: optionName => optionValue
*/
public function getViewOptions()
{
if ($this->_viewOptions === null) {
$this->_viewOptions = $this->defaultViewOptions();
}
return $this->_viewOptions;
}
/**
* Generates service name.
* @return string service name.
*/
protected function defaultName()
{
return Inflector::camel2id(StringHelper::basename(get_class($this)));
}
/**
* Generates service title.
* @return string service title.
*/
protected function defaultTitle()
{
return StringHelper::basename(get_class($this));
}
/**
* Initializes authenticated user attributes.
* @return array auth user attributes.
*/
protected function initUserAttributes()
{
throw new NotSupportedException('Method "' . get_class($this) . '::' . __FUNCTION__ . '" not implemented.');
}
/**
* Returns the default [[normalizeUserAttributeMap]] value.
* Particular client may override this method in order to provide specific default map.
* @return array normalize attribute map.
*/
protected function defaultNormalizeUserAttributeMap()
{
return [];
}
/**
* Returns the default [[viewOptions]] value.
* Particular client may override this method in order to provide specific default view options.
* @return array list of default [[viewOptions]]
*/
protected function defaultViewOptions()
{
return [];
}
/**
* Normalize given user attributes according to {@link normalizeUserAttributeMap}.
* @param array $attributes raw attributes.
* @return array normalized attributes.
*/
protected function normalizeUserAttributes($attributes)
{
foreach ($this->getNormalizeUserAttributeMap() as $normalizedName => $actualName) {
if (array_key_exists($actualName, $attributes)) {
$attributes[$normalizedName] = $attributes[$actualName];
}
}
return $attributes;
}
}

503
extensions/yii/authclient/BaseOAuth.php

@ -0,0 +1,503 @@
<?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\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 BaseOAuth extends BaseClient implements ClientInterface
{
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 OAuthToken|array access token instance or its array configuration.
*/
private $_accessToken;
/**
* @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 ($this->_returnUrl === null) {
$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|OAuthToken $token
*/
public function setAccessToken($token)
{
if (!is_object($token)) {
$token = $this->createToken($token);
}
$this->_accessToken = $token;
$this->saveAccessToken($token);
}
/**
* @return OAuthToken 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\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 ' . $this->version . ' 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_str($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 OAuthToken token instance.
*/
protected function createToken(array $tokenConfig = [])
{
if (!array_key_exists('class', $tokenConfig)) {
$tokenConfig['class'] = OAuthToken::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 OAuthToken $token auth token
* @return static self reference.
*/
protected function saveAccessToken(OAuthToken $token)
{
return $this->setState('token', $token);
}
/**
* Restores access token.
* @return OAuthToken auth token.
*/
protected function restoreAccessToken()
{
$token = $this->getState('token');
if (is_object($token)) {
/* @var $token OAuthToken */
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 OAuthToken $token expired auth token.
* @return OAuthToken new auth token.
*/
abstract public function refreshAccessToken(OAuthToken $token);
/**
* 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.
*/
abstract protected function apiInternal($accessToken, $url, $method, array $params);
}

57
extensions/yii/authclient/ClientInterface.php

@ -0,0 +1,57 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient;
/**
* ClientInterface declares basic interface all Auth clients should follow.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
interface ClientInterface
{
/**
* @param string $id service id.
*/
public function setId($id);
/**
* @return string service id
*/
public function getId();
/**
* @return string service name.
*/
public function getName();
/**
* @param string $name service name.
*/
public function setName($name);
/**
* @return string service title.
*/
public function getTitle();
/**
* @param string $title service title.
*/
public function setTitle($title);
/**
* @return array list of user attributes
*/
public function getUserAttributes();
/**
* @return array view options in format: optionName => optionValue
*/
public function getViewOptions();
}

107
extensions/yii/authclient/Collection.php

@ -0,0 +1,107 @@
<?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\Component;
use yii\base\InvalidParamException;
use Yii;
/**
* Collection is a storage for all auth clients in the application.
*
* Example application configuration:
*
* ~~~
* 'components' => [
* 'authClientCollection' => [
* 'class' => 'yii\authclient\Collection',
* 'clients' => [
* 'google' => [
* 'class' => 'yii\authclient\clients\GoogleOpenId'
* ],
* 'facebook' => [
* 'class' => 'yii\authclient\clients\Facebook',
* 'clientId' => 'facebook_client_id',
* 'clientSecret' => 'facebook_client_secret',
* ],
* ],
* ]
* ...
* ]
* ~~~
*
* @property array $clients list of Auth clients with their configuration in format: 'clientId' => [...]
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class Collection extends Component
{
/**
* @var array list of Auth clients with their configuration in format: 'clientId' => [...]
*/
private $_clients = [];
/**
* @param array $clients list of auth clients
*/
public function setClients(array $clients)
{
$this->_clients = $clients;
}
/**
* @return ClientInterface[] list of auth clients.
*/
public function getClients()
{
$clients = [];
foreach ($this->_clients as $id => $client) {
$clients[$id] = $this->getClient($id);
}
return $clients;
}
/**
* @param string $id service id.
* @return ClientInterface auth client instance.
* @throws InvalidParamException on non existing client request.
*/
public function getClient($id)
{
if (!array_key_exists($id, $this->_clients)) {
throw new InvalidParamException("Unknown auth client '{$id}'.");
}
if (!is_object($this->_clients[$id])) {
$this->_clients[$id] = $this->createClient($id, $this->_clients[$id]);
}
return $this->_clients[$id];
}
/**
* Checks if client exists in the hub.
* @param string $id client id.
* @return boolean whether client exist.
*/
public function hasClient($id)
{
return array_key_exists($id, $this->_clients);
}
/**
* Creates auth client instance from its array configuration.
* @param string $id auth client id.
* @param array $config auth client instance configuration.
* @return ClientInterface auth client instance.
*/
protected function createClient($id, $config)
{
$config['id'] = $id;
return Yii::createObject($config);
}
}

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.

354
extensions/yii/authclient/OAuth1.php

@ -0,0 +1,354 @@
<?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
* return 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)) {
$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']);
uksort($params, 'strcmp'); // Parameters are sorted by name, using lexicographical byte value ordering. Ref: Spec: 9.1.1
$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/OAuth2.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;
use Yii;
use yii\base\Exception;
/**
* OAuth2 serves as a client for the OAuth 2 flow.
*
* In oder to acquire access token perform following sequence:
*
* ~~~
* use yii\authclient\OAuth2;
*
* $oauthClient = new OAuth2();
* $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 OAuth2 extends BaseOAuth
{
/**
* @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 OAuthToken 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 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['access_token'] = $accessToken->getToken();
return $this->sendRequest($method, $url, $params);
}
/**
* Gets new auth token to replace expired one.
* @param OAuthToken $token expired auth token.
* @return OAuthToken new auth token.
*/
public function refreshAccessToken(OAuthToken $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 OAuthToken token instance.
*/
protected function createToken(array $tokenConfig = [])
{
$tokenConfig['tokenParamKey'] = 'access_token';
return parent::createToken($tokenConfig);
}
}

186
extensions/yii/authclient/OAuthToken.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;
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 OAuthToken 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());
}
}

928
extensions/yii/authclient/OpenId.php

@ -0,0 +1,928 @@
<?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\base\NotSupportedException;
use Yii;
/**
* OpenId provides a simple interface for OpenID (1.1 and 2.0) authentication.
* Supports Yadis and HTML discovery.
*
* Usage:
*
* ~~~
* use yii\authclient\OpenId;
*
* $client = new OpenId();
* $client->authUrl = 'https://open.id.provider.url'; // Setup provider endpoint
* $url = $client->buildAuthUrl(); // Get authentication URL
* return Yii::$app->getResponse()->redirect($url); // Redirect to authentication URL
* // After user returns at our site:
* if ($client->validate()) { // validate response
* $userAttributes = $client->getUserAttributes(); // get account info
* ...
* }
* ~~~
*
* AX and SREG extensions are supported.
* To use them, specify [[requiredAttributes]] and/or [[optionalAttributes]].
*
* @see http://openid.net/
*
* @property string $returnUrl authentication return URL.
* @property string $claimedId claimed identifier (identity).
* @property string $trustRoot client trust root (realm), by default [[\yii\web\Request::hostInfo]] value will be used.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class OpenId extends BaseClient implements ClientInterface
{
/**
* @var string authentication base URL, which should be used to compose actual authentication URL
* by [[buildAuthUrl()]] method.
*/
public $authUrl;
/**
* @var array list of attributes, which always should be returned from server.
* Attribute names should be always specified in AX format.
* For example:
* ~~~
* ['namePerson/friendly', 'contact/email']
* ~~~
*/
public $requiredAttributes = [];
/**
* @var array list of attributes, which could be returned from server.
* Attribute names should be always specified in AX format.
* For example:
* ~~~
* ['namePerson/first', 'namePerson/last']
* ~~~
*/
public $optionalAttributes = [];
/**
* @var boolean whether to verify the peer's certificate.
*/
public $verifyPeer;
/**
* @var string directory that holds multiple CA certificates.
* This value will take effect only if [[verifyPeer]] is set.
*/
public $capath;
/**
* @var string the name of a file holding one or more certificates to verify the peer with.
* This value will take effect only if [[verifyPeer]] is set.
*/
public $cainfo;
/**
* @var string authentication return URL.
*/
private $_returnUrl;
/**
* @var string claimed identifier (identity)
*/
private $_claimedId;
/**
* @var string client trust root (realm), by default [[\yii\web\Request::hostInfo]] value will be used.
*/
private $_trustRoot;
/**
* @var array data, which should be used to retrieve the OpenID response.
* If not set combination of GET and POST will be used.
*/
public $data;
/**
* @var array map of matches between AX and SREG attribute names in format: axAttributeName => sregAttributeName
*/
public $axToSregMap = [
'namePerson/friendly' => 'nickname',
'contact/email' => 'email',
'namePerson' => 'fullname',
'birthDate' => 'dob',
'person/gender' => 'gender',
'contact/postalCode/home' => 'postcode',
'contact/country/home' => 'country',
'pref/language' => 'language',
'pref/timezone' => 'timezone',
];
/**
* @inheritdoc
*/
public function init()
{
if ($this->data === null) {
$this->data = array_merge($_GET, $_POST); // OPs may send data as POST or GET.
}
}
/**
* @param string $claimedId claimed identifier (identity).
*/
public function setClaimedId($claimedId)
{
$this->_claimedId = $claimedId;
}
/**
* @return string claimed identifier (identity).
*/
public function getClaimedId()
{
if ($this->_claimedId === null) {
if (isset($this->data['openid_claimed_id'])) {
$this->_claimedId = $this->data['openid_claimed_id'];
} elseif (isset($this->data['openid_identity'])) {
$this->_claimedId = $this->data['openid_identity'];
}
}
return $this->_claimedId;
}
/**
* @param string $returnUrl authentication return URL.
*/
public function setReturnUrl($returnUrl)
{
$this->_returnUrl = $returnUrl;
}
/**
* @return string authentication return URL.
*/
public function getReturnUrl()
{
if ($this->_returnUrl === null) {
$this->_returnUrl = $this->defaultReturnUrl();
}
return $this->_returnUrl;
}
/**
* @param string $value client trust root (realm).
*/
public function setTrustRoot($value)
{
$this->_trustRoot = $value;
}
/**
* @return string client trust root (realm).
*/
public function getTrustRoot()
{
if ($this->_trustRoot === null) {
$this->_trustRoot = Yii::$app->getRequest()->getHostInfo();
}
return $this->_trustRoot;
}
/**
* Generates default [[returnUrl]] value.
* @return string default authentication return URL.
*/
protected function defaultReturnUrl()
{
$params = $_GET;
foreach ($params as $name => $value) {
if (strncmp('openid', $name, 6) === 0) {
unset($params[$name]);
}
}
$url = Yii::$app->getUrlManager()->createUrl(Yii::$app->requestedRoute, $params);
return $this->getTrustRoot() . $url;
}
/**
* Checks if the server specified in the url exists.
* @param string $url URL to check
* @return boolean true, if the server exists; false otherwise
*/
public function hostExists($url)
{
if (strpos($url, '/') === false) {
$server = $url;
} else {
$server = @parse_url($url, PHP_URL_HOST);
}
if (!$server) {
return false;
}
$ips = gethostbynamel($server);
return !empty($ips);
}
/**
* Sends HTTP request.
* @param string $url request URL.
* @param string $method request method.
* @param array $params request params.
* @return array|string response.
* @throws \yii\base\Exception on failure.
*/
protected function sendCurlRequest($url, $method = 'GET', $params = [])
{
$params = http_build_query($params, '', '&');
$curl = curl_init($url . ($method == 'GET' && $params ? '?' . $params : ''));
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept: application/xrds+xml, */*'));
if ($this->verifyPeer !== null) {
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifyPeer);
if($this->capath) {
curl_setopt($curl, CURLOPT_CAPATH, $this->capath);
}
if($this->cainfo) {
curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo);
}
}
if ($method == 'POST') {
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
} elseif ($method == 'HEAD') {
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_NOBODY, true);
} else {
curl_setopt($curl, CURLOPT_HTTPGET, true);
}
$response = curl_exec($curl);
if ($method == 'HEAD') {
$headers = [];
foreach (explode("\n", $response) as $header) {
$pos = strpos($header, ':');
$name = strtolower(trim(substr($header, 0, $pos)));
$headers[$name] = trim(substr($header, $pos+1));
}
return $headers;
}
if (curl_errno($curl)) {
throw new Exception(curl_error($curl), curl_errno($curl));
}
return $response;
}
/**
* Sends HTTP request.
* @param string $url request URL.
* @param string $method request method.
* @param array $params request params.
* @return array|string response.
* @throws \yii\base\Exception on failure.
* @throws \yii\base\NotSupportedException if request method is not supported.
*/
protected function sendStreamRequest($url, $method = 'GET', $params = [])
{
if (!$this->hostExists($url)) {
throw new Exception('Invalid request.');
}
$params = http_build_query($params, '', '&');
switch ($method) {
case 'GET':
$options = [
'http' => [
'method' => 'GET',
'header' => 'Accept: application/xrds+xml, */*',
'ignore_errors' => true,
]
];
$url = $url . ($params ? '?' . $params : '');
break;
case 'POST':
$options = [
'http' => [
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => $params,
'ignore_errors' => true,
]
];
break;
case 'HEAD':
/* We want to send a HEAD request,
but since get_headers doesn't accept $context parameter,
we have to change the defaults.*/
$default = stream_context_get_options(stream_context_get_default());
stream_context_get_default([
'http' => [
'method' => 'HEAD',
'header' => 'Accept: application/xrds+xml, */*',
'ignore_errors' => true,
]
]);
$url = $url . ($params ? '?' . $params : '');
$headersTmp = get_headers($url);
if (empty($headersTmp)) {
return [];
}
// Parsing headers.
$headers = [];
foreach ($headersTmp as $header) {
$pos = strpos($header, ':');
$name = strtolower(trim(substr($header, 0, $pos)));
$headers[$name] = trim(substr($header, $pos + 1));
}
// and restore them
stream_context_get_default($default);
return $headers;
default:
throw new NotSupportedException("Method {$method} not supported");
}
if ($this->verifyPeer) {
$options = array_merge(
$options,
[
'ssl' => [
'verify_peer' => true,
'capath' => $this->capath,
'cafile' => $this->cainfo,
]
]
);
}
$context = stream_context_create($options);
return file_get_contents($url, false, $context);
}
/**
* Sends request to the server
* @param string $url request URL.
* @param string $method request method.
* @param array $params request parameters.
* @return array|string response.
*/
protected function sendRequest($url, $method = 'GET', $params = [])
{
if (function_exists('curl_init') && !ini_get('safe_mode')) {
return $this->sendCurlRequest($url, $method, $params);
}
return $this->sendStreamRequest($url, $method, $params);
}
/**
* Combines given URLs into single one.
* @param string $baseUrl base URL.
* @param string|array $additionalUrl additional URL string or information array.
* @return string composed URL.
*/
protected function buildUrl($baseUrl, $additionalUrl)
{
$baseUrl = parse_url($baseUrl);
if (!is_array($additionalUrl)) {
$additionalUrl = parse_url($additionalUrl);
}
if (isset($baseUrl['query'], $additionalUrl['query'])) {
$additionalUrl['query'] = $baseUrl['query'] . '&' . $additionalUrl['query'];
}
$urlInfo = array_merge($baseUrl, $additionalUrl);
$url = $urlInfo['scheme'] . '://'
. (empty($urlInfo['username']) ? ''
:(empty($urlInfo['password']) ? "{$urlInfo['username']}@"
:"{$urlInfo['username']}:{$urlInfo['password']}@"))
. $urlInfo['host']
. (empty($urlInfo['port']) ? '' : ":{$urlInfo['port']}")
. (empty($urlInfo['path']) ? '' : $urlInfo['path'])
. (empty($urlInfo['query']) ? '' : "?{$urlInfo['query']}")
. (empty($urlInfo['fragment']) ? '' : "#{$urlInfo['fragment']}");
return $url;
}
/**
* Scans content for <meta>/<link> tags and extract information from them.
* @param string $content HTML content to be be parsed.
* @param string $tag name of the source tag.
* @param string $matchAttributeName name of the source tag attribute, which should contain $matchAttributeValue
* @param string $matchAttributeValue required value of $matchAttributeName
* @param string $valueAttributeName name of the source tag attribute, which should contain searched value.
* @return string|boolean searched value, "false" on failure.
*/
protected function extractHtmlTagValue($content, $tag, $matchAttributeName, $matchAttributeValue, $valueAttributeName)
{
preg_match_all("#<{$tag}[^>]*$matchAttributeName=['\"].*?$matchAttributeValue.*?['\"][^>]*$valueAttributeName=['\"](.+?)['\"][^>]*/?>#i", $content, $matches1);
preg_match_all("#<{$tag}[^>]*$valueAttributeName=['\"](.+?)['\"][^>]*$matchAttributeName=['\"].*?$matchAttributeValue.*?['\"][^>]*/?>#i", $content, $matches2);
$result = array_merge($matches1[1], $matches2[1]);
return empty($result) ? false : $result[0];
}
/**
* Performs Yadis and HTML discovery.
* @param string $url Identity URL.
* @return array OpenID provider info, following keys will be available:
* - 'url' - string OP Endpoint (i.e. OpenID provider address).
* - 'version' - integer OpenID protocol version used by provider.
* - 'identity' - string identity value.
* - 'identifier_select' - boolean whether to request OP to select identity for an user in OpenID 2, does not affect OpenID 1.
* - 'ax' - boolean whether AX attributes should be used.
* - 'sreg' - boolean whether SREG attributes should be used.
* @throws Exception on failure.
*/
public function discover($url)
{
if (empty($url)) {
throw new Exception('No identity supplied.');
}
$result = [
'url' => null,
'version' => null,
'identity' => $url,
'identifier_select' => false,
'ax' => false,
'sreg' => false,
];
// Use xri.net proxy to resolve i-name identities
if (!preg_match('#^https?:#', $url)) {
$url = 'https://xri.net/' . $url;
}
/* We save the original url in case of Yadis discovery failure.
It can happen when we'll be lead to an XRDS document
which does not have any OpenID2 services.*/
$originalUrl = $url;
// A flag to disable yadis discovery in case of failure in headers.
$yadis = true;
// We'll jump a maximum of 5 times, to avoid endless redirections.
for ($i = 0; $i < 5; $i ++) {
if ($yadis) {
$headers = $this->sendRequest($url, 'HEAD');
$next = false;
if (isset($headers['x-xrds-location'])) {
$url = $this->buildUrl($url, trim($headers['x-xrds-location']));
$next = true;
}
if (isset($headers['content-type'])
&& (strpos($headers['content-type'], 'application/xrds+xml') !== false
|| strpos($headers['content-type'], 'text/xml') !== false)
) {
/* Apparently, some providers return XRDS documents as text/html.
While it is against the spec, allowing this here shouldn't break
compatibility with anything.
---
Found an XRDS document, now let's find the server, and optionally delegate.*/
$content = $this->sendRequest($url, 'GET');
preg_match_all('#<Service.*?>(.*?)</Service>#s', $content, $m);
foreach ($m[1] as $content) {
$content = ' ' . $content; // The space is added, so that strpos doesn't return 0.
// OpenID 2
$ns = preg_quote('http://specs.openid.net/auth/2.0/');
if (preg_match('#<Type>\s*'.$ns.'(server|signon)\s*</Type>#s', $content, $type)) {
if ($type[1] == 'server') {
$result['identifier_select'] = true;
}
preg_match('#<URI.*?>(.*)</URI>#', $content, $server);
preg_match('#<(Local|Canonical)ID>(.*)</\1ID>#', $content, $delegate);
if (empty($server)) {
throw new Exception('No servers found!');
}
// Does the server advertise support for either AX or SREG?
$result['ax'] = (bool) strpos($content, '<Type>http://openid.net/srv/ax/1.0</Type>');
$result['sreg'] = strpos($content, '<Type>http://openid.net/sreg/1.0</Type>') || strpos($content, '<Type>http://openid.net/extensions/sreg/1.1</Type>');
$server = $server[1];
if (isset($delegate[2])) {
$result['identity'] = trim($delegate[2]);
}
$result['url'] = $server;
$result['version'] = 2;
return $result;
}
// OpenID 1.1
$ns = preg_quote('http://openid.net/signon/1.1');
if (preg_match('#<Type>\s*'.$ns.'\s*</Type>#s', $content)) {
preg_match('#<URI.*?>(.*)</URI>#', $content, $server);
preg_match('#<.*?Delegate>(.*)</.*?Delegate>#', $content, $delegate);
if (empty($server)) {
throw new Exception('No servers found!');
}
// AX can be used only with OpenID 2.0, so checking only SREG
$result['sreg'] = strpos($content, '<Type>http://openid.net/sreg/1.0</Type>') || strpos($content, '<Type>http://openid.net/extensions/sreg/1.1</Type>');
$server = $server[1];
if (isset($delegate[1])) {
$result['identity'] = $delegate[1];
}
$result['url'] = $server;
$result['version'] = 1;
return $result;
}
}
$next = true;
$yadis = false;
$url = $originalUrl;
$content = null;
break;
}
if ($next) {
continue;
}
// There are no relevant information in headers, so we search the body.
$content = $this->sendRequest($url, 'GET');
$location = $this->extractHtmlTagValue($content, 'meta', 'http-equiv', 'X-XRDS-Location', 'content');
if ($location) {
$url = $this->buildUrl($url, $location);
continue;
}
}
if (!isset($content)) {
$content = $this->sendRequest($url, 'GET');
}
// At this point, the YADIS Discovery has failed, so we'll switch to openid2 HTML discovery, then fallback to openid 1.1 discovery.
$server = $this->extractHtmlTagValue($content, 'link', 'rel', 'openid2.provider', 'href');
if (!$server) {
// The same with openid 1.1
$server = $this->extractHtmlTagValue($content, 'link', 'rel', 'openid.server', 'href');
$delegate = $this->extractHtmlTagValue($content, 'link', 'rel', 'openid.delegate', 'href');
$version = 1;
} else {
$delegate = $this->extractHtmlTagValue($content, 'link', 'rel', 'openid2.local_id', 'href');
$version = 2;
}
if ($server) {
// We found an OpenID2 OP Endpoint
if ($delegate) {
// We have also found an OP-Local ID.
$result['identity'] = $delegate;
}
$result['url'] = $server;
$result['version'] = $version;
return $result;
}
throw new Exception('No servers found!');
}
throw new Exception('Endless redirection!');
}
/**
* Composes SREG request parameters.
* @return array SREG parameters.
*/
protected function buildSregParams()
{
$params = [];
/* We always use SREG 1.1, even if the server is advertising only support for 1.0.
That's because it's fully backwards compatibile with 1.0, and some providers
advertise 1.0 even if they accept only 1.1. One such provider is myopenid.com */
$params['openid.ns.sreg'] = 'http://openid.net/extensions/sreg/1.1';
if (!empty($this->requiredAttributes)) {
$params['openid.sreg.required'] = [];
foreach ($this->requiredAttributes as $required) {
if (!isset($this->axToSregMap[$required])) {
continue;
}
$params['openid.sreg.required'][] = $this->axToSregMap[$required];
}
$params['openid.sreg.required'] = implode(',', $params['openid.sreg.required']);
}
if (!empty($this->optionalAttributes)) {
$params['openid.sreg.optional'] = [];
foreach ($this->optionalAttributes as $optional) {
if (!isset($this->axToSregMap[$optional])) {
continue;
}
$params['openid.sreg.optional'][] = $this->axToSregMap[$optional];
}
$params['openid.sreg.optional'] = implode(',', $params['openid.sreg.optional']);
}
return $params;
}
/**
* Composes AX request parameters.
* @return array AX parameters.
*/
protected function buildAxParams()
{
$params = [];
if (!empty($this->requiredAttributes) || !empty($this->optionalAttributes)) {
$params['openid.ns.ax'] = 'http://openid.net/srv/ax/1.0';
$params['openid.ax.mode'] = 'fetch_request';
$aliases = [];
$counts = [];
$required = [];
$optional = [];
foreach (['requiredAttributes', 'optionalAttributes'] as $type) {
foreach ($this->$type as $alias => $field) {
if (is_int($alias)) {
$alias = strtr($field, '/', '_');
}
$aliases[$alias] = 'http://axschema.org/' . $field;
if (empty($counts[$alias])) {
$counts[$alias] = 0;
}
$counts[$alias] += 1;
${$type}[] = $alias;
}
}
foreach ($aliases as $alias => $ns) {
$params['openid.ax.type.' . $alias] = $ns;
}
foreach ($counts as $alias => $count) {
if ($count == 1) {
continue;
}
$params['openid.ax.count.' . $alias] = $count;
}
// Don't send empty ax.requied and ax.if_available.
// Google and possibly other providers refuse to support ax when one of these is empty.
if ($required) {
$params['openid.ax.required'] = implode(',', $required);
}
if ($optional) {
$params['openid.ax.if_available'] = implode(',', $optional);
}
}
return $params;
}
/**
* Builds authentication URL for the protocol version 1.
* @param array $serverInfo OpenID server info.
* @return string authentication URL.
*/
protected function buildAuthUrlV1($serverInfo)
{
$returnUrl = $this->getReturnUrl();
/* If we have an openid.delegate that is different from our claimed id,
we need to somehow preserve the claimed id between requests.
The simplest way is to just send it along with the return_to url.*/
if ($serverInfo['identity'] != $this->getClaimedId()) {
$returnUrl .= (strpos($returnUrl, '?') ? '&' : '?') . 'openid.claimed_id=' . $this->getClaimedId();
}
$params = array_merge(
$this->buildSregParams(),
[
'openid.return_to' => $returnUrl,
'openid.mode' => 'checkid_setup',
'openid.identity' => $serverInfo['identity'],
'openid.trust_root' => $this->trustRoot,
]
);
return $this->buildUrl($serverInfo['url'], ['query' => http_build_query($params, '', '&')]);
}
/**
* Builds authentication URL for the protocol version 2.
* @param array $serverInfo OpenID server info.
* @return string authentication URL.
*/
protected function buildAuthUrlV2($serverInfo)
{
$params = [
'openid.ns' => 'http://specs.openid.net/auth/2.0',
'openid.mode' => 'checkid_setup',
'openid.return_to' => $this->getReturnUrl(),
'openid.realm' => $this->getTrustRoot(),
];
if ($serverInfo['ax']) {
$params = array_merge($this->buildAxParams(), $params);
}
if ($serverInfo['sreg']) {
$params = array_merge($this->buildSregParams(), $params);
}
if (!$serverInfo['ax'] && !$serverInfo['sreg']) {
// If OP doesn't advertise either SREG, nor AX, let's send them both in worst case we don't get anything in return.
$params = array_merge($this->buildSregParams(), $this->buildAxParams(), $params);
}
if ($serverInfo['identifier_select']) {
$url = 'http://specs.openid.net/auth/2.0/identifier_select';
$params['openid.identity'] = $url;
$params['openid.claimed_id']= $url;
} else {
$params['openid.identity'] = $serverInfo['identity'];
$params['openid.claimed_id'] = $this->getClaimedId();
}
return $this->buildUrl($serverInfo['url'], ['query' => http_build_query($params, '', '&')]);
}
/**
* Returns authentication URL. Usually, you want to redirect your user to it.
* @param boolean $identifierSelect whether to request OP to select identity for an user in OpenID 2, does not affect OpenID 1.
* @return string the authentication URL.
* @throws Exception on failure.
*/
public function buildAuthUrl($identifierSelect = null)
{
$authUrl = $this->authUrl;
$claimedId = $this->getClaimedId();
if (empty($claimedId)) {
$this->setClaimedId($authUrl);
}
$serverInfo = $this->discover($authUrl);
if ($serverInfo['version'] == 2) {
if ($identifierSelect !== null) {
$serverInfo['identifier_select'] = $identifierSelect;
}
return $this->buildAuthUrlV2($serverInfo);
}
return $this->buildAuthUrlV1($serverInfo);
}
/**
* Performs OpenID verification with the OP.
* @param boolean $validateRequiredAttributes whether to validate required attributes.
* @return boolean whether the verification was successful.
*/
public function validate($validateRequiredAttributes = true)
{
$claimedId = $this->getClaimedId();
if (empty($claimedId)) {
return false;
}
$params = [
'openid.assoc_handle' => $this->data['openid_assoc_handle'],
'openid.signed' => $this->data['openid_signed'],
'openid.sig' => $this->data['openid_sig'],
];
if (isset($this->data['openid_ns'])) {
/* We're dealing with an OpenID 2.0 server, so let's set an ns
Even though we should know location of the endpoint,
we still need to verify it by discovery, so $server is not set here*/
$params['openid.ns'] = 'http://specs.openid.net/auth/2.0';
} elseif (isset($this->data['openid_claimed_id']) && $this->data['openid_claimed_id'] != $this->data['openid_identity']) {
// If it's an OpenID 1 provider, and we've got claimed_id,
// we have to append it to the returnUrl, like authUrlV1 does.
$this->returnUrl .= (strpos($this->returnUrl, '?') ? '&' : '?') . 'openid.claimed_id=' . $claimedId;
}
if ($this->data['openid_return_to'] != $this->returnUrl) {
// The return_to url must match the url of current request.
return false;
}
$serverInfo = $this->discover($claimedId);
foreach (explode(',', $this->data['openid_signed']) as $item) {
$value = $this->data['openid_' . str_replace('.', '_', $item)];
$params['openid.' . $item] = $value;
}
$params['openid.mode'] = 'check_authentication';
$response = $this->sendRequest($serverInfo['url'], 'POST', $params);
if (preg_match('/is_valid\s*:\s*true/i', $response)) {
if ($validateRequiredAttributes) {
return $this->validateRequiredAttributes();
} else {
return true;
}
} else {
return false;
}
}
/**
* Checks if all required attributes are present in the server response.
* @return boolean whether all required attributes are present.
*/
protected function validateRequiredAttributes()
{
if (!empty($this->requiredAttributes)) {
$attributes = $this->fetchAttributes();
foreach ($this->requiredAttributes as $openIdAttributeName) {
if (!isset($attributes[$openIdAttributeName])) {
return false;
}
}
}
return true;
}
/**
* Gets AX attributes provided by OP.
* @return array array of attributes.
*/
protected function fetchAxAttributes()
{
$alias = null;
if (isset($this->data['openid_ns_ax']) && $this->data['openid_ns_ax'] != 'http://openid.net/srv/ax/1.0') {
// It's the most likely case, so we'll check it before
$alias = 'ax';
} else {
// 'ax' prefix is either undefined, or points to another extension, so we search for another prefix
foreach ($this->data as $key => $value) {
if (substr($key, 0, strlen('openid_ns_')) == 'openid_ns_' && $value == 'http://openid.net/srv/ax/1.0') {
$alias = substr($key, strlen('openid_ns_'));
break;
}
}
}
if (!$alias) {
// An alias for AX schema has not been found, so there is no AX data in the OP's response
return [];
}
$attributes = [];
foreach ($this->data as $key => $value) {
$keyMatch = 'openid_' . $alias . '_value_';
if (substr($key, 0, strlen($keyMatch)) != $keyMatch) {
continue;
}
$key = substr($key, strlen($keyMatch));
if (!isset($this->data['openid_' . $alias . '_type_' . $key])) {
/* OP is breaking the spec by returning a field without
associated ns. This shouldn't happen, but it's better
to check, than cause an E_NOTICE.*/
continue;
}
$key = substr($this->data['openid_' . $alias . '_type_' . $key], strlen('http://axschema.org/'));
$attributes[$key] = $value;
}
return $attributes;
}
/**
* Gets SREG attributes provided by OP. SREG names will be mapped to AX names.
* @return array array of attributes with keys being the AX schema names, e.g. 'contact/email'
*/
protected function fetchSregAttributes()
{
$attributes = [];
$sregToAx = array_flip($this->axToSregMap);
foreach ($this->data as $key => $value) {
$keyMatch = 'openid_sreg_';
if (substr($key, 0, strlen($keyMatch)) != $keyMatch) {
continue;
}
$key = substr($key, strlen($keyMatch));
if (!isset($sregToAx[$key])) {
// The field name isn't part of the SREG spec, so we ignore it.
continue;
}
$attributes[$sregToAx[$key]] = $value;
}
return $attributes;
}
/**
* Gets AX/SREG attributes provided by OP. Should be used only after successful validation.
* Note that it does not guarantee that any of the required/optional parameters will be present,
* or that there will be no other attributes besides those specified.
* In other words. OP may provide whatever information it wants to.
* SREG names will be mapped to AX names.
* @return array array of attributes with keys being the AX schema names, e.g. 'contact/email'
* @see http://www.axschema.org/types/
*/
public function fetchAttributes()
{
if (isset($this->data['openid_ns']) && $this->data['openid_ns'] == 'http://specs.openid.net/auth/2.0') {
// OpenID 2.0
// We search for both AX and SREG attributes, with AX taking precedence.
return array_merge($this->fetchSregAttributes(), $this->fetchAxAttributes());
}
return $this->fetchSregAttributes();
}
/**
* @inheritdoc
*/
protected function initUserAttributes()
{
return array_merge(['id' => $this->getClaimedId()], $this->fetchAttributes());
}
}

83
extensions/yii/authclient/README.md

@ -0,0 +1,83 @@
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 provides the ability of the authentication via external credentials providers.
It covers OpenID, OAuth1 and OAuth2 protocols.
You need to setup auth client collection application component:
```
'components' => [
'authClientCollection' => [
'class' => 'yii\authclient\Collection',
'clients' => [
'google' => [
'class' => 'yii\authclient\clients\GoogleOpenId'
],
'facebook' => [
'class' => 'yii\authclient\clients\Facebook',
'clientId' => 'facebook_client_id',
'clientSecret' => 'facebook_client_secret',
],
],
]
...
]
```
Then you need to apply [[yii\authclient\AuthAction]] to some of your web controllers:
```
class SiteController extends Controller
{
public function actions()
{
return [
'auth' => [
'class' => 'yii\authclient\AuthAction',
'successCallback' => [$this, 'successCallback'],
],
]
}
public function successCallback($client)
{
$atributes = $client->getUserAttributes();
// user login or signup comes here
}
}
```
You may use [[yii\authclient\widgets\Choice]] to compose auth client selection:
```
<?= yii\authclient\Choice::widget([
'baseAuthUrl' => ['site/auth']
]); ?>
```

82
extensions/yii/authclient/clients/Facebook.php

@ -0,0 +1,82 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\clients;
use yii\authclient\OAuth2;
/**
* Facebook allows authentication via Facebook OAuth.
* In order to use Facebook OAuth you must register your application at [[https://developers.facebook.com/apps]].
*
* Example application configuration:
*
* ~~~
* 'components' => [
* 'authClientCollection' => [
* 'class' => 'yii\authclient\Collection',
* 'clients' => [
* 'facebook' => [
* 'class' => 'yii\authclient\clients\Facebook',
* 'clientId' => 'facebook_client_id',
* 'clientSecret' => 'facebook_client_secret',
* ],
* ],
* ]
* ...
* ]
* ~~~
*
* @see https://developers.facebook.com/apps
* @see http://developers.facebook.com/docs/reference/api
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class Facebook extends OAuth2
{
/**
* @inheritdoc
*/
public $authUrl = 'https://www.facebook.com/dialog/oauth';
/**
* @inheritdoc
*/
public $tokenUrl = 'https://graph.facebook.com/oauth/access_token';
/**
* @inheritdoc
*/
public $apiBaseUrl = 'https://graph.facebook.com';
/**
* @inheritdoc
*/
public $scope = 'email';
/**
* @inheritdoc
*/
protected function initUserAttributes()
{
return $this->api('me', 'GET');
}
/**
* @inheritdoc
*/
protected function defaultName()
{
return 'facebook';
}
/**
* @inheritdoc
*/
protected function defaultTitle()
{
return 'Facebook';
}
}

76
extensions/yii/authclient/clients/GitHub.php

@ -0,0 +1,76 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\clients;
use yii\authclient\OAuth2;
/**
* GitHub allows authentication via GitHub OAuth.
* In order to use GitHub OAuth you must register your application at [[https://github.com/settings/applications/new]].
*
* Example application configuration:
*
* ~~~
* 'components' => [
* 'authClientCollection' => [
* 'class' => 'yii\authclient\Collection',
* 'clients' => [
* 'github' => [
* 'class' => 'yii\authclient\clients\GitHub',
* 'clientId' => 'github_client_id',
* 'clientSecret' => 'github_client_secret',
* ],
* ],
* ]
* ...
* ]
* ~~~
*
* @see http://developer.github.com/v3/oauth/
* @see https://github.com/settings/applications/new
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class GitHub extends OAuth2
{
/**
* @inheritdoc
*/
public $authUrl = 'https://github.com/login/oauth/authorize';
/**
* @inheritdoc
*/
public $tokenUrl = 'https://github.com/login/oauth/access_token';
/**
* @inheritdoc
*/
public $apiBaseUrl = 'https://api.github.com';
/**
* @inheritdoc
*/
public function init()
{
parent::init();
if ($this->scope === null) {
$this->scope = implode(' ', [
'user',
'user:email',
]);
}
}
/**
* @inheritdoc
*/
protected function initUserAttributes()
{
return $this->api('user', 'GET');
}
}

92
extensions/yii/authclient/clients/GoogleOAuth.php

@ -0,0 +1,92 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\clients;
use yii\authclient\OAuth2;
/**
* GoogleOAuth allows authentication via Google OAuth.
* In order to use Google OAuth you must register your application at [[https://code.google.com/apis/console#access]].
*
* Example application configuration:
*
* ~~~
* 'components' => [
* 'authClientCollection' => [
* 'class' => 'yii\authclient\Collection',
* 'clients' => [
* 'google' => [
* 'class' => 'yii\authclient\clients\GoogleOAuth',
* 'clientId' => 'google_client_id',
* 'clientSecret' => 'google_client_secret',
* ],
* ],
* ]
* ...
* ]
* ~~~
*
* @see https://code.google.com/apis/console#access
* @see https://developers.google.com/google-apps/contacts/v3/
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class GoogleOAuth extends OAuth2
{
/**
* @inheritdoc
*/
public $authUrl = 'https://accounts.google.com/o/oauth2/auth';
/**
* @inheritdoc
*/
public $tokenUrl = 'https://accounts.google.com/o/oauth2/token';
/**
* @inheritdoc
*/
public $apiBaseUrl = 'https://www.googleapis.com/oauth2/v1';
/**
* @inheritdoc
*/
public function init()
{
parent::init();
if ($this->scope === null) {
$this->scope = implode(' ', [
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email',
]);
}
}
/**
* @inheritdoc
*/
protected function initUserAttributes()
{
return $this->api('userinfo', 'GET');
}
/**
* @inheritdoc
*/
protected function defaultName()
{
return 'google';
}
/**
* @inheritdoc
*/
protected function defaultTitle()
{
return 'Google';
}
}

90
extensions/yii/authclient/clients/GoogleOpenId.php

@ -0,0 +1,90 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\clients;
use yii\authclient\OpenId;
/**
* GoogleOpenId allows authentication via Google OpenId.
* Unlike Google OAuth you do not need to register your application anywhere in order to use Google OpenId.
*
* Example application configuration:
*
* ~~~
* 'components' => [
* 'authClientCollection' => [
* 'class' => 'yii\authclient\Collection',
* 'clients' => [
* 'google' => [
* 'class' => 'yii\authclient\clients\GoogleOpenId'
* ],
* ],
* ]
* ...
* ]
* ~~~
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class GoogleOpenId extends OpenId
{
/**
* @inheritdoc
*/
public $authUrl = 'https://www.google.com/accounts/o8/id';
/**
* @inheritdoc
*/
public $requiredAttributes = [
'namePerson/first',
'namePerson/last',
'contact/email',
'pref/language',
];
/**
* @inheritdoc
*/
protected function defaultNormalizeUserAttributeMap()
{
return [
'first_name' => 'namePerson/first',
'last_name' => 'namePerson/last',
'email' => 'contact/email',
'language' => 'pref/language',
];
}
/**
* @inheritdoc
*/
protected function defaultViewOptions()
{
return [
'popupWidth' => 880,
'popupHeight' => 520,
];
}
/**
* @inheritdoc
*/
protected function defaultName()
{
return 'google';
}
/**
* @inheritdoc
*/
protected function defaultTitle()
{
return 'Google';
}
}

167
extensions/yii/authclient/clients/LinkedIn.php

@ -0,0 +1,167 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\clients;
use yii\authclient\OAuth2;
use yii\web\HttpException;
use Yii;
/**
* LinkedIn allows authentication via LinkedIn OAuth.
* In order to use linkedIn OAuth you must register your application at [[https://www.linkedin.com/secure/developer]].
*
* Example application configuration:
*
* ~~~
* 'components' => [
* 'authClientCollection' => [
* 'class' => 'yii\authclient\Collection',
* 'clients' => [
* 'linkedin' => [
* 'class' => 'yii\authclient\clients\LinkedIn',
* 'clientId' => 'linkedin_client_id',
* 'clientSecret' => 'linkedin_client_secret',
* ],
* ],
* ]
* ...
* ]
* ~~~
*
* @see http://developer.linkedin.com/documents/authentication
* @see https://www.linkedin.com/secure/developer
* @see http://developer.linkedin.com/apis
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class LinkedIn extends OAuth2
{
/**
* @inheritdoc
*/
public $authUrl = 'https://www.linkedin.com/uas/oauth2/authorization';
/**
* @inheritdoc
*/
public $tokenUrl = 'https://www.linkedin.com/uas/oauth2/accessToken';
/**
* @inheritdoc
*/
public $apiBaseUrl = 'https://api.linkedin.com/v1';
/**
* @inheritdoc
*/
public function init()
{
parent::init();
if ($this->scope === null) {
$this->scope = implode(' ', [
'r_basicprofile',
'r_emailaddress',
]);
}
}
/**
* @inheritdoc
*/
protected function defaultNormalizeUserAttributeMap()
{
return [
'email' => 'email-address',
'first_name' => 'first-name',
'last_name' => 'last-name',
];
}
/**
* @inheritdoc
*/
protected function initUserAttributes()
{
$attributeNames = [
'id',
'email-address',
'first-name',
'last-name',
'public-profile-url',
];
return $this->api('people/~:(' . implode(',', $attributeNames) . ')', 'GET');
}
/**
* @inheritdoc
*/
public function buildAuthUrl(array $params = [])
{
$authState = $this->generateAuthState();
$this->setState('authState', $authState);
$params['state'] = $authState;
return parent::buildAuthUrl($params);
}
/**
* @inheritdoc
*/
public function fetchAccessToken($authCode, array $params = [])
{
$authState = $this->getState('authState');
if (!isset($_REQUEST['state']) || empty($authState) || strcmp($_REQUEST['state'], $authState) !== 0) {
throw new HttpException(400, 'Invalid auth state parameter.');
} else {
$this->removeState('authState');
}
return parent::fetchAccessToken($authCode, $params);
}
/**
* @inheritdoc
*/
protected function apiInternal($accessToken, $url, $method, array $params)
{
$params['oauth2_access_token'] = $accessToken->getToken();
return $this->sendRequest($method, $url, $params);
}
/**
* @inheritdoc
*/
protected function defaultReturnUrl()
{
$params = $_GET;
unset($params['code']);
unset($params['state']);
return Yii::$app->getUrlManager()->createAbsoluteUrl(Yii::$app->controller->getRoute(), $params);
}
/**
* Generates the auth state value.
* @return string auth state value.
*/
protected function generateAuthState() {
return sha1(uniqid(get_class($this), true));
}
/**
* @inheritdoc
*/
protected function defaultName()
{
return 'linkedin';
}
/**
* @inheritdoc
*/
protected function defaultTitle()
{
return 'LinkedIn';
}
}

90
extensions/yii/authclient/clients/Twitter.php

@ -0,0 +1,90 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\clients;
use yii\authclient\OAuth1;
/**
* Twitter allows authentication via Twitter OAuth.
* In order to use Twitter OAuth you must register your application at [[https://dev.twitter.com/apps/new]].
*
* Example application configuration:
*
* ~~~
* 'components' => [
* 'authClientCollection' => [
* 'class' => 'yii\authclient\Collection',
* 'clients' => [
* 'twitter' => [
* 'class' => 'yii\authclient\clients\Twitter',
* 'consumerKey' => 'twitter_consumer_key',
* 'consumerSecret' => 'twitter_consumer_secret',
* ],
* ],
* ]
* ...
* ]
* ~~~
*
* @see https://dev.twitter.com/apps/new
* @see https://dev.twitter.com/docs/api
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class Twitter extends OAuth1
{
/**
* @inheritdoc
*/
public $authUrl = 'https://api.twitter.com/oauth/authorize';
/**
* @inheritdoc
*/
public $requestTokenUrl = 'https://api.twitter.com/oauth/request_token';
/**
* @inheritdoc
*/
public $requestTokenMethod = 'POST';
/**
* @inheritdoc
*/
public $accessTokenUrl = 'https://api.twitter.com/oauth/access_token';
/**
* @inheritdoc
*/
public $accessTokenMethod = 'POST';
/**
* @inheritdoc
*/
public $apiBaseUrl = 'https://api.twitter.com/1.1';
/**
* @inheritdoc
*/
protected function initUserAttributes()
{
return $this->api('account/verify_credentials.json', 'GET');
}
/**
* @inheritdoc
*/
protected function defaultName()
{
return 'twitter';
}
/**
* @inheritdoc
*/
protected function defaultTitle()
{
return 'Twitter';
}
}

90
extensions/yii/authclient/clients/YandexOAuth.php

@ -0,0 +1,90 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\clients;
use yii\authclient\OAuth2;
/**
* YandexOAuth allows authentication via Yandex OAuth.
* In order to use Yandex OAuth you must register your application at [[https://oauth.yandex.ru/client/new]].
*
* Example application configuration:
*
* ~~~
* 'components' => [
* 'authClientCollection' => [
* 'class' => 'yii\authclient\Collection',
* 'clients' => [
* 'yandex' => [
* 'class' => 'yii\authclient\clients\YandexOAuth',
* 'clientId' => 'yandex_client_id',
* 'clientSecret' => 'yandex_client_secret',
* ],
* ],
* ]
* ...
* ]
* ~~~
*
* @see https://oauth.yandex.ru/client/new
* @see http://api.yandex.ru/login/doc/dg/reference/response.xml
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class YandexOAuth extends OAuth2
{
/**
* @inheritdoc
*/
public $authUrl = 'https://oauth.yandex.ru/authorize';
/**
* @inheritdoc
*/
public $tokenUrl = 'https://oauth.yandex.ru/token';
/**
* @inheritdoc
*/
public $apiBaseUrl = 'https://login.yandex.ru';
/**
* @inheritdoc
*/
protected function initUserAttributes()
{
return $this->api('info', 'GET');
}
/**
* @inheritdoc
*/
protected function apiInternal($accessToken, $url, $method, array $params)
{
if (!isset($params['format'])) {
$params['format'] = 'json';
}
$params['oauth_token'] = $accessToken->getToken();
return $this->sendRequest($method, $url, $params);
}
/**
* @inheritdoc
*/
protected function defaultName()
{
return 'yandex';
}
/**
* @inheritdoc
*/
protected function defaultTitle()
{
return 'Yandex';
}
}

86
extensions/yii/authclient/clients/YandexOpenId.php

@ -0,0 +1,86 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\clients;
use yii\authclient\OpenId;
/**
* YandexOpenId allows authentication via Yandex OpenId.
* Unlike Yandex OAuth you do not need to register your application anywhere in order to use Yandex OpenId.
*
* Example application configuration:
*
* ~~~
* 'components' => [
* 'authClientCollection' => [
* 'class' => 'yii\authclient\Collection',
* 'clients' => [
* 'yandex' => [
* 'class' => 'yii\authclient\clients\YandexOpenId'
* ],
* ],
* ]
* ...
* ]
* ~~~
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class YandexOpenId extends OpenId
{
/**
* @inheritdoc
*/
public $authUrl = 'http://openid.yandex.ru';
/**
* @inheritdoc
*/
public $requiredAttributes = [
'namePerson',
'contact/email',
];
/**
* @inheritdoc
*/
protected function defaultNormalizeUserAttributeMap()
{
return [
'name' => 'namePerson',
'email' => 'contact/email',
];
}
/**
* @inheritdoc
*/
protected function defaultViewOptions()
{
return [
'popupWidth' => 900,
'popupHeight' => 550,
];
}
/**
* @inheritdoc
*/
protected function defaultName()
{
return 'yandex';
}
/**
* @inheritdoc
*/
protected function defaultTitle()
{
return 'Yandex';
}
}

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"
}

51
extensions/yii/authclient/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\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/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\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/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\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/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\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);
}
}

38
extensions/yii/authclient/views/redirect.php

@ -0,0 +1,38 @@
<?php
use yii\helpers\Html;
use yii\helpers\Json;
/* @var $this \yii\base\View */
/* @var $url string */
/* @var $enforceRedirect boolean */
$redirectJavaScript = <<<EOL
function popupWindowRedirect(url, enforceRedirect = true) {
if (window.opener) {
window.close();
if (enforceRedirect) {
window.opener.location = url;
}
} else {
window.location = url;
}
}
EOL;
$redirectJavaScript .= 'popupWindowRedirect(' . Json::encode($url) . ', ' . Json::encode($enforceRedirect) . ');';
?>
<!DOCTYPE html>
<html>
<head>
<?= Html::script($redirectJavaScript); ?>
</head>
<body>
<h2 id="title" style="display:none;">Redirecting back to the &quot;<?= Yii::$app->name; ?>&quot;...</h2>
<h3 id="link"><a href="<?= $url; ?>">Click here to return to the &quot;<?= Yii::$app->name; ?>&quot;.</a></h3>
<script type="text/javascript">
document.getElementById('title').style.display = '';
document.getElementById('link').style.display = 'none';
</script>
</body>
</html>

229
extensions/yii/authclient/widgets/Choice.php

@ -0,0 +1,229 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\widgets;
use yii\base\Widget;
use Yii;
use yii\helpers\Html;
use yii\authclient\ClientInterface;
/**
* Choice prints buttons for authentication via various auth clients.
* By default this widget relies on presence of [[\yii\authclient\Collection]] among application components
* to get auth clients information.
*
* Example:
* ~~~
* <?= yii\authclient\Choice::widget([
* 'baseAuthUrl' => ['site/auth']
* ]); ?>
* ~~~
*
* You can customize the widget appearance by using [[beginWidget()]] and [[endWidget()]] syntax
* along with using method {@link clientLink()} or {@link createClientUrl()}.
* For example:
*
* ~~~
* <?php $authChoice = yii\authclient\Choice::beginWidget([
* 'baseAuthUrl' => ['site/auth']
* ]); ?>
* <ul>
* <?php foreach ($authChoice->getClients() as $client): ?>
* <li><?= $authChoice->clientLink($client); ?></li>
* <?php endforeach; ?>
* </ul>
* <?php yii\authclient\Choice::endWidget(); ?>
* ~~~
*
* @see \yii\authclient\AuthAction
*
* @property ClientInterface[] $providers auth providers list.
* @property array $baseAuthUrl configuration for the external services base authentication URL.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class Choice extends Widget
{
/**
* @var ClientInterface[] auth providers list.
*/
private $_clients;
/**
* @var string name of the auth client collection application component.
* This component will be used to fetch {@link services} value if it is not set.
*/
public $clientCollection = 'authClientCollection';
/**
* @var array configuration for the external clients base authentication URL.
*/
private $_baseAuthUrl;
/**
* @var string name of the GET param , which should be used to passed auth client id to URL
* defined by {@link baseAuthUrl}.
*/
public $clientIdGetParamName = 'authclient';
/**
* @var array the HTML attributes that should be rendered in the div HTML tag representing the container element.
*/
public $mainContainerHtmlOptions = [
'class' => 'auth-clients'
];
/**
* @var boolean indicates if popup window should be used instead of direct links.
*/
public $popupMode = true;
/**
* @var boolean indicates if widget content, should be rendered automatically.
* Note: this value automatically set to 'false' at the first call of [[createProviderUrl()]]
*/
public $autoRender = true;
/**
* @param ClientInterface[] $clients auth providers
*/
public function setClients(array $clients)
{
$this->_clients = $clients;
}
/**
* @return ClientInterface[] auth providers
*/
public function getClients()
{
if ($this->_clients === null) {
$this->_clients = $this->defaultClients();
}
return $this->_clients;
}
/**
* @param array $baseAuthUrl base auth URL configuration.
*/
public function setBaseAuthUrl(array $baseAuthUrl)
{
$this->_baseAuthUrl = $baseAuthUrl;
}
/**
* @return array base auth URL configuration.
*/
public function getBaseAuthUrl()
{
if (!is_array($this->_baseAuthUrl)) {
$this->_baseAuthUrl = $this->defaultBaseAuthUrl();
}
return $this->_baseAuthUrl;
}
/**
* Returns default auth clients list.
* @return ClientInterface[] auth clients list.
*/
protected function defaultClients()
{
/** @var $collection \yii\authclient\Collection */
$collection = Yii::$app->getComponent($this->clientCollection);
return $collection->getClients();
}
/**
* Composes default base auth URL configuration.
* @return array base auth URL configuration.
*/
protected function defaultBaseAuthUrl()
{
$baseAuthUrl = [
Yii::$app->controller->getRoute()
];
$params = $_GET;
unset($params[$this->clientIdGetParamName]);
$baseAuthUrl = array_merge($baseAuthUrl, $params);
return $baseAuthUrl;
}
/**
* Outputs client auth link.
* @param ClientInterface $client external auth client instance.
* @param string $text link text, if not set - default value will be generated.
* @param array $htmlOptions link HTML options.
*/
public function clientLink($client, $text = null, array $htmlOptions = [])
{
if ($text === null) {
$text = Html::tag('span', '', ['class' => 'auth-icon ' . $client->getName()]);
$text .= Html::tag('span', $client->getTitle(), ['class' => 'auth-title']);
}
if (!array_key_exists('class', $htmlOptions)) {
$htmlOptions['class'] = 'auth-link ' . $client->getName();
}
if ($this->popupMode) {
$viewOptions = $client->getViewOptions();
if (isset($viewOptions['popupWidth'])) {
$htmlOptions['data-popup-width'] = $viewOptions['popupWidth'];
}
if (isset($viewOptions['popupHeight'])) {
$htmlOptions['data-popup-height'] = $viewOptions['popupHeight'];
}
}
echo Html::a($text, $this->createClientUrl($client), $htmlOptions);
}
/**
* Composes client auth URL.
* @param ClientInterface $provider external auth client instance.
* @return string auth URL.
*/
public function createClientUrl($provider)
{
$this->autoRender = false;
$url = $this->getBaseAuthUrl();
$url[$this->clientIdGetParamName] = $provider->getId();
return Html::url($url);
}
/**
* Renders the main content, which includes all external services links.
*/
protected function renderMainContent()
{
echo Html::beginTag('ul', ['class' => 'auth-clients clear']);
foreach ($this->getClients() as $externalService) {
echo Html::beginTag('li', ['class' => 'auth-client']);
$this->clientLink($externalService);
echo Html::endTag('li');
}
echo Html::endTag('ul');
}
/**
* Initializes the widget.
*/
public function init()
{
if ($this->popupMode) {
$view = Yii::$app->getView();
ChoiceAsset::register($view);
$view->registerJs("\$('#" . $this->getId() . "').authchoice();");
}
$this->mainContainerHtmlOptions['id'] = $this->getId();
echo Html::beginTag('div', $this->mainContainerHtmlOptions);
}
/**
* Runs the widget.
*/
public function run()
{
if ($this->autoRender) {
$this->renderMainContent();
}
echo Html::endTag('div');
}
}

30
extensions/yii/authclient/widgets/ChoiceAsset.php

@ -0,0 +1,30 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\authclient\widgets;
use yii\web\AssetBundle;
/**
* ChoiceAsset is an asset bundle for [[Choice]] widget.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class ChoiceAsset extends AssetBundle
{
public $sourcePath = '@yii/authclient/widgets/assets';
public $js = [
'authchoice.js',
];
public $css = [
'authchoice.css',
];
public $depends = [
'yii\web\YiiAsset',
];
}

82
extensions/yii/authclient/widgets/assets/authchoice.css

@ -0,0 +1,82 @@
.clients {
overflow:auto;
}
.auth-icon {
display: block;
width: 32px;
height: 32px;
background: url(authchoice.png) no-repeat;
}
.auth-icon.google,
.auth-icon.google_openid,
.auth-icon.google_oauth {
background-position: 0 -34px;
}
.auth-icon.twitter {
background-position: 0 -68px;
}
.auth-icon.yandex,
.auth-icon.yandex_openid,
.auth-icon.yandex_oauth {
background-position: 0 -102px;
}
.auth-icon.vkontakte {
background-position: 0 -136px;
}
.auth-icon.facebook {
background-position: 0 -170px;
}
.auth-icon.mailru {
background-position: 0 -204px;
}
.auth-icon.moikrug {
background-position: 0 -238px;
}
.auth-icon.odnoklassniki {
background-position: 0 -272px;
}
.auth-icon.linkedin {
background-position: 0 -306px;
}
.auth-icon.github {
background-position: 0 -340px;
}
.auth-icon.live {
background-position: 0 -372px;
}
.auth-link:hover .auth-icon i,
.auth-link:focus .auth-icon i {
display: block;
width: 32px;
height: 32px;
background: url(authchoice.png) 0 0 no-repeat;
}
.auth-clients {
margin: 0 0 1em;
list-style: none;
overflow: auto;
}
.auth-client {
float: left;
margin: 0 1em 0 0;
}
.auth-clients .auth-client .auth-link {
display: block;
width: 58px;
}
.auth-client .auth-link .auth-icon {
margin: 0 auto;
}
.auth-client .auth-link .auth-title {
display: block;
margin-top: 0.4em;
text-align: center;
}

68
extensions/yii/authclient/widgets/assets/authchoice.js

@ -0,0 +1,68 @@
/**
* Yii auth choice widget.
*
* This is the JavaScript widget used by the yii\authclient\widgets\Choice widget.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
jQuery(function($) {
$.fn.authchoice = function(options) {
options = $.extend({
popup: {
resizable: 'yes',
scrollbars: 'no',
toolbar: 'no',
menubar: 'no',
location: 'no',
directories: 'no',
status: 'yes',
width: 450,
height: 380
}
}, options);
return this.each(function() {
var $container = $(this);
$container.find('a').on('click', function(e) {
e.preventDefault();
var authChoicePopup = null;
if (authChoicePopup = $container.data('authChoicePopup')) {
authChoicePopup.close();
}
var url = this.href;
var popupOptions = options.popup;
var localPopupWidth = this.getAttribute('data-popup-width');
if (localPopupWidth) {
popupOptions.width = localPopupWidth;
}
var localPopupHeight = this.getAttribute('data-popup-height');
if (localPopupWidth) {
popupOptions.height = localPopupHeight;
}
popupOptions.left = (window.screen.width - options.popup.width) / 2;
popupOptions.top = (window.screen.height - options.popup.height) / 2;
var popupFeatureParts = [];
for (var propName in popupOptions) {
popupFeatureParts.push(propName + '=' + popupOptions[propName]);
}
var popupFeature = popupFeatureParts.join(',');
authChoicePopup = window.open(url, 'yii_auth_choice', popupFeature);
authChoicePopup.focus();
$container.data('authChoicePopup', authChoicePopup);
});
});
};
});

BIN
extensions/yii/authclient/widgets/assets/authchoice.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

68
tests/unit/extensions/authclient/AuthActionTest.php

@ -0,0 +1,68 @@
<?php
namespace yiiunit\extensions\authclient;
use yii\authclient\AuthAction;
class AuthActionTest extends TestCase
{
protected function setUp()
{
$config = [
'components' => [
'user' => [
'identityClass' => '\yii\web\IdentityInterface'
],
'request' => [
'hostInfo' => 'http://testdomain.com',
'scriptUrl' => '/index.php',
],
]
];
$this->mockApplication($config, '\yii\web\Application');
}
public function testSetGet()
{
$action = new AuthAction(null, null);
$successUrl = 'http://test.success.url';
$action->setSuccessUrl($successUrl);
$this->assertEquals($successUrl, $action->getSuccessUrl(), 'Unable to setup success URL!');
$cancelUrl = 'http://test.cancel.url';
$action->setCancelUrl($cancelUrl);
$this->assertEquals($cancelUrl, $action->getCancelUrl(), 'Unable to setup cancel URL!');
}
/**
* @depends testSetGet
*/
public function testGetDefaultSuccessUrl()
{
$action = new AuthAction(null, null);
$this->assertNotEmpty($action->getSuccessUrl(), 'Unable to get default success URL!');
}
/**
* @depends testSetGet
*/
public function testGetDefaultCancelUrl()
{
$action = new AuthAction(null, null);
$this->assertNotEmpty($action->getSuccessUrl(), 'Unable to get default cancel URL!');
}
public function testRedirect()
{
$action = new AuthAction(null, null);
$url = 'http://test.url';
$response = $action->redirect($url, true);
$this->assertContains($url, $response->content);
}
}

82
tests/unit/extensions/authclient/BaseClientTest.php

@ -0,0 +1,82 @@
<?php
namespace yiiunit\extensions\authclient;
use yii\authclient\BaseClient;
class BaseClientTest extends TestCase
{
public function testSetGet()
{
$client = new Client();
$id = 'test_id';
$client->setId($id);
$this->assertEquals($id, $client->getId(), 'Unable to setup id!');
$name = 'test_name';
$client->setName($name);
$this->assertEquals($name, $client->getName(), 'Unable to setup name!');
$title = 'test_title';
$client->setTitle($title);
$this->assertEquals($title, $client->getTitle(), 'Unable to setup title!');
$userAttributes = [
'attribute1' => 'value1',
'attribute2' => 'value2',
];
$client->setUserAttributes($userAttributes);
$this->assertEquals($userAttributes, $client->getUserAttributes(), 'Unable to setup user attributes!');
$normalizeUserAttributeMap = [
'name' => 'some/name',
'email' => 'some/email',
];
$client->setNormalizeUserAttributeMap($normalizeUserAttributeMap);
$this->assertEquals($normalizeUserAttributeMap, $client->getNormalizeUserAttributeMap(), 'Unable to setup normalize user attribute map!');
$viewOptions = [
'option1' => 'value1',
'option2' => 'value2',
];
$client->setViewOptions($viewOptions);
$this->assertEquals($viewOptions, $client->getViewOptions(), 'Unable to setup view options!');
}
public function testGetDefaults()
{
$client = new Client();
$this->assertNotEmpty($client->getName(), 'Unable to get default name!');
$this->assertNotEmpty($client->getTitle(), 'Unable to get default title!');
$this->assertNotNull($client->getViewOptions(), 'Unable to get default view options!');
$this->assertNotNull($client->getNormalizeUserAttributeMap(), 'Unable to get default normalize user attribute map!');
}
/**
* @depends testSetGet
*/
public function testNormalizeUserAttributes()
{
$client = new Client();
$normalizeUserAttributeMap = [
'raw/name' => 'name',
'raw/email' => 'email',
];
$client->setNormalizeUserAttributeMap($normalizeUserAttributeMap);
$rawUserAttributes = [
'raw/name' => 'name value',
'raw/email' => 'email value',
];
$client->setUserAttributes($rawUserAttributes);
$normalizedUserAttributes = $client->getUserAttributes();
$expectedNormalizedUserAttributes = array_combine(array_keys($normalizeUserAttributeMap), array_values($rawUserAttributes));
$this->assertEquals($expectedNormalizedUserAttributes, $normalizedUserAttributes);
}
}
class Client extends BaseClient
{
}

251
tests/unit/extensions/authclient/BaseOAuthTest.php

@ -0,0 +1,251 @@
<?php
namespace yiiunit\extensions\authclient;
use yii\authclient\signature\PlainText;
use yii\authclient\OAuthToken;
use yiiunit\extensions\authclient\TestCase;
use yii\authclient\BaseOAuth;
class BaseOAuthTest extends TestCase
{
/**
* Creates test OAuth client instance.
* @return BaseOAuth oauth client.
*/
protected function createOAuthClient()
{
$oauthClient = $this->getMock(BaseOAuth::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 BaseOAuth $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 OAuthToken();
$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\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 OAuthToken();
$accessToken->setToken('test_access_token');
$accessToken->setExpireDuration(1000);
$oauthClient->setAccessToken($accessToken);
$oauthClient->apiBaseUrl = $apiBaseUrl;
$this->assertEquals($expectedApiFullUrl, $oauthClient->api($apiSubUrl));
}
}

85
tests/unit/extensions/authclient/CollectionTest.php

@ -0,0 +1,85 @@
<?php
namespace yiiunit\extensions\authclient;
use yii\authclient\Collection;
use yii\authclient\BaseClient;
use yiiunit\extensions\authclient\TestCase;
class CollectionTest extends TestCase
{
// Tests :
public function testSetGet()
{
$collection = new Collection();
$clients = [
'testClient1' => new TestClient(),
'testClient2' => new TestClient(),
];
$collection->setClients($clients);
$this->assertEquals($clients, $collection->getClients(), 'Unable to setup clients!');
}
/**
* @depends testSetGet
*/
public function testGetProviderById()
{
$collection = new Collection();
$clientId = 'testClientId';
$client = new TestClient();
$clients = [
$clientId => $client
];
$collection->setClients($clients);
$this->assertEquals($client, $collection->getClient($clientId), 'Unable to get client by id!');
}
/**
* @depends testGetProviderById
*/
public function testCreateProvider()
{
$collection = new Collection();
$clientId = 'testClientId';
$clientClassName = TestClient::className();
$clients = [
$clientId => [
'class' => $clientClassName
]
];
$collection->setClients($clients);
$provider = $collection->getClient($clientId);
$this->assertTrue(is_object($provider), 'Unable to create client by config!');
$this->assertTrue(is_a($provider, $clientClassName), 'Client has wrong class name!');
}
/**
* @depends testSetGet
*/
public function testHasProvider()
{
$collection = new Collection();
$clientName = 'testClientName';
$clients = [
$clientName => [
'class' => 'TestClient1'
],
];
$collection->setClients($clients);
$this->assertTrue($collection->hasClient($clientName), 'Existing client check fails!');
$this->assertFalse($collection->hasClient('unExistingClientName'), 'Not existing client check fails!');
}
}
class TestClient extends BaseClient
{
}

109
tests/unit/extensions/authclient/OAuth1Test.php

@ -0,0 +1,109 @@
<?php
namespace yiiunit\extensions\authclient\oauth;
use yii\authclient\OAuth1;
use yii\authclient\signature\PlainText;
use yii\authclient\OAuthToken;
use yiiunit\extensions\authclient\TestCase;
class OAuth1Test extends TestCase
{
protected function setUp()
{
$this->mockApplication([], '\yii\web\Application');
}
/**
* Invokes the OAuth client method even if it is protected.
* @param OAuth1 $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 OAuth1();
$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 OAuth1();
$authorizationHeader = $this->invokeOAuthClientMethod($oauthClient, 'composeAuthorizationHeader', [$params, $realm]);
$this->assertEquals($expectedAuthorizationHeader, $authorizationHeader);
}
public function testBuildAuthUrl() {
$oauthClient = new OAuth1();
$authUrl = 'http://test.auth.url';
$oauthClient->authUrl = $authUrl;
$requestTokenToken = 'test_request_token';
$requestToken = new OAuthToken();
$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/OAuth2Test.php

@ -0,0 +1,33 @@
<?php
namespace yiiunit\extensions\authclient\oauth;
use yii\authclient\OAuth2;
use yiiunit\extensions\authclient\TestCase;
class OAuth2Test extends TestCase
{
protected function setUp()
{
$this->mockApplication([], '\yii\web\Application');
}
// Tests :
public function testBuildAuthUrl()
{
$oauthClient = new OAuth2();
$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!');
}
}

61
tests/unit/extensions/authclient/OpenIdTest.php

@ -0,0 +1,61 @@
<?php
namespace yiiunit\extensions\authclient;
use yii\authclient\OpenId;
class OpenIdTest extends TestCase
{
protected function setUp()
{
$config = [
'components' => [
'request' => [
'hostInfo' => 'http://testdomain.com',
'scriptUrl' => '/index.php',
],
]
];
$this->mockApplication($config, '\yii\web\Application');
}
// Tests :
public function testSetGet()
{
$client = new OpenId();
$trustRoot = 'http://trust.root';
$client->setTrustRoot($trustRoot);
$this->assertEquals($trustRoot, $client->getTrustRoot(), 'Unable to setup trust root!');
$returnUrl = 'http://return.url';
$client->setReturnUrl($returnUrl);
$this->assertEquals($returnUrl, $client->getReturnUrl(), 'Unable to setup return URL!');
}
/**
* @depends testSetGet
*/
public function testGetDefaults()
{
$client = new OpenId();
$this->assertNotEmpty($client->getTrustRoot(), 'Unable to get default trust root!');
$this->assertNotEmpty($client->getReturnUrl(), 'Unable to get default return URL!');
}
public function testDiscover()
{
$url = 'https://www.google.com/accounts/o8/id';
$client = new OpenId();
$info = $client->discover($url);
$this->assertNotEmpty($info);
$this->assertNotEmpty($info['url']);
$this->assertNotEmpty($info['identity']);
$this->assertEquals(2, $info['version']);
$this->assertArrayHasKey('identifier_select', $info);
$this->assertArrayHasKey('ax', $info);
$this->assertArrayHasKey('sreg', $info);
}
}

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

@ -0,0 +1,30 @@
<?php
namespace yiiunit\extensions\authclient;
use yii\helpers\FileHelper;
use Yii;
/**
* TestCase for "authclient" extension.
*/
class TestCase extends \yiiunit\TestCase
{
/**
* Adds sphinx extension files to [[Yii::$classPath]],
* avoiding the necessity of usage Composer autoloader.
*/
public 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;
}
}
}
TestCase::loadClassMap();

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

@ -0,0 +1,133 @@
<?php
namespace yiiunit\extensions\authclient\oauth;
use yii\authclient\OAuthToken;
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 OAuthToken($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 OAuthToken();
$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 OAuthToken();
$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 OAuthToken();
$oauthToken->setParams($params);
$this->assertEquals($expectedExpireDuration, $oauthToken->getExpireDuration());
}
/**
* @depends testSetupParamsShortcuts
*/
public function testGetIsExpired()
{
$oauthToken = new OAuthToken();
$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 OAuthToken();
$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/signature/BaseMethodTest.php

@ -0,0 +1,50 @@
<?php
namespace yiiunit\extensions\authclient\signature;
use yiiunit\extensions\authclient\TestCase;
class BaseMethodTest extends TestCase
{
/**
* Creates test signature method instance.
* @return \yii\authclient\signature\BaseMethod
*/
protected function createTestSignatureMethod()
{
$signatureMethod = $this->getMock('\yii\authclient\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/signature/HmacSha1Test.php

@ -0,0 +1,20 @@
<?php
namespace yiiunit\extensions\authclient\signature;
use yii\authclient\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/signature/PlainTextTest.php

@ -0,0 +1,20 @@
<?php
namespace yiiunit\extensions\authclient\oauth\signature;
use yii\authclient\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/signature/RsaSha1Test.php

@ -0,0 +1,110 @@
<?php
namespace yiiunit\extensions\authclient\oauth\signature;
use yii\authclient\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