Попытка реализовать отдельный компонент для oAuth авторизации в свой же API. Не тестировал, не проверял работу, просто пушнул, чтобы потом продолжить в дргуом месте.

This commit is contained in:
ErickSkrauch 2016-08-04 01:07:21 +03:00
parent 71d9511d8e
commit 26b37c2f6b
13 changed files with 241 additions and 8 deletions

View File

@ -0,0 +1,22 @@
<?php
namespace api\components\ApiUser;
use common\models\OauthAccessToken;
use yii\rbac\CheckAccessInterface;
class AuthChecker implements CheckAccessInterface {
/**
* @inheritdoc
*/
public function checkAccess($token, $permissionName, $params = []) : bool {
/** @var OauthAccessToken|null $accessToken */
$accessToken = OauthAccessToken::findOne($token);
if ($accessToken === null) {
return false;
}
return $accessToken->getScopes()->exists($permissionName);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace api\components\ApiUser;
use yii\web\User as YiiUserComponent;
/**
* @property Identity|null $identity
*
* @method Identity|null getIdentity()
*/
class Component extends YiiUserComponent {
public $identity = Identity::class;
public $enableSession = false;
public $loginUrl = null;
}

View File

@ -0,0 +1,81 @@
<?php
namespace api\components\ApiUser;
use common\models\Account;
use common\models\OauthAccessToken;
use common\models\OauthClient;
use common\models\OauthSession;
use yii\base\NotSupportedException;
use yii\web\IdentityInterface;
use yii\web\UnauthorizedHttpException;
/**
* @property Account $account
* @property OauthClient $client
* @property OauthSession $session
* @property OauthAccessToken $accessToken
*/
class Identity implements IdentityInterface {
/**
* @var OauthAccessToken
*/
private $_accessToken;
/**
* @inheritdoc
*/
public static function findIdentityByAccessToken($token, $type = null) {
/** @var OauthAccessToken|null $model */
$model = OauthAccessToken::findOne($token);
if ($model === null) {
throw new UnauthorizedHttpException('Incorrect token');
} elseif ($model->isExpired()) {
throw new UnauthorizedHttpException('Token expired');
}
return new static($model);
}
private function __construct(OauthAccessToken $accessToken) {
$this->_accessToken = $accessToken;
}
public function getAccount() : Account {
return $this->getSession()->account;
}
public function getClient() : OauthClient {
return $this->getSession()->client;
}
public function getSession() : OauthSession {
return $this->_accessToken->session;
}
public function getAccessToken() : OauthAccessToken {
return $this->_accessToken;
}
/**
* Этот метод используется для получения пользователя, к которому привязаны права.
* У нас права привязываются к токенам, так что возвращаем именно его id.
* @inheritdoc
*/
public function getId() {
return $this->_accessToken->access_token;
}
public function getAuthKey() {
throw new NotSupportedException('This method used for cookie auth, except we using Bearer auth');
}
public function validateAuthKey($authKey) {
throw new NotSupportedException('This method used for cookie auth, except we using Bearer auth');
}
public static function findIdentity($id) {
throw new NotSupportedException('This method used for cookie auth, except we using Bearer auth');
}
}

View File

@ -26,6 +26,12 @@ use yii\web\User as YiiUserComponent;
*/ */
class Component extends YiiUserComponent { class Component extends YiiUserComponent {
public $enableSession = false;
public $loginUrl = null;
public $identityClass = AccountIdentity::class;
public $secret; public $secret;
public $expirationTimeout = 3600; // 1h public $expirationTimeout = 3600; // 1h

View File

@ -15,11 +15,11 @@ return [
'components' => [ 'components' => [
'user' => [ 'user' => [
'class' => \api\components\User\Component::class, 'class' => \api\components\User\Component::class,
'identityClass' => \api\models\AccountIdentity::class,
'enableSession' => false,
'loginUrl' => null,
'secret' => $params['userSecret'], 'secret' => $params['userSecret'],
], ],
'apiUser' => [
'class' => \api\components\ApiUser\Component::class,
],
'log' => [ 'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0, 'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [ 'targets' => [

View File

@ -0,0 +1,31 @@
<?php
namespace api\controllers;
use Yii;
use yii\filters\auth\HttpBearerAuth;
/**
* Поведения:
* @mixin \yii\filters\ContentNegotiator
* @mixin \yii\filters\VerbFilter
* @mixin HttpBearerAuth
*/
class ApiController extends \yii\rest\Controller {
public function behaviors() {
$parentBehaviors = parent::behaviors();
// Добавляем авторизатор для входа по Bearer токенам
$parentBehaviors['authenticator'] = [
'class' => HttpBearerAuth::class,
'user' => Yii::$app->apiUser,
];
// xml нам не понадобится
unset($parentBehaviors['contentNegotiator']['formats']['application/xml']);
// rate limiter здесь не применяется
unset($parentBehaviors['rateLimiter']);
return $parentBehaviors;
}
}

View File

@ -2,6 +2,7 @@
namespace api\controllers; namespace api\controllers;
use api\traits\ApiNormalize; use api\traits\ApiNormalize;
use Yii;
use yii\filters\auth\HttpBearerAuth; use yii\filters\auth\HttpBearerAuth;
/** /**
@ -18,6 +19,7 @@ class Controller extends \yii\rest\Controller {
// Добавляем авторизатор для входа по jwt токенам // Добавляем авторизатор для входа по jwt токенам
$parentBehaviors['authenticator'] = [ $parentBehaviors['authenticator'] = [
'class' => HttpBearerAuth::class, 'class' => HttpBearerAuth::class,
'user' => Yii::$app->getUser(),
]; ];
// xml нам не понадобится // xml нам не понадобится

View File

@ -0,0 +1,43 @@
<?php
namespace api\controllers;
use common\models\OauthScope;
use Yii;
use yii\filters\AccessControl;
use yii\helpers\ArrayHelper;
class IdentityInfoController extends ApiController {
public function behaviors() {
return ArrayHelper::merge(parent::behaviors(), [
'access' => [
'class' => AccessControl::class,
'rules' => [
[
'actions' => ['index'],
'allow' => true,
'roles' => ['@'],
],
],
],
]);
}
public function actionIndex() {
$account = Yii::$app->apiUser->getIdentity()->getAccount();
$response = [
'id' => $account->id,
'uuid' => $account->uuid,
'registeredAt' => $account->created_at,
'profileLink' => $account->getProfileLink(),
'preferredLanguage' => $account->lang,
];
if (Yii::$app->apiUser->can(OauthScope::ACCOUNT_EMAIL)) {
$response['email'] = $account->email;
}
return $response;
}
}

View File

@ -28,6 +28,7 @@ abstract class BaseApplication extends yii\base\Application {
* Include only Web application related components here * Include only Web application related components here
* *
* @property \api\components\User\Component $user User component. * @property \api\components\User\Component $user User component.
* @property \api\components\ApiUser\Component $apiUser Api User component.
* @property \api\components\ReCaptcha\Component $reCaptcha * @property \api\components\ReCaptcha\Component $reCaptcha
* @property \common\components\oauth\Component $oauth * @property \common\components\oauth\Component $oauth
* *

View File

@ -3,7 +3,7 @@ namespace common\helpers;
class StringHelper { class StringHelper {
public static function getEmailMask($email) { public static function getEmailMask(string $email) : string {
$username = explode('@', $email)[0]; $username = explode('@', $email)[0];
$usernameLength = mb_strlen($username); $usernameLength = mb_strlen($username);
$maskChars = '**'; $maskChars = '**';
@ -21,4 +21,16 @@ class StringHelper {
return $mask . mb_substr($email, $usernameLength); return $mask . mb_substr($email, $usernameLength);
} }
/**
* Проверяет на то, что переданная строка является валидным UUID
* Regex найдено на просторах интернета: http://stackoverflow.com/a/6223221
*
* @param string $uuid
* @return bool
*/
public static function isUuid(string $uuid) : bool {
$re = '/[a-f0-9]{8}\-[a-f0-9]{4}\-4[a-f0-9]{3}\-(8|9|a|b)[a-f0-9]{3}\-[a-f0-9]{12}/';
return preg_match($re, $uuid, $matches) === 1;
}
} }

View File

@ -25,7 +25,8 @@ use yii\db\ActiveRecord;
* @property integer $password_changed_at * @property integer $password_changed_at
* *
* Геттеры-сеттеры: * Геттеры-сеттеры:
* @property string $password пароль пользователя (только для записи) * @property string $password пароль пользователя (только для записи)
* @property string $profileLink ссылка на профиль на Ely без поддержки static url (только для записи)
* *
* Отношения: * Отношения:
* @property EmailActivation[] $emailActivations * @property EmailActivation[] $emailActivations
@ -144,7 +145,7 @@ class Account extends ActiveRecord {
* *
* @return bool * @return bool
*/ */
public function canAutoApprove(OauthClient $client, array $scopes = []) { public function canAutoApprove(OauthClient $client, array $scopes = []) : bool {
if ($client->is_trusted) { if ($client->is_trusted) {
return true; return true;
} }
@ -165,10 +166,14 @@ class Account extends ActiveRecord {
* Выполняет проверку, принадлежит ли этому нику аккаунт у Mojang * Выполняет проверку, принадлежит ли этому нику аккаунт у Mojang
* @return bool * @return bool
*/ */
public function hasMojangUsernameCollision() { public function hasMojangUsernameCollision() : bool {
return MojangUsername::find() return MojangUsername::find()
->andWhere(['username' => $this->username]) ->andWhere(['username' => $this->username])
->exists(); ->exists();
} }
public function getProfileLink() : string {
return 'http://ely.by/u' . $this->id;
}
} }

View File

@ -2,7 +2,6 @@
namespace common\models; namespace common\models;
use common\components\redis\Set; use common\components\redis\Set;
use Yii;
use yii\db\ActiveRecord; use yii\db\ActiveRecord;
/** /**
@ -13,6 +12,8 @@ use yii\db\ActiveRecord;
* @property integer $expire_time * @property integer $expire_time
* *
* @property Set $scopes * @property Set $scopes
*
* @property OauthSession $session
*/ */
class OauthAccessToken extends ActiveRecord { class OauthAccessToken extends ActiveRecord {
@ -38,4 +39,8 @@ class OauthAccessToken extends ActiveRecord {
return true; return true;
} }
public function isExpired() {
return time() > $this->expire_time;
}
} }

View File

@ -13,4 +13,10 @@ class StringHelperTest extends \PHPUnit_Framework_TestCase {
$this->assertEquals('эр**уч@елу.бел', StringHelper::getEmailMask('эрикскрауч@елу.бел')); $this->assertEquals('эр**уч@елу.бел', StringHelper::getEmailMask('эрикскрауч@елу.бел'));
} }
public function testIsUuid() {
$this->assertTrue(StringHelper::isUuid('a80b4487-a5c6-45a5-9829-373b4a494135'));
$this->assertFalse(StringHelper::isUuid('12345678'));
$this->assertFalse(StringHelper::isUuid('12345678-1234-1234-1234-123456789123'));
}
} }