Все классы, отвечающие за oAuth передвинуты в компоненты API, освежён код, поправлены неймспейсы

This commit is contained in:
ErickSkrauch
2016-11-27 00:43:42 +03:00
parent e6fa0fe6f3
commit 20286f1744
19 changed files with 163 additions and 127 deletions

View File

@@ -1,70 +0,0 @@
<?php
namespace common\components\oauth;
use common\components\oauth\Storage\Redis\AuthCodeStorage;
use common\components\oauth\Storage\Redis\RefreshTokenStorage;
use common\components\oauth\Storage\Yii2\AccessTokenStorage;
use common\components\oauth\Storage\Yii2\ClientStorage;
use common\components\oauth\Storage\Yii2\ScopeStorage;
use common\components\oauth\Storage\Yii2\SessionStorage;
use common\components\oauth\Util\KeyAlgorithm\UuidAlgorithm;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Grant;
use League\OAuth2\Server\Util\SecureKey;
use yii\base\InvalidConfigException;
/**
* @property AuthorizationServer $authServer
*/
class Component extends \yii\base\Component {
/**
* @var AuthorizationServer
*/
private $_authServer;
/**
* @var string[]
*/
public $grantTypes = [];
/**
* @var array grant type => class
*/
public $grantMap = [
'authorization_code' => Grant\AuthCodeGrant::class,
'client_credentials' => Grant\ClientCredentialsGrant::class,
'password' => Grant\PasswordGrant::class,
'refresh_token' => Grant\RefreshTokenGrant::class,
];
public function getAuthServer() {
if ($this->_authServer === null) {
$authServer = new AuthorizationServer();
$authServer
->setAccessTokenStorage(new AccessTokenStorage())
->setClientStorage(new ClientStorage())
->setScopeStorage(new ScopeStorage())
->setSessionStorage(new SessionStorage())
->setAuthCodeStorage(new AuthCodeStorage())
->setRefreshTokenStorage(new RefreshTokenStorage())
->setScopeDelimiter(',');
$this->_authServer = $authServer;
foreach ($this->grantTypes as $grantType) {
if (!array_key_exists($grantType, $this->grantMap)) {
throw new InvalidConfigException('Invalid grant type');
}
$grant = new $this->grantMap[$grantType]();
$this->_authServer->addGrantType($grant);
}
SecureKey::setAlgorithm(new UuidAlgorithm());
}
return $this->_authServer;
}
}

View File

@@ -1,27 +0,0 @@
<?php
namespace common\components\oauth\Entity;
use League\OAuth2\Server\Entity\EntityTrait;
use League\OAuth2\Server\Entity\SessionEntity as OriginalSessionEntity;
class AccessTokenEntity extends \League\OAuth2\Server\Entity\AccessTokenEntity {
use EntityTrait;
protected $sessionId;
public function getSessionId() {
return $this->sessionId;
}
/**
* @inheritdoc
* @return static
*/
public function setSession(OriginalSessionEntity $session) {
parent::setSession($session);
$this->sessionId = $session->getId();
return $this;
}
}

View File

@@ -1,27 +0,0 @@
<?php
namespace common\components\oauth\Entity;
use League\OAuth2\Server\Entity\EntityTrait;
use League\OAuth2\Server\Entity\SessionEntity;
class AuthCodeEntity extends \League\OAuth2\Server\Entity\AuthCodeEntity {
use EntityTrait;
protected $sessionId;
public function getSessionId() {
return $this->sessionId;
}
/**
* @inheritdoc
* @return static
*/
public function setSession(SessionEntity $session) {
parent::setSession($session);
$this->sessionId = $session->getId();
return $this;
}
}

View File

@@ -1,27 +0,0 @@
<?php
namespace common\components\oauth\Entity;
use League\OAuth2\Server\Entity\ClientEntity;
use League\OAuth2\Server\Entity\EntityTrait;
class SessionEntity extends \League\OAuth2\Server\Entity\SessionEntity {
use EntityTrait;
protected $clientId;
public function getClientId() {
return $this->clientId;
}
/**
* @inheritdoc
* @return static
*/
public function associateClient(ClientEntity $client) {
parent::associateClient($client);
$this->clientId = $client->getId();
return $this;
}
}

View File

@@ -1,22 +0,0 @@
<?php
namespace common\components\oauth\Exception;
use League\OAuth2\Server\Exception\OAuthException;
class AcceptRequiredException extends OAuthException {
public $httpStatusCode = 401;
/**
* {@inheritdoc}
*/
public $errorType = 'accept_required';
/**
* {@inheritdoc}
*/
public function __construct() {
parent::__construct('Client must accept authentication request.');
}
}

View File

@@ -1,11 +0,0 @@
<?php
namespace common\components\oauth\Exception;
class AccessDeniedException extends \League\OAuth2\Server\Exception\AccessDeniedException {
public function __construct($redirectUri = null) {
parent::__construct();
$this->redirectUri = $redirectUri;
}
}

View File

@@ -1,84 +0,0 @@
<?php
namespace common\components\oauth\Storage\Redis;
use common\components\oauth\Entity\AuthCodeEntity;
use common\components\redis\Key;
use common\components\redis\Set;
use League\OAuth2\Server\Entity\AuthCodeEntity as OriginalAuthCodeEntity;
use League\OAuth2\Server\Entity\ScopeEntity;
use League\OAuth2\Server\Storage\AbstractStorage;
use League\OAuth2\Server\Storage\AuthCodeInterface;
class AuthCodeStorage extends AbstractStorage implements AuthCodeInterface {
public $dataTable = 'oauth_auth_codes';
public $ttl = 3600; // 1h
/**
* @inheritdoc
*/
public function get($code) {
$result = (new Key($this->dataTable, $code))->getValue();
if (!$result) {
return null;
}
if ($result['expire_time'] < time()) {
return null;
}
return (new AuthCodeEntity($this->server))->hydrate([
'id' => $result['id'],
'redirectUri' => $result['client_redirect_uri'],
'expireTime' => $result['expire_time'],
'sessionId' => $result['session_id'],
]);
}
/**
* @inheritdoc
*/
public function create($token, $expireTime, $sessionId, $redirectUri) {
$payload = [
'id' => $token,
'expire_time' => $expireTime,
'session_id' => $sessionId,
'client_redirect_uri' => $redirectUri,
];
(new Key($this->dataTable, $token))->setValue($payload)->expire($this->ttl);
}
/**
* @inheritdoc
*/
public function getScopes(OriginalAuthCodeEntity $token) {
$result = new Set($this->dataTable, $token->getId(), 'scopes');
$response = [];
foreach ($result as $scope) {
// TODO: нужно проверить все выданные скоупы на их существование
$response[] = (new ScopeEntity($this->server))->hydrate(['id' => $scope]);
}
return $response;
}
/**
* @inheritdoc
*/
public function associateScope(OriginalAuthCodeEntity $token, ScopeEntity $scope) {
(new Set($this->dataTable, $token->getId(), 'scopes'))->add($scope->getId())->expire($this->ttl);
}
/**
* @inheritdoc
*/
public function delete(OriginalAuthCodeEntity $token) {
// Удаляем ключ
(new Set($this->dataTable, $token->getId()))->delete();
// Удаляем список скоупов для ключа
(new Set($this->dataTable, $token->getId(), 'scopes'))->delete();
}
}

View File

@@ -1,48 +0,0 @@
<?php
namespace common\components\oauth\Storage\Redis;
use common\components\redis\Key;
use League\OAuth2\Server\Entity\RefreshTokenEntity;
use League\OAuth2\Server\Storage\AbstractStorage;
use League\OAuth2\Server\Storage\RefreshTokenInterface;
class RefreshTokenStorage extends AbstractStorage implements RefreshTokenInterface {
public $dataTable = 'oauth_refresh_tokens';
/**
* @inheritdoc
*/
public function get($token) {
$result = (new Key($this->dataTable, $token))->getValue();
if (!$result) {
return null;
}
return (new RefreshTokenEntity($this->server))
->setId($result['id'])
->setExpireTime($result['expire_time'])
->setAccessTokenId($result['access_token_id']);
}
/**
* @inheritdoc
*/
public function create($token, $expireTime, $accessToken) {
$payload = [
'id' => $token,
'expire_time' => $expireTime,
'access_token_id' => $accessToken,
];
(new Key($this->dataTable, $token))->setValue($payload);
}
/**
* @inheritdoc
*/
public function delete(RefreshTokenEntity $token) {
(new Key($this->dataTable, $token->getId()))->delete();
}
}

View File

@@ -1,84 +0,0 @@
<?php
namespace common\components\oauth\Storage\Yii2;
use common\components\oauth\Entity\AccessTokenEntity;
use common\models\OauthAccessToken;
use League\OAuth2\Server\Entity\AccessTokenEntity as OriginalAccessTokenEntity;
use League\OAuth2\Server\Entity\ScopeEntity;
use League\OAuth2\Server\Storage\AbstractStorage;
use League\OAuth2\Server\Storage\AccessTokenInterface;
use yii\db\Exception;
class AccessTokenStorage extends AbstractStorage implements AccessTokenInterface {
private $cache = [];
/**
* @param string $token
* @return OauthAccessToken|null
*/
private function getTokenModel($token) {
if (!isset($this->cache[$token])) {
$this->cache[$token] = OauthAccessToken::findOne($token);
}
return $this->cache[$token];
}
/**
* @inheritdoc
*/
public function get($token) {
$model = $this->getTokenModel($token);
if ($model === null) {
return null;
}
return (new AccessTokenEntity($this->server))->hydrate([
'id' => $model->access_token,
'expireTime' => $model->expire_time,
'sessionId' => $model->session_id,
]);
}
/**
* @inheritdoc
*/
public function getScopes(OriginalAccessTokenEntity $token) {
$entities = [];
foreach($this->getTokenModel($token->getId())->getScopes() as $scope) {
$entities[] = (new ScopeEntity($this->server))->hydrate(['id' => $scope]);
}
return $entities;
}
/**
* @inheritdoc
*/
public function create($token, $expireTime, $sessionId) {
$model = new OauthAccessToken();
$model->access_token = $token;
$model->expire_time = $expireTime;
$model->session_id = $sessionId;
if (!$model->save()) {
throw new Exception('Cannot save ' . OauthAccessToken::class . ' model.');
}
}
/**
* @inheritdoc
*/
public function associateScope(OriginalAccessTokenEntity $token, ScopeEntity $scope) {
$this->getTokenModel($token->getId())->getScopes()->add($scope->getId());
}
/**
* @inheritdoc
*/
public function delete(OriginalAccessTokenEntity $token) {
$this->getTokenModel($token->getId())->delete();
}
}

View File

@@ -1,83 +0,0 @@
<?php
namespace common\components\oauth\Storage\Yii2;
use common\components\oauth\Entity\SessionEntity;
use common\models\OauthClient;
use League\OAuth2\Server\Entity\ClientEntity;
use League\OAuth2\Server\Entity\SessionEntity as OriginalSessionEntity;
use League\OAuth2\Server\Storage\AbstractStorage;
use League\OAuth2\Server\Storage\ClientInterface;
use yii\helpers\StringHelper;
class ClientStorage extends AbstractStorage implements ClientInterface {
const REDIRECT_STATIC_PAGE = 'static_page';
const REDIRECT_STATIC_PAGE_WITH_CODE = 'static_page_with_code';
/**
* @inheritdoc
*/
public function get($clientId, $clientSecret = null, $redirectUri = null, $grantType = null) {
$query = OauthClient::find()
->select(['id', 'name', 'secret', 'redirect_uri'])
->where([OauthClient::tableName() . '.id' => $clientId]);
if ($clientSecret !== null) {
$query->andWhere(['secret' => $clientSecret]);
}
$model = $query->asArray()->one();
if ($model === null) {
return null;
}
// TODO: нужно учитывать тип приложения
/*
* Для приложений типа "настольный" redirect_uri необязателем - он должен быть по умолчанию равен
* статичному редиректу на страницу сайта
* А для приложений типа "сайт" редирект должен быть всегда.
* Короче это нужно учесть
*/
if ($redirectUri !== null) {
if ($redirectUri === self::REDIRECT_STATIC_PAGE || $redirectUri === self::REDIRECT_STATIC_PAGE_WITH_CODE) {
// Тут, наверное, нужно проверить тип приложения
} else {
if (!StringHelper::startsWith($redirectUri, $model['redirect_uri'], false)) {
return null;
}
}
}
$entity = new ClientEntity($this->server);
$entity->hydrate([
'id' => $model['id'],
'name' => $model['name'],
'secret' => $model['secret'],
'redirectUri' => $redirectUri,
]);
return $entity;
}
/**
* @inheritdoc
*/
public function getBySession(OriginalSessionEntity $session) {
if (!$session instanceof SessionEntity) {
throw new \ErrorException('This module assumes that $session typeof ' . SessionEntity::class);
}
$model = OauthClient::find()
->select(['id', 'name'])
->andWhere(['id' => $session->getClientId()])
->asArray()
->one();
if ($model === null) {
return null;
}
return (new ClientEntity($this->server))->hydrate($model);
}
}

View File

@@ -1,26 +0,0 @@
<?php
namespace common\components\oauth\Storage\Yii2;
use common\models\OauthScope;
use League\OAuth2\Server\Entity\ScopeEntity;
use League\OAuth2\Server\Storage\AbstractStorage;
use League\OAuth2\Server\Storage\ScopeInterface;
class ScopeStorage extends AbstractStorage implements ScopeInterface {
/**
* @inheritdoc
*/
public function get($scope, $grantType = null, $clientId = null) {
$row = OauthScope::find()->andWhere(['id' => $scope])->asArray()->one();
if ($row === null) {
return null;
}
$entity = new ScopeEntity($this->server);
$entity->hydrate($row);
return $entity;
}
}

View File

@@ -1,126 +0,0 @@
<?php
namespace common\components\oauth\Storage\Yii2;
use common\components\oauth\Entity\AuthCodeEntity;
use common\components\oauth\Entity\SessionEntity;
use common\models\OauthSession;
use ErrorException;
use League\OAuth2\Server\Entity\AccessTokenEntity as OriginalAccessTokenEntity;
use League\OAuth2\Server\Entity\AuthCodeEntity as OriginalAuthCodeEntity;
use League\OAuth2\Server\Entity\ScopeEntity;
use League\OAuth2\Server\Entity\SessionEntity as OriginalSessionEntity;
use League\OAuth2\Server\Storage\AbstractStorage;
use League\OAuth2\Server\Storage\SessionInterface;
use yii\db\ActiveQuery;
use yii\db\Exception;
class SessionStorage extends AbstractStorage implements SessionInterface {
private $cache = [];
/**
* @param string $sessionId
* @return OauthSession|null
*/
private function getSessionModel($sessionId) {
if (!isset($this->cache[$sessionId])) {
$this->cache[$sessionId] = OauthSession::findOne($sessionId);
}
return $this->cache[$sessionId];
}
private function hydrateEntity($sessionModel) {
if (!$sessionModel instanceof OauthSession) {
return null;
}
return (new SessionEntity($this->server))->hydrate([
'id' => $sessionModel->id,
'client_id' => $sessionModel->client_id,
])->setOwner($sessionModel->owner_type, $sessionModel->owner_id);
}
/**
* @param string $sessionId
* @return SessionEntity|null
*/
public function getSession($sessionId) {
return $this->hydrateEntity($this->getSessionModel($sessionId));
}
/**
* @inheritdoc
*/
public function getByAccessToken(OriginalAccessTokenEntity $accessToken) {
/** @var OauthSession|null $model */
$model = OauthSession::find()->innerJoinWith([
'accessTokens' => function(ActiveQuery $query) use ($accessToken) {
$query->andWhere(['access_token' => $accessToken->getId()]);
},
])->one();
return $this->hydrateEntity($model);
}
/**
* @inheritdoc
*/
public function getByAuthCode(OriginalAuthCodeEntity $authCode) {
if (!$authCode instanceof AuthCodeEntity) {
throw new ErrorException('This module assumes that $authCode typeof ' . AuthCodeEntity::class);
}
return $this->getSession($authCode->getSessionId());
}
/**
* {@inheritdoc}
*/
public function getScopes(OriginalSessionEntity $session) {
$result = [];
foreach ($this->getSessionModel($session->getId())->getScopes() as $scope) {
// TODO: нужно проверить все выданные скоупы на их существование
$result[] = (new ScopeEntity($this->server))->hydrate(['id' => $scope]);
}
return $result;
}
/**
* @inheritdoc
*/
public function create($ownerType, $ownerId, $clientId, $clientRedirectUri = null) {
$sessionId = OauthSession::find()
->select('id')
->andWhere([
'client_id' => $clientId,
'owner_type' => $ownerType,
'owner_id' => $ownerId,
])->scalar();
if ($sessionId === false) {
$model = new OauthSession();
$model->client_id = $clientId;
$model->owner_type = $ownerType;
$model->owner_id = $ownerId;
$model->client_redirect_uri = $clientRedirectUri;
if (!$model->save()) {
throw new Exception('Cannot save ' . OauthSession::class . ' model.');
}
$sessionId = $model->id;
}
return $sessionId;
}
/**
* @inheritdoc
*/
public function associateScope(OriginalSessionEntity $session, ScopeEntity $scope) {
$this->getSessionModel($session->getId())->getScopes()->add($scope->getId());
}
}

View File

@@ -1,16 +0,0 @@
<?php
namespace common\components\oauth\Util\KeyAlgorithm;
use League\OAuth2\Server\Util\KeyAlgorithm\DefaultAlgorithm;
use Ramsey\Uuid\Uuid;
class UuidAlgorithm extends DefaultAlgorithm {
/**
* @inheritdoc
*/
public function generate($len = 40) : string {
return Uuid::uuid4()->toString();
}
}