mirror of
https://github.com/elyby/accounts.git
synced 2024-11-10 07:22:00 +05:30
Merge branch 'develop'
This commit is contained in:
commit
dbe04c2bee
45
.env-dist
45
.env-dist
@ -1,9 +1,44 @@
|
||||
# Основные параметры
|
||||
# Параметры приложения
|
||||
## Env приложения
|
||||
YII_DEBUG=true
|
||||
YII_ENV=dev
|
||||
|
||||
## Параметры, отвечающие за безопасность
|
||||
JWT_USER_SECRET=
|
||||
|
||||
## Внешние сервисы
|
||||
RECAPTCHA_PUBLIC=
|
||||
RECAPTCHA_SECRET=
|
||||
SENTRY_DSN=
|
||||
|
||||
## SMTP параметры
|
||||
SMTP_USER=
|
||||
SMTP_PASS=
|
||||
SMTP_PORT=
|
||||
|
||||
## Параметры подключения к базе данных
|
||||
DB_HOST=db
|
||||
DB_DATABASE=ely_accounts
|
||||
DB_USER=ely_accounts_user
|
||||
DB_PASSWORD=ely_accounts_password
|
||||
|
||||
## Параметры подключения к redis
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_DATABASE=0
|
||||
REDIS_PASSWORD=
|
||||
|
||||
## Параметры подключения к rabbitmq
|
||||
RABBITMQ_HOST=rabbitmq
|
||||
RABBITMQ_PORT=5672
|
||||
RABBITMQ_USER=ely-accounts-app
|
||||
RABBITMQ_PASS=ely-accounts-app-password
|
||||
RABBITMQ_VHOST=/ely.by
|
||||
|
||||
## Конфигурация для Dev.
|
||||
XDEBUG_CONFIG=remote_host=10.254.254.254
|
||||
PHP_IDE_CONFIG=serverName=docker
|
||||
|
||||
|
||||
# Web
|
||||
VIRTUAL_HOST=account.ely.by,authserver.ely.by
|
||||
@ -11,10 +46,6 @@ AUTHSERVER_HOST=authserver.ely.by
|
||||
# LETSENCRYPT_HOST=account.ely.by
|
||||
# LETSENCRYPT_EMAIL=erickskrauch@ely.by
|
||||
|
||||
# SMTP (только для production)
|
||||
SMTP_USER=
|
||||
SMTP_PASS=
|
||||
|
||||
# MySQL
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD=yes
|
||||
MYSQL_ROOT_PASSWORD=
|
||||
@ -26,7 +57,3 @@ MYSQL_PASSWORD=ely_accounts_password
|
||||
RABBITMQ_DEFAULT_USER=ely-accounts-app
|
||||
RABBITMQ_DEFAULT_PASS=ely-accounts-app-password
|
||||
RABBITMQ_DEFAULT_VHOST=/ely.by
|
||||
|
||||
# Конфигурация для Dev.
|
||||
XDEBUG_CONFIG=remote_host=10.254.254.254
|
||||
PHP_IDE_CONFIG=serverName=docker
|
||||
|
@ -4,21 +4,40 @@ stages:
|
||||
- release
|
||||
|
||||
variables:
|
||||
CONTAINER_IMAGE: registry.ely.by/elyby/accounts
|
||||
DOCKER_DRIVER: aufs
|
||||
CONTAINER_IMAGE: "registry.ely.by/elyby/accounts"
|
||||
|
||||
test:backend:
|
||||
image: jonaskello/docker-and-compose:1.12.1-1.8.0
|
||||
image: docker:latest
|
||||
services:
|
||||
- docker:1.12.1-dind
|
||||
- mariadb:10.0
|
||||
- redis:3.0-alpine
|
||||
variables:
|
||||
# mariadb config
|
||||
MYSQL_RANDOM_ROOT_PASSWORD: "true"
|
||||
MYSQL_DATABASE: "ely_accounts_test"
|
||||
MYSQL_USER: "ely_accounts_tester"
|
||||
MYSQL_PASSWORD: "ely_accounts_tester_password"
|
||||
stage: test
|
||||
before_script:
|
||||
- docker login -u gitlab-ci -p $CI_BUILD_TOKEN registry.ely.by
|
||||
- echo "$SSH_PRIVATE_KEY" > id_rsa
|
||||
- docker-compose -f tests/docker-compose.yml build --pull testphp
|
||||
after_script:
|
||||
- docker-compose -f tests/docker-compose.yml down -v
|
||||
script:
|
||||
- docker-compose -f tests/docker-compose.yml run --rm testphp ./vendor/bin/codecept run -c tests
|
||||
- export TEMP_DEV_IMAGE="${CONTAINER_IMAGE}:ci-${CI_BUILD_ID}"
|
||||
- docker build --pull -f Dockerfile-dev -t $TEMP_DEV_IMAGE .
|
||||
- >
|
||||
docker run --rm
|
||||
--add-host=mariadb:`getent hosts mariadb | awk '{ print $1 ; exit }'`
|
||||
--add-host=redis:`getent hosts redis | awk '{ print $1 ; exit }'`
|
||||
-e YII_DEBUG="true"
|
||||
-e YII_ENV="test"
|
||||
-e DB_HOST="mariadb"
|
||||
-e DB_DATABASE="ely_accounts_test"
|
||||
-e DB_USER="ely_accounts_tester"
|
||||
-e DB_PASSWORD="ely_accounts_tester_password"
|
||||
-e REDIS_HOST="redis"
|
||||
$TEMP_DEV_IMAGE
|
||||
php vendor/bin/codecept run -c tests
|
||||
|
||||
test:frontend:
|
||||
image: node:5.12
|
||||
@ -28,8 +47,8 @@ test:frontend:
|
||||
- frontend/node_modules
|
||||
script:
|
||||
- cd frontend
|
||||
- npm i --silent
|
||||
- npm run test
|
||||
- npm i --silent > /dev/null
|
||||
- npm run test --silent
|
||||
|
||||
build:production:
|
||||
image: docker:latest
|
||||
|
12
Dockerfile
12
Dockerfile
@ -1,4 +1,8 @@
|
||||
FROM registry.ely.by/elyby/accounts-php:1.0.0
|
||||
FROM registry.ely.by/elyby/accounts-php:1.2.0
|
||||
|
||||
# Вносим конфигурации для крона и воркеров
|
||||
COPY docker/cron/* /etc/cron.d/
|
||||
COPY docker/supervisor/* /etc/supervisor/conf.d/
|
||||
|
||||
COPY id_rsa /root/.ssh/id_rsa
|
||||
|
||||
@ -7,7 +11,7 @@ RUN chmod 400 ~/.ssh/id_rsa \
|
||||
&& eval $(ssh-agent -s) \
|
||||
&& ssh-add /root/.ssh/id_rsa \
|
||||
&& touch /root/.ssh/known_hosts \
|
||||
&& ssh-keyscan gitlab.com >> /root/.ssh/known_hosts
|
||||
&& ssh-keyscan gitlab.com gitlab.ely.by >> /root/.ssh/known_hosts
|
||||
|
||||
# Копируем composer.json в родительскую директорию, которая не будет синкаться с хостом через
|
||||
# volume на dev окружении. В entrypoint эта папка будет скопирована обратно.
|
||||
@ -28,7 +32,7 @@ COPY ./frontend/scripts /var/www/frontend/scripts
|
||||
COPY ./frontend/webpack-utils /var/www/frontend/webpack-utils
|
||||
|
||||
RUN cd ../frontend \
|
||||
&& npm install \
|
||||
&& npm install --quiet --depth -1 \
|
||||
&& cd -
|
||||
|
||||
# Удаляем ключи из production контейнера на всякий случай
|
||||
@ -42,7 +46,7 @@ RUN mkdir -p api/runtime api/web/assets console/runtime \
|
||||
# Билдим фронт
|
||||
&& cd frontend \
|
||||
&& ln -s /var/www/frontend/node_modules $PWD/node_modules \
|
||||
&& npm run build \
|
||||
&& npm run build:quite --quiet \
|
||||
&& rm node_modules \
|
||||
# Копируем билд наружу, чтобы его не затёрло volume в dev режиме
|
||||
&& cp -r ./dist /var/www/dist \
|
||||
|
@ -1,4 +1,8 @@
|
||||
FROM registry.ely.by/elyby/accounts-php:1.0.0-dev
|
||||
FROM registry.ely.by/elyby/accounts-php:1.2.0-dev
|
||||
|
||||
# Вносим конфигурации для крона и воркеров
|
||||
COPY docker/cron/* /etc/cron.d/
|
||||
COPY docker/supervisor/* /etc/supervisor/conf.d/
|
||||
|
||||
COPY id_rsa /root/.ssh/id_rsa
|
||||
|
||||
@ -7,7 +11,7 @@ RUN chmod 400 ~/.ssh/id_rsa \
|
||||
&& eval $(ssh-agent -s) \
|
||||
&& ssh-add /root/.ssh/id_rsa \
|
||||
&& touch /root/.ssh/known_hosts \
|
||||
&& ssh-keyscan gitlab.com >> /root/.ssh/known_hosts
|
||||
&& ssh-keyscan gitlab.com gitlab.ely.by >> /root/.ssh/known_hosts
|
||||
|
||||
# Копируем composer.json в родительскую директорию, которая не будет синкаться с хостом через
|
||||
# volume на dev окружении. В entrypoint эта папка будет скопирована обратно.
|
||||
@ -28,7 +32,7 @@ COPY ./frontend/scripts /var/www/frontend/scripts
|
||||
COPY ./frontend/webpack-utils /var/www/frontend/webpack-utils
|
||||
|
||||
RUN cd ../frontend \
|
||||
&& npm install \
|
||||
&& npm install --quiet --depth -1 \
|
||||
&& cd -
|
||||
|
||||
# Наконец переносим все сорцы внутрь контейнера
|
||||
@ -39,7 +43,7 @@ RUN mkdir -p api/runtime api/web/assets console/runtime \
|
||||
# Билдим фронт
|
||||
&& cd frontend \
|
||||
&& ln -s /var/www/frontend/node_modules $PWD/node_modules \
|
||||
&& npm run build \
|
||||
&& npm run build:quite --quiet \
|
||||
&& rm node_modules \
|
||||
# Копируем билд наружу, чтобы его не затёрло volume в dev режиме
|
||||
&& cp -r ./dist /var/www/dist \
|
||||
|
54
README.md
54
README.md
@ -1,6 +1,6 @@
|
||||
# Accounts Ely.by
|
||||
|
||||
## Развёртывание dev
|
||||
## Развёртывание dev [backend]
|
||||
|
||||
Предварительно нужно установить [git](https://git-scm.com/downloads),
|
||||
[docker](https://docs.docker.com/engine/installation/) и его
|
||||
@ -15,8 +15,8 @@
|
||||
За тем сливаем репозиторий:
|
||||
|
||||
```sh
|
||||
git clone git@gitlab.com:elyby/account.git account.ely.by
|
||||
cd account.ely.by.local
|
||||
git clone git@gitlab.ely.by:elyby/accounts.git account.ely.by
|
||||
cd account.ely.by
|
||||
```
|
||||
|
||||
Далее нужно создать `.env`, `docker-compose.yml` и `id_rsa` файлы:
|
||||
@ -27,12 +27,12 @@ cp docker-compose.dev.yml docker-compose.yml
|
||||
cp ~/.ssh/id_rsa id_rsa # Использовать ссылку нельзя
|
||||
```
|
||||
|
||||
Касательно файла id_rsa: часть зависимостей находятся в наших приватных репозиториях, получить
|
||||
**Касательно файла id_rsa**: часть зависимостей находятся в наших приватных репозиториях, получить
|
||||
доступ куда можно только в том случае, если в контейнере окажется ключ, который имеет доступ к этим
|
||||
репозиториям.
|
||||
|
||||
Все вышеперечисленные файла находятся под gitignore, так что с полученными файлами можно произвести
|
||||
все необходимые манипуляции под конкретный кейс использования. **В файле `.env` обязательно следует
|
||||
Все вышеперечисленные файлы находятся под gitignore, так что с конечными файлами можно произвести
|
||||
все необходимые манипуляции под конкретную задачу разработки. **В файле `.env` обязательно следует
|
||||
задать `JWT_USER_SECRET`, иначе авторизация на бекенде не заработает.**
|
||||
|
||||
После этого просто выполняем старт всех контейнеров:
|
||||
@ -41,10 +41,50 @@ cp ~/.ssh/id_rsa id_rsa # Использовать ссылку нельзя
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Они автоматически сбилдятся и начнут свою работу.
|
||||
Контейнеры автоматически сбилдятся и начнут свою работу.
|
||||
|
||||
## Развёртывание dev [frontend]
|
||||
|
||||
Чтобы поднять сборку frontend приложения, необходимо иметь установленный в системе [Node.js](https://nodejs.org)
|
||||
версии 5.x или 6.x, а так же npm 3-ей версии (`npm i -g npm` для обновления).
|
||||
|
||||
За тем переходим в папку `frontend` и устанавливаем зависимости:
|
||||
|
||||
```sh
|
||||
cd frontend
|
||||
npm i
|
||||
```
|
||||
|
||||
После того, как все зависимости будут установлены, можно поднять dev-сервер. Здесь есть 2 пути: можно, следуя
|
||||
инструкции выше, поднять backend на своей машине через Docker. Если же разработка не привязывается к специфичной
|
||||
версии backend, то более быстрым и удобным способ будет использовать наш dev-сервер, расположенный под адресу
|
||||
https://dev.account.ely.by.
|
||||
|
||||
В любом из случаев необходимо в папке `frontend/config` скопировать файл `template.env.js` в `env.js` (находится
|
||||
под .gitignore) и указать в параметре `apiHost` или свой локальный сервер (тот хост, что был указан в .env
|
||||
как `VIRTUAL_HOST`), или указав просто `https://dev.account.ely.by`.
|
||||
|
||||
После того, как это будет сделано, запускаем dev-сервер (находясь в папке frontend):
|
||||
|
||||
```
|
||||
npm start
|
||||
```
|
||||
|
||||
dev-сервер поднимется на 8080 порту и будет доступен по адресу http://localhost:8080.
|
||||
|
||||
### Как влезть в работающий контейнер
|
||||
|
||||
Начиная с версии docker-compose 1.9.0, появилась команда `docker-compose exec`, которая позволяет выполнить
|
||||
на работающем контейнере произвольную команду, основываясь на имени сервиса в compose файле.
|
||||
|
||||
```
|
||||
docker-compose exec app bash
|
||||
```
|
||||
|
||||
------------------------
|
||||
|
||||
_// Старый вариант_
|
||||
|
||||
Сперва, с помощью команды `docker ps` мы увидим все запущенные контейнеры. Нас интересуют значения
|
||||
из первой колонки CONTAINER ID или NAMES. Узнать, чему они соответствуют можно прочитав название IMAGE
|
||||
из 2 колонки. Чтобы выполнить команду внутри работабщего контейнера, нужно выполнить:
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace api\components\ApiUser;
|
||||
|
||||
use common\models\OauthAccessToken;
|
||||
use Yii;
|
||||
use yii\rbac\CheckAccessInterface;
|
||||
|
||||
class AuthChecker implements CheckAccessInterface {
|
||||
@ -10,13 +10,12 @@ class AuthChecker implements CheckAccessInterface {
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function checkAccess($token, $permissionName, $params = []) : bool {
|
||||
/** @var OauthAccessToken|null $accessToken */
|
||||
$accessToken = OauthAccessToken::findOne($token);
|
||||
$accessToken = Yii::$app->oauth->getAuthServer()->getAccessTokenStorage()->get($token);
|
||||
if ($accessToken === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $accessToken->getScopes()->exists($permissionName);
|
||||
return $accessToken->hasScope($permissionName);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,24 +1,25 @@
|
||||
<?php
|
||||
namespace api\components\ApiUser;
|
||||
|
||||
use api\components\OAuth2\Entities\AccessTokenEntity;
|
||||
use common\models\Account;
|
||||
use common\models\OauthAccessToken;
|
||||
use common\models\OauthClient;
|
||||
use common\models\OauthSession;
|
||||
use Yii;
|
||||
use yii\base\NotSupportedException;
|
||||
use yii\web\IdentityInterface;
|
||||
use yii\web\UnauthorizedHttpException;
|
||||
|
||||
/**
|
||||
* @property Account $account
|
||||
* @property OauthClient $client
|
||||
* @property OauthSession $session
|
||||
* @property OauthAccessToken $accessToken
|
||||
* @property Account $account
|
||||
* @property OauthClient $client
|
||||
* @property OauthSession $session
|
||||
* @property AccessTokenEntity $accessToken
|
||||
*/
|
||||
class Identity implements IdentityInterface {
|
||||
|
||||
/**
|
||||
* @var OauthAccessToken
|
||||
* @var AccessTokenEntity
|
||||
*/
|
||||
private $_accessToken;
|
||||
|
||||
@ -26,8 +27,7 @@ class Identity implements IdentityInterface {
|
||||
* @inheritdoc
|
||||
*/
|
||||
public static function findIdentityByAccessToken($token, $type = null) {
|
||||
/** @var OauthAccessToken|null $model */
|
||||
$model = OauthAccessToken::findOne($token);
|
||||
$model = Yii::$app->oauth->getAuthServer()->getAccessTokenStorage()->get($token);
|
||||
if ($model === null) {
|
||||
throw new UnauthorizedHttpException('Incorrect token');
|
||||
} elseif ($model->isExpired()) {
|
||||
@ -37,7 +37,7 @@ class Identity implements IdentityInterface {
|
||||
return new static($model);
|
||||
}
|
||||
|
||||
private function __construct(OauthAccessToken $accessToken) {
|
||||
private function __construct(AccessTokenEntity $accessToken) {
|
||||
$this->_accessToken = $accessToken;
|
||||
}
|
||||
|
||||
@ -50,20 +50,20 @@ class Identity implements IdentityInterface {
|
||||
}
|
||||
|
||||
public function getSession() : OauthSession {
|
||||
return $this->_accessToken->session;
|
||||
return OauthSession::findOne($this->_accessToken->getSessionId());
|
||||
}
|
||||
|
||||
public function getAccessToken() : OauthAccessToken {
|
||||
public function getAccessToken() : AccessTokenEntity {
|
||||
return $this->_accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Этот метод используется для получения пользователя, к которому привязаны права.
|
||||
* Этот метод используется для получения токена, к которому привязаны права.
|
||||
* У нас права привязываются к токенам, так что возвращаем именно его id.
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getId() {
|
||||
return $this->_accessToken->access_token;
|
||||
return $this->_accessToken->getId();
|
||||
}
|
||||
|
||||
public function getAuthKey() {
|
||||
|
@ -1,13 +1,13 @@
|
||||
<?php
|
||||
namespace common\components\oauth;
|
||||
namespace api\components\OAuth2;
|
||||
|
||||
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 api\components\OAuth2\Storage\AuthCodeStorage;
|
||||
use api\components\OAuth2\Storage\RefreshTokenStorage;
|
||||
use api\components\OAuth2\Storage\AccessTokenStorage;
|
||||
use api\components\OAuth2\Storage\ClientStorage;
|
||||
use api\components\OAuth2\Storage\ScopeStorage;
|
||||
use api\components\OAuth2\Storage\SessionStorage;
|
||||
use api\components\OAuth2\Utils\KeyAlgorithm\UuidAlgorithm;
|
||||
use League\OAuth2\Server\AuthorizationServer;
|
||||
use League\OAuth2\Server\Grant;
|
||||
use League\OAuth2\Server\Util\SecureKey;
|
||||
@ -41,22 +41,22 @@ class Component extends \yii\base\Component {
|
||||
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(',');
|
||||
$authServer->setAccessTokenStorage(new AccessTokenStorage());
|
||||
$authServer->setClientStorage(new ClientStorage());
|
||||
$authServer->setScopeStorage(new ScopeStorage());
|
||||
$authServer->setSessionStorage(new SessionStorage());
|
||||
$authServer->setAuthCodeStorage(new AuthCodeStorage());
|
||||
$authServer->setRefreshTokenStorage(new RefreshTokenStorage());
|
||||
$authServer->setScopeDelimiter(',');
|
||||
|
||||
$this->_authServer = $authServer;
|
||||
|
||||
foreach ($this->grantTypes as $grantType) {
|
||||
if (!array_key_exists($grantType, $this->grantMap)) {
|
||||
if (!isset($this->grantMap[$grantType])) {
|
||||
throw new InvalidConfigException('Invalid grant type');
|
||||
}
|
||||
|
||||
/** @var Grant\GrantTypeInterface $grant */
|
||||
$grant = new $this->grantMap[$grantType]();
|
||||
$this->_authServer->addGrantType($grant);
|
||||
}
|
44
api/components/OAuth2/Entities/AccessTokenEntity.php
Normal file
44
api/components/OAuth2/Entities/AccessTokenEntity.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace api\components\OAuth2\Entities;
|
||||
|
||||
use api\components\OAuth2\Storage\SessionStorage;
|
||||
use ErrorException;
|
||||
use League\OAuth2\Server\Entity\SessionEntity as OriginalSessionEntity;
|
||||
|
||||
class AccessTokenEntity extends \League\OAuth2\Server\Entity\AccessTokenEntity {
|
||||
|
||||
protected $sessionId;
|
||||
|
||||
public function getSessionId() {
|
||||
return $this->sessionId;
|
||||
}
|
||||
|
||||
public function setSessionId($sessionId) {
|
||||
$this->sessionId = $sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @return static
|
||||
*/
|
||||
public function setSession(OriginalSessionEntity $session) {
|
||||
parent::setSession($session);
|
||||
$this->sessionId = $session->getId();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSession() {
|
||||
if ($this->session instanceof OriginalSessionEntity) {
|
||||
return $this->session;
|
||||
}
|
||||
|
||||
$sessionStorage = $this->server->getSessionStorage();
|
||||
if (!$sessionStorage instanceof SessionStorage) {
|
||||
throw new ErrorException('SessionStorage must be instance of ' . SessionStorage::class);
|
||||
}
|
||||
|
||||
return $sessionStorage->getById($this->sessionId);
|
||||
}
|
||||
|
||||
}
|
@ -1,11 +1,9 @@
|
||||
<?php
|
||||
namespace common\components\oauth\Entity;
|
||||
namespace api\components\OAuth2\Entities;
|
||||
|
||||
use League\OAuth2\Server\Entity\EntityTrait;
|
||||
use League\OAuth2\Server\Entity\SessionEntity as OriginalSessionEntity;
|
||||
|
||||
class AccessTokenEntity extends \League\OAuth2\Server\Entity\AccessTokenEntity {
|
||||
use EntityTrait;
|
||||
class AuthCodeEntity extends \League\OAuth2\Server\Entity\AuthCodeEntity {
|
||||
|
||||
protected $sessionId;
|
||||
|
||||
@ -24,4 +22,8 @@ class AccessTokenEntity extends \League\OAuth2\Server\Entity\AccessTokenEntity {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setSessionId(string $sessionId) {
|
||||
$this->sessionId = $sessionId;
|
||||
}
|
||||
|
||||
}
|
22
api/components/OAuth2/Entities/ClientEntity.php
Normal file
22
api/components/OAuth2/Entities/ClientEntity.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace api\components\OAuth2\Entities;
|
||||
|
||||
class ClientEntity extends \League\OAuth2\Server\Entity\ClientEntity {
|
||||
|
||||
public function setId(string $id) {
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function setName(string $name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function setSecret(string $secret) {
|
||||
$this->secret = $secret;
|
||||
}
|
||||
|
||||
public function setRedirectUri($redirectUri) {
|
||||
$this->redirectUri = $redirectUri;
|
||||
}
|
||||
|
||||
}
|
44
api/components/OAuth2/Entities/RefreshTokenEntity.php
Normal file
44
api/components/OAuth2/Entities/RefreshTokenEntity.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace api\components\OAuth2\Entities;
|
||||
|
||||
use api\components\OAuth2\Storage\SessionStorage;
|
||||
use ErrorException;
|
||||
use League\OAuth2\Server\Entity\SessionEntity as OriginalSessionEntity;
|
||||
|
||||
class RefreshTokenEntity extends \League\OAuth2\Server\Entity\RefreshTokenEntity {
|
||||
|
||||
private $sessionId;
|
||||
|
||||
public function isExpired() : bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getSession() : SessionEntity {
|
||||
if ($this->session instanceof SessionEntity) {
|
||||
return $this->session;
|
||||
}
|
||||
|
||||
$sessionStorage = $this->server->getSessionStorage();
|
||||
if (!$sessionStorage instanceof SessionStorage) {
|
||||
throw new ErrorException('SessionStorage must be instance of ' . SessionStorage::class);
|
||||
}
|
||||
|
||||
return $sessionStorage->getById($this->sessionId);
|
||||
}
|
||||
|
||||
public function getSessionId() : int {
|
||||
return $this->sessionId;
|
||||
}
|
||||
|
||||
public function setSession(OriginalSessionEntity $session) {
|
||||
parent::setSession($session);
|
||||
$this->setSessionId($session->getId());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setSessionId(int $sessionId) {
|
||||
$this->sessionId = $sessionId;
|
||||
}
|
||||
|
||||
}
|
10
api/components/OAuth2/Entities/ScopeEntity.php
Normal file
10
api/components/OAuth2/Entities/ScopeEntity.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
namespace api\components\OAuth2\Entities;
|
||||
|
||||
class ScopeEntity extends \League\OAuth2\Server\Entity\ScopeEntity {
|
||||
|
||||
public function setId(string $id) {
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace common\components\oauth\Entity;
|
||||
namespace api\components\OAuth2\Entities;
|
||||
|
||||
use League\OAuth2\Server\Entity\ClientEntity;
|
||||
use League\OAuth2\Server\Entity\ClientEntity as OriginalClientEntity;
|
||||
use League\OAuth2\Server\Entity\EntityTrait;
|
||||
|
||||
class SessionEntity extends \League\OAuth2\Server\Entity\SessionEntity {
|
||||
@ -13,15 +13,15 @@ class SessionEntity extends \League\OAuth2\Server\Entity\SessionEntity {
|
||||
return $this->clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @return static
|
||||
*/
|
||||
public function associateClient(ClientEntity $client) {
|
||||
public function associateClient(OriginalClientEntity $client) {
|
||||
parent::associateClient($client);
|
||||
$this->clientId = $client->getId();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setClientId(string $clientId) {
|
||||
$this->clientId = $clientId;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace common\components\oauth\Exception;
|
||||
namespace api\components\OAuth2\Exception;
|
||||
|
||||
use League\OAuth2\Server\Exception\OAuthException;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace common\components\oauth\Exception;
|
||||
namespace api\components\OAuth2\Exception;
|
||||
|
||||
class AccessDeniedException extends \League\OAuth2\Server\Exception\AccessDeniedException {
|
||||
|
20
api/components/OAuth2/Grants/AuthCodeGrant.php
Normal file
20
api/components/OAuth2/Grants/AuthCodeGrant.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
namespace api\components\OAuth2\Grants;
|
||||
|
||||
use api\components\OAuth2\Entities;
|
||||
|
||||
class AuthCodeGrant extends \League\OAuth2\Server\Grant\AuthCodeGrant {
|
||||
|
||||
protected function createAccessTokenEntity() {
|
||||
return new Entities\AccessTokenEntity($this->server);
|
||||
}
|
||||
|
||||
protected function createRefreshTokenEntity() {
|
||||
return new Entities\RefreshTokenEntity($this->server);
|
||||
}
|
||||
|
||||
protected function createSessionEntity() {
|
||||
return new Entities\SessionEntity($this->server);
|
||||
}
|
||||
|
||||
}
|
150
api/components/OAuth2/Grants/RefreshTokenGrant.php
Normal file
150
api/components/OAuth2/Grants/RefreshTokenGrant.php
Normal file
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
namespace api\components\OAuth2\Grants;
|
||||
|
||||
use api\components\OAuth2\Entities;
|
||||
use ErrorException;
|
||||
use League\OAuth2\Server\Entity\ClientEntity as OriginalClientEntity;
|
||||
use League\OAuth2\Server\Entity\RefreshTokenEntity as OriginalRefreshTokenEntity;
|
||||
use League\OAuth2\Server\Event;
|
||||
use League\OAuth2\Server\Exception;
|
||||
use League\OAuth2\Server\Util\SecureKey;
|
||||
|
||||
class RefreshTokenGrant extends \League\OAuth2\Server\Grant\RefreshTokenGrant {
|
||||
|
||||
public $refreshTokenRotate = false;
|
||||
|
||||
protected function createAccessTokenEntity() {
|
||||
return new Entities\AccessTokenEntity($this->server);
|
||||
}
|
||||
|
||||
protected function createRefreshTokenEntity() {
|
||||
return new Entities\RefreshTokenEntity($this->server);
|
||||
}
|
||||
|
||||
protected function createSessionEntity() {
|
||||
return new Entities\SessionEntity($this->server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод таки пришлось переписать по той причине, что нынче мы храним access_token в redis с expire значением,
|
||||
* так что он может банально несуществовать на тот момент, когда к нему через refresh_token попытаются обратиться.
|
||||
* Поэтому мы расширили логику RefreshTokenEntity и она теперь знает о сессии, в рамках которой была создана
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function completeFlow() {
|
||||
$clientId = $this->server->getRequest()->request->get('client_id', $this->server->getRequest()->getUser());
|
||||
if (is_null($clientId)) {
|
||||
throw new Exception\InvalidRequestException('client_id');
|
||||
}
|
||||
|
||||
$clientSecret = $this->server->getRequest()->request->get(
|
||||
'client_secret',
|
||||
$this->server->getRequest()->getPassword()
|
||||
);
|
||||
if ($this->shouldRequireClientSecret() && is_null($clientSecret)) {
|
||||
throw new Exception\InvalidRequestException('client_secret');
|
||||
}
|
||||
|
||||
// Validate client ID and client secret
|
||||
$client = $this->server->getClientStorage()->get(
|
||||
$clientId,
|
||||
$clientSecret,
|
||||
null,
|
||||
$this->getIdentifier()
|
||||
);
|
||||
|
||||
if (($client instanceof OriginalClientEntity) === false) {
|
||||
$this->server->getEventEmitter()->emit(new Event\ClientAuthenticationFailedEvent($this->server->getRequest()));
|
||||
throw new Exception\InvalidClientException();
|
||||
}
|
||||
|
||||
$oldRefreshTokenParam = $this->server->getRequest()->request->get('refresh_token', null);
|
||||
if ($oldRefreshTokenParam === null) {
|
||||
throw new Exception\InvalidRequestException('refresh_token');
|
||||
}
|
||||
|
||||
// Validate refresh token
|
||||
$oldRefreshToken = $this->server->getRefreshTokenStorage()->get($oldRefreshTokenParam);
|
||||
if (($oldRefreshToken instanceof OriginalRefreshTokenEntity) === false) {
|
||||
throw new Exception\InvalidRefreshException();
|
||||
}
|
||||
|
||||
// Ensure the old refresh token hasn't expired
|
||||
if ($oldRefreshToken->isExpired()) {
|
||||
throw new Exception\InvalidRefreshException();
|
||||
}
|
||||
|
||||
/** @var Entities\AccessTokenEntity|null $oldAccessToken */
|
||||
$oldAccessToken = $oldRefreshToken->getAccessToken();
|
||||
if ($oldAccessToken instanceof Entities\AccessTokenEntity) {
|
||||
// Get the scopes for the original session
|
||||
$session = $oldAccessToken->getSession();
|
||||
} else {
|
||||
if (!$oldRefreshToken instanceof Entities\RefreshTokenEntity) {
|
||||
throw new ErrorException('oldRefreshToken must be instance of ' . Entities\RefreshTokenEntity::class);
|
||||
}
|
||||
|
||||
$session = $oldRefreshToken->getSession();
|
||||
}
|
||||
|
||||
$scopes = $this->formatScopes($session->getScopes());
|
||||
|
||||
// Get and validate any requested scopes
|
||||
$requestedScopesString = $this->server->getRequest()->request->get('scope', '');
|
||||
$requestedScopes = $this->validateScopes($requestedScopesString, $client);
|
||||
|
||||
// If no new scopes are requested then give the access token the original session scopes
|
||||
if (count($requestedScopes) === 0) {
|
||||
$newScopes = $scopes;
|
||||
} else {
|
||||
// The OAuth spec says that a refreshed access token can have the original scopes or fewer so ensure
|
||||
// the request doesn't include any new scopes
|
||||
foreach ($requestedScopes as $requestedScope) {
|
||||
if (!isset($scopes[$requestedScope->getId()])) {
|
||||
throw new Exception\InvalidScopeException($requestedScope->getId());
|
||||
}
|
||||
}
|
||||
|
||||
$newScopes = $requestedScopes;
|
||||
}
|
||||
|
||||
// Generate a new access token and assign it the correct sessions
|
||||
$newAccessToken = $this->createAccessTokenEntity();
|
||||
$newAccessToken->setId(SecureKey::generate());
|
||||
$newAccessToken->setExpireTime($this->getAccessTokenTTL() + time());
|
||||
$newAccessToken->setSession($session);
|
||||
|
||||
foreach ($newScopes as $newScope) {
|
||||
$newAccessToken->associateScope($newScope);
|
||||
}
|
||||
|
||||
// Expire the old token and save the new one
|
||||
($oldAccessToken instanceof Entities\AccessTokenEntity) && $oldAccessToken->expire();
|
||||
$newAccessToken->save();
|
||||
|
||||
$this->server->getTokenType()->setSession($session);
|
||||
$this->server->getTokenType()->setParam('access_token', $newAccessToken->getId());
|
||||
$this->server->getTokenType()->setParam('expires_in', $this->getAccessTokenTTL());
|
||||
|
||||
if ($this->shouldRotateRefreshTokens()) {
|
||||
// Expire the old refresh token
|
||||
$oldRefreshToken->expire();
|
||||
|
||||
// Generate a new refresh token
|
||||
$newRefreshToken = $this->createRefreshTokenEntity();
|
||||
$newRefreshToken->setId(SecureKey::generate());
|
||||
$newRefreshToken->setExpireTime($this->getRefreshTokenTTL() + time());
|
||||
$newRefreshToken->setAccessToken($newAccessToken);
|
||||
$newRefreshToken->save();
|
||||
|
||||
$this->server->getTokenType()->setParam('refresh_token', $newRefreshToken->getId());
|
||||
} else {
|
||||
$oldRefreshToken->setAccessToken($newAccessToken);
|
||||
$oldRefreshToken->save();
|
||||
}
|
||||
|
||||
return $this->server->getTokenType()->generateResponse();
|
||||
}
|
||||
|
||||
}
|
67
api/components/OAuth2/Storage/AccessTokenStorage.php
Normal file
67
api/components/OAuth2/Storage/AccessTokenStorage.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
namespace api\components\OAuth2\Storage;
|
||||
|
||||
use api\components\OAuth2\Entities\AccessTokenEntity;
|
||||
use common\components\Redis\Key;
|
||||
use common\components\Redis\Set;
|
||||
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\helpers\Json;
|
||||
|
||||
class AccessTokenStorage extends AbstractStorage implements AccessTokenInterface {
|
||||
|
||||
public $dataTable = 'oauth_access_tokens';
|
||||
|
||||
public function get($token) {
|
||||
$result = Json::decode((new Key($this->dataTable, $token))->getValue());
|
||||
|
||||
$token = new AccessTokenEntity($this->server);
|
||||
$token->setId($result['id']);
|
||||
$token->setExpireTime($result['expire_time']);
|
||||
$token->setSessionId($result['session_id']);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function getScopes(OriginalAccessTokenEntity $token) {
|
||||
$scopes = $this->scopes($token->getId());
|
||||
$entities = [];
|
||||
foreach($scopes as $scope) {
|
||||
if ($this->server->getScopeStorage()->get($scope) !== null) {
|
||||
$entities[] = (new ScopeEntity($this->server))->hydrate(['id' => $scope]);
|
||||
}
|
||||
}
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
public function create($token, $expireTime, $sessionId) {
|
||||
$payload = Json::encode([
|
||||
'id' => $token,
|
||||
'expire_time' => $expireTime,
|
||||
'session_id' => $sessionId,
|
||||
]);
|
||||
|
||||
$this->key($token)->setValue($payload)->expireAt($expireTime);
|
||||
}
|
||||
|
||||
public function associateScope(OriginalAccessTokenEntity $token, ScopeEntity $scope) {
|
||||
$this->scopes($token->getId())->add($scope->getId())->expireAt($token->getExpireTime());
|
||||
}
|
||||
|
||||
public function delete(OriginalAccessTokenEntity $token) {
|
||||
$this->key($token->getId())->delete();
|
||||
$this->scopes($token->getId())->delete();
|
||||
}
|
||||
|
||||
private function key(string $token) : Key {
|
||||
return new Key($this->dataTable, $token);
|
||||
}
|
||||
|
||||
private function scopes(string $token) : Set {
|
||||
return new Set($this->dataTable, $token, 'scopes');
|
||||
}
|
||||
|
||||
}
|
72
api/components/OAuth2/Storage/AuthCodeStorage.php
Normal file
72
api/components/OAuth2/Storage/AuthCodeStorage.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
namespace api\components\OAuth2\Storage;
|
||||
|
||||
use api\components\OAuth2\Entities\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;
|
||||
use yii\helpers\Json;
|
||||
|
||||
class AuthCodeStorage extends AbstractStorage implements AuthCodeInterface {
|
||||
|
||||
public $dataTable = 'oauth_auth_codes';
|
||||
|
||||
public function get($code) {
|
||||
$result = Json::decode((new Key($this->dataTable, $code))->getValue());
|
||||
if ($result === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$entity = new AuthCodeEntity($this->server);
|
||||
$entity->setId($result['id']);
|
||||
$entity->setExpireTime($result['expire_time']);
|
||||
$entity->setSessionId($result['session_id']);
|
||||
$entity->setRedirectUri($result['client_redirect_uri']);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
public function create($token, $expireTime, $sessionId, $redirectUri) {
|
||||
$payload = Json::encode([
|
||||
'id' => $token,
|
||||
'expire_time' => $expireTime,
|
||||
'session_id' => $sessionId,
|
||||
'client_redirect_uri' => $redirectUri,
|
||||
]);
|
||||
|
||||
$this->key($token)->setValue($payload)->expireAt($expireTime);
|
||||
}
|
||||
|
||||
public function getScopes(OriginalAuthCodeEntity $token) {
|
||||
$scopes = $this->scopes($token->getId());
|
||||
$scopesEntities = [];
|
||||
foreach ($scopes as $scope) {
|
||||
if ($this->server->getScopeStorage()->get($scope) !== null) {
|
||||
$scopesEntities[] = (new ScopeEntity($this->server))->hydrate(['id' => $scope]);
|
||||
}
|
||||
}
|
||||
|
||||
return $scopesEntities;
|
||||
}
|
||||
|
||||
public function associateScope(OriginalAuthCodeEntity $token, ScopeEntity $scope) {
|
||||
$this->scopes($token->getId())->add($scope->getId())->expireAt($token->getExpireTime());
|
||||
}
|
||||
|
||||
public function delete(OriginalAuthCodeEntity $token) {
|
||||
$this->key($token->getId())->delete();
|
||||
$this->scopes($token->getId())->delete();
|
||||
}
|
||||
|
||||
private function key(string $token) : Key {
|
||||
return new Key($this->dataTable, $token);
|
||||
}
|
||||
|
||||
private function scopes(string $token) : Set {
|
||||
return new Set($this->dataTable, $token, 'scopes');
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
<?php
|
||||
namespace common\components\oauth\Storage\Yii2;
|
||||
namespace api\components\OAuth2\Storage;
|
||||
|
||||
use common\components\oauth\Entity\SessionEntity;
|
||||
use api\components\OAuth2\Entities\ClientEntity;
|
||||
use api\components\OAuth2\Entities\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;
|
||||
@ -18,15 +18,13 @@ class ClientStorage extends AbstractStorage implements ClientInterface {
|
||||
* @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]);
|
||||
|
||||
$query = OauthClient::find()->andWhere(['id' => $clientId]);
|
||||
if ($clientSecret !== null) {
|
||||
$query->andWhere(['secret' => $clientSecret]);
|
||||
}
|
||||
|
||||
$model = $query->asArray()->one();
|
||||
/** @var OauthClient|null $model */
|
||||
$model = $query->one();
|
||||
if ($model === null) {
|
||||
return null;
|
||||
}
|
||||
@ -39,22 +37,17 @@ class ClientStorage extends AbstractStorage implements ClientInterface {
|
||||
* Короче это нужно учесть
|
||||
*/
|
||||
if ($redirectUri !== null) {
|
||||
if ($redirectUri === self::REDIRECT_STATIC_PAGE || $redirectUri === self::REDIRECT_STATIC_PAGE_WITH_CODE) {
|
||||
if (in_array($redirectUri, [self::REDIRECT_STATIC_PAGE, self::REDIRECT_STATIC_PAGE_WITH_CODE], true)) {
|
||||
// Тут, наверное, нужно проверить тип приложения
|
||||
} else {
|
||||
if (!StringHelper::startsWith($redirectUri, $model['redirect_uri'], false)) {
|
||||
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,
|
||||
]);
|
||||
$entity = $this->hydrate($model);
|
||||
$entity->setRedirectUri($redirectUri);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
@ -67,17 +60,23 @@ class ClientStorage extends AbstractStorage implements ClientInterface {
|
||||
throw new \ErrorException('This module assumes that $session typeof ' . SessionEntity::class);
|
||||
}
|
||||
|
||||
$model = OauthClient::find()
|
||||
->select(['id', 'name'])
|
||||
->andWhere(['id' => $session->getClientId()])
|
||||
->asArray()
|
||||
->one();
|
||||
|
||||
/** @var OauthClient|null $model */
|
||||
$model = OauthClient::findOne($session->getClientId());
|
||||
if ($model === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (new ClientEntity($this->server))->hydrate($model);
|
||||
return $this->hydrate($model);
|
||||
}
|
||||
|
||||
private function hydrate(OauthClient $model) : ClientEntity {
|
||||
$entity = new ClientEntity($this->server);
|
||||
$entity->setId($model->id);
|
||||
$entity->setName($model->name);
|
||||
$entity->setSecret($model->secret);
|
||||
$entity->setRedirectUri($model->redirect_uri);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
}
|
60
api/components/OAuth2/Storage/RefreshTokenStorage.php
Normal file
60
api/components/OAuth2/Storage/RefreshTokenStorage.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
namespace api\components\OAuth2\Storage;
|
||||
|
||||
use api\components\OAuth2\Entities\RefreshTokenEntity;
|
||||
use common\components\Redis\Key;
|
||||
use common\components\Redis\Set;
|
||||
use common\models\OauthSession;
|
||||
use ErrorException;
|
||||
use League\OAuth2\Server\Entity\RefreshTokenEntity as OriginalRefreshTokenEntity;
|
||||
use League\OAuth2\Server\Storage\AbstractStorage;
|
||||
use League\OAuth2\Server\Storage\RefreshTokenInterface;
|
||||
use Yii;
|
||||
use yii\helpers\Json;
|
||||
|
||||
class RefreshTokenStorage extends AbstractStorage implements RefreshTokenInterface {
|
||||
|
||||
public $dataTable = 'oauth_refresh_tokens';
|
||||
|
||||
public function get($token) {
|
||||
$result = Json::decode((new Key($this->dataTable, $token))->getValue());
|
||||
|
||||
$entity = new RefreshTokenEntity($this->server);
|
||||
$entity->setId($result['id']);
|
||||
$entity->setAccessTokenId($result['access_token_id']);
|
||||
$entity->setSessionId($result['session_id']);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
public function create($token, $expireTime, $accessToken) {
|
||||
$sessionId = $this->server->getAccessTokenStorage()->get($accessToken)->getSession()->getId();
|
||||
$payload = Json::encode([
|
||||
'id' => $token,
|
||||
'access_token_id' => $accessToken,
|
||||
'session_id' => $sessionId,
|
||||
]);
|
||||
|
||||
$this->key($token)->setValue($payload);
|
||||
$this->sessionHash($sessionId)->add($token);
|
||||
}
|
||||
|
||||
public function delete(OriginalRefreshTokenEntity $token) {
|
||||
if (!$token instanceof RefreshTokenEntity) {
|
||||
throw new ErrorException('Token must be instance of ' . RefreshTokenEntity::class);
|
||||
}
|
||||
|
||||
$this->key($token->getId())->delete();
|
||||
$this->sessionHash($token->getSessionId())->remove($token->getId());
|
||||
}
|
||||
|
||||
public function sessionHash(string $sessionId) : Set {
|
||||
$tableName = Yii::$app->db->getSchema()->getRawTableName(OauthSession::tableName());
|
||||
return new Set($tableName, $sessionId, 'refresh_tokens');
|
||||
}
|
||||
|
||||
private function key(string $token) : Key {
|
||||
return new Key($this->dataTable, $token);
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
namespace common\components\oauth\Storage\Yii2;
|
||||
namespace api\components\OAuth2\Storage;
|
||||
|
||||
use api\components\OAuth2\Entities\ScopeEntity;
|
||||
use common\models\OauthScope;
|
||||
use League\OAuth2\Server\Entity\ScopeEntity;
|
||||
use League\OAuth2\Server\Storage\AbstractStorage;
|
||||
use League\OAuth2\Server\Storage\ScopeInterface;
|
||||
|
||||
@ -12,13 +12,12 @@ 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) {
|
||||
if (!in_array($scope, OauthScope::getScopes(), true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$entity = new ScopeEntity($this->server);
|
||||
$entity->hydrate($row);
|
||||
$entity->setId($scope);
|
||||
|
||||
return $entity;
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
namespace common\components\oauth\Storage\Yii2;
|
||||
namespace api\components\OAuth2\Storage;
|
||||
|
||||
use common\components\oauth\Entity\AuthCodeEntity;
|
||||
use common\components\oauth\Entity\SessionEntity;
|
||||
use api\components\OAuth2\Entities\AuthCodeEntity;
|
||||
use api\components\OAuth2\Entities\SessionEntity;
|
||||
use common\models\OauthSession;
|
||||
use ErrorException;
|
||||
use League\OAuth2\Server\Entity\AccessTokenEntity as OriginalAccessTokenEntity;
|
||||
@ -11,85 +11,41 @@ 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));
|
||||
public function getById($sessionId) {
|
||||
return $this->hydrate($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);
|
||||
throw new ErrorException('This method is not implemented and should not be used');
|
||||
}
|
||||
|
||||
/**
|
||||
* @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());
|
||||
return $this->getById($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]);
|
||||
if ($this->server->getScopeStorage()->get($scope) !== null) {
|
||||
$result[] = (new ScopeEntity($this->server))->hydrate(['id' => $scope]);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function create($ownerType, $ownerId, $clientId, $clientRedirectUri = null) {
|
||||
$sessionId = OauthSession::find()
|
||||
->select('id')
|
||||
@ -116,11 +72,26 @@ class SessionStorage extends AbstractStorage implements SessionInterface {
|
||||
return $sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function associateScope(OriginalSessionEntity $session, ScopeEntity $scope) {
|
||||
$this->getSessionModel($session->getId())->getScopes()->add($scope->getId());
|
||||
}
|
||||
|
||||
private function getSessionModel(string $sessionId) : OauthSession {
|
||||
$session = OauthSession::findOne($sessionId);
|
||||
if ($session === null) {
|
||||
throw new ErrorException('Cannot find oauth session');
|
||||
}
|
||||
|
||||
return $session;
|
||||
}
|
||||
|
||||
private function hydrate(OauthSession $sessionModel) {
|
||||
$entity = new SessionEntity($this->server);
|
||||
$entity->setId($sessionModel->id);
|
||||
$entity->setClientId($sessionModel->client_id);
|
||||
$entity->setOwner($sessionModel->owner_type, $sessionModel->owner_id);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
}
|
16
api/components/OAuth2/Utils/KeyAlgorithm/UuidAlgorithm.php
Normal file
16
api/components/OAuth2/Utils/KeyAlgorithm/UuidAlgorithm.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
namespace api\components\OAuth2\Utils\KeyAlgorithm;
|
||||
|
||||
use League\OAuth2\Server\Util\KeyAlgorithm\KeyAlgorithmInterface;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
class UuidAlgorithm implements KeyAlgorithmInterface {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function generate($len = 40) : string {
|
||||
return Uuid::uuid4()->toString();
|
||||
}
|
||||
|
||||
}
|
@ -23,7 +23,7 @@ use yii\web\User as YiiUserComponent;
|
||||
* @property AccountSession|null $activeSession
|
||||
* @property AccountIdentity|null $identity
|
||||
*
|
||||
* @method AccountIdentity|null getIdentity($autoRenew = true)
|
||||
* @method AccountIdentity|null loginByAccessToken($token, $type = null)
|
||||
*/
|
||||
class Component extends YiiUserComponent {
|
||||
|
||||
@ -39,6 +39,8 @@ class Component extends YiiUserComponent {
|
||||
|
||||
public $sessionTimeout = 'P7D';
|
||||
|
||||
private $_identity;
|
||||
|
||||
public function init() {
|
||||
parent::init();
|
||||
if (!$this->secret) {
|
||||
@ -46,6 +48,24 @@ class Component extends YiiUserComponent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $autoRenew
|
||||
* @return null|AccountIdentity
|
||||
*/
|
||||
public function getIdentity($autoRenew = true) {
|
||||
$result = parent::getIdentity($autoRenew);
|
||||
if ($result === null && $this->_identity !== false) {
|
||||
$bearer = $this->getBearerToken();
|
||||
if ($bearer !== null) {
|
||||
$result = $this->loginByAccessToken($bearer);
|
||||
}
|
||||
|
||||
$this->_identity = $result ?: false;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IdentityInterface $identity
|
||||
* @param bool $rememberMe
|
||||
@ -90,7 +110,7 @@ class Component extends YiiUserComponent {
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function renew(AccountSession $session) {
|
||||
public function renew(AccountSession $session): RenewResult {
|
||||
$account = $session->account;
|
||||
$transaction = Yii::$app->db->beginTransaction();
|
||||
try {
|
||||
@ -149,14 +169,9 @@ class Component extends YiiUserComponent {
|
||||
return null;
|
||||
}
|
||||
|
||||
$authHeader = Yii::$app->request->getHeaders()->get('Authorization');
|
||||
if ($authHeader === null || !preg_match('/^Bearer\s+(.*?)$/', $authHeader, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$token = $matches[1];
|
||||
$bearer = $this->getBearerToken();
|
||||
try {
|
||||
$token = $this->parseToken($token);
|
||||
$token = $this->parseToken($bearer);
|
||||
} catch (VerificationException $e) {
|
||||
return null;
|
||||
}
|
||||
@ -203,4 +218,16 @@ class Component extends YiiUserComponent {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?string
|
||||
*/
|
||||
private function getBearerToken() {
|
||||
$authHeader = Yii::$app->request->getHeaders()->get('Authorization');
|
||||
if ($authHeader === null || !preg_match('/^Bearer\s+(.*?)$/', $authHeader, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
$params = array_merge(
|
||||
require(__DIR__ . '/../../common/config/params.php'),
|
||||
require(__DIR__ . '/params.php')
|
||||
require __DIR__ . '/../../common/config/params.php',
|
||||
require __DIR__ . '/params.php'
|
||||
);
|
||||
|
||||
return [
|
||||
@ -21,6 +21,17 @@ return [
|
||||
'log' => [
|
||||
'traceLevel' => YII_DEBUG ? 3 : 0,
|
||||
'targets' => [
|
||||
[
|
||||
'class' => mito\sentry\Target::class,
|
||||
'levels' => ['error', 'warning'],
|
||||
'except' => [
|
||||
'legacy-authserver',
|
||||
'session',
|
||||
'yii\web\HttpException:*',
|
||||
'api\modules\session\exceptions\SessionServerException:*',
|
||||
'api\modules\authserver\exceptions\AuthserverException:*',
|
||||
],
|
||||
],
|
||||
[
|
||||
'class' => yii\log\FileTarget::class,
|
||||
'levels' => ['error', 'warning'],
|
||||
@ -63,8 +74,12 @@ return [
|
||||
'format' => yii\web\Response::FORMAT_JSON,
|
||||
],
|
||||
'oauth' => [
|
||||
'class' => common\components\oauth\Component::class,
|
||||
'class' => api\components\OAuth2\Component::class,
|
||||
'grantTypes' => ['authorization_code'],
|
||||
'grantMap' => [
|
||||
'authorization_code' => api\components\OAuth2\Grants\AuthCodeGrant::class,
|
||||
'refresh_token' => api\components\OAuth2\Grants\RefreshTokenGrant::class,
|
||||
],
|
||||
],
|
||||
'errorHandler' => [
|
||||
'class' => api\components\ErrorHandler::class,
|
||||
|
@ -79,7 +79,7 @@ class AccountsController extends Controller {
|
||||
if (!$model->changePassword()) {
|
||||
return [
|
||||
'success' => false,
|
||||
'errors' => $this->normalizeModelErrors($model->getErrors()),
|
||||
'errors' => $model->getFirstErrors(),
|
||||
];
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ class AccountsController extends Controller {
|
||||
if (!$model->change()) {
|
||||
return [
|
||||
'success' => false,
|
||||
'errors' => $this->normalizeModelErrors($model->getErrors()),
|
||||
'errors' => $model->getFirstErrors(),
|
||||
];
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ class AccountsController extends Controller {
|
||||
if (!$model->sendCurrentEmailConfirmation()) {
|
||||
$data = [
|
||||
'success' => false,
|
||||
'errors' => $this->normalizeModelErrors($model->getErrors()),
|
||||
'errors' => $model->getFirstErrors(),
|
||||
];
|
||||
|
||||
if (ArrayHelper::getValue($data['errors'], 'email') === E::RECENTLY_SENT_MESSAGE) {
|
||||
@ -136,7 +136,7 @@ class AccountsController extends Controller {
|
||||
if (!$model->sendNewEmailConfirmation()) {
|
||||
return [
|
||||
'success' => false,
|
||||
'errors' => $this->normalizeModelErrors($model->getErrors()),
|
||||
'errors' => $model->getFirstErrors(),
|
||||
];
|
||||
}
|
||||
|
||||
@ -152,7 +152,7 @@ class AccountsController extends Controller {
|
||||
if (!$model->changeEmail()) {
|
||||
return [
|
||||
'success' => false,
|
||||
'errors' => $this->normalizeModelErrors($model->getErrors()),
|
||||
'errors' => $model->getFirstErrors(),
|
||||
];
|
||||
}
|
||||
|
||||
@ -171,7 +171,7 @@ class AccountsController extends Controller {
|
||||
if (!$model->applyLanguage()) {
|
||||
return [
|
||||
'success' => false,
|
||||
'errors' => $this->normalizeModelErrors($model->getErrors()),
|
||||
'errors' => $model->getFirstErrors(),
|
||||
];
|
||||
}
|
||||
|
||||
@ -187,7 +187,7 @@ class AccountsController extends Controller {
|
||||
if (!$model->agreeWithLatestRules()) {
|
||||
return [
|
||||
'success' => false,
|
||||
'errors' => $this->normalizeModelErrors($model->getErrors()),
|
||||
'errors' => $model->getFirstErrors(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -17,13 +17,14 @@ class AuthenticationController extends Controller {
|
||||
public function behaviors() {
|
||||
return ArrayHelper::merge(parent::behaviors(), [
|
||||
'authenticator' => [
|
||||
'except' => ['login', 'forgot-password', 'recover-password', 'refresh-token'],
|
||||
'only' => ['logout'],
|
||||
],
|
||||
'access' => [
|
||||
'class' => AccessControl::class,
|
||||
'except' => ['refresh-token'],
|
||||
'rules' => [
|
||||
[
|
||||
'actions' => ['login', 'forgot-password', 'recover-password', 'refresh-token'],
|
||||
'actions' => ['login', 'forgot-password', 'recover-password'],
|
||||
'allow' => true,
|
||||
'roles' => ['?'],
|
||||
],
|
||||
@ -53,7 +54,7 @@ class AuthenticationController extends Controller {
|
||||
if (($result = $model->login()) === false) {
|
||||
$data = [
|
||||
'success' => false,
|
||||
'errors' => $this->normalizeModelErrors($model->getErrors()),
|
||||
'errors' => $model->getFirstErrors(),
|
||||
];
|
||||
|
||||
if (ArrayHelper::getValue($data['errors'], 'login') === E::ACCOUNT_NOT_ACTIVATED) {
|
||||
@ -83,7 +84,7 @@ class AuthenticationController extends Controller {
|
||||
if ($model->forgotPassword() === false) {
|
||||
$data = [
|
||||
'success' => false,
|
||||
'errors' => $this->normalizeModelErrors($model->getErrors()),
|
||||
'errors' => $model->getFirstErrors(),
|
||||
];
|
||||
|
||||
if (ArrayHelper::getValue($data['errors'], 'login') === E::RECENTLY_SENT_MESSAGE) {
|
||||
@ -119,7 +120,7 @@ class AuthenticationController extends Controller {
|
||||
if (($result = $model->recoverPassword()) === false) {
|
||||
return [
|
||||
'success' => false,
|
||||
'errors' => $this->normalizeModelErrors($model->getErrors()),
|
||||
'errors' => $model->getFirstErrors(),
|
||||
];
|
||||
}
|
||||
|
||||
@ -134,7 +135,7 @@ class AuthenticationController extends Controller {
|
||||
if (($result = $model->renew()) === false) {
|
||||
return [
|
||||
'success' => false,
|
||||
'errors' => $this->normalizeModelErrors($model->getErrors()),
|
||||
'errors' => $model->getFirstErrors(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
namespace api\controllers;
|
||||
|
||||
use api\traits\ApiNormalize;
|
||||
use Yii;
|
||||
use yii\filters\auth\HttpBearerAuth;
|
||||
|
||||
@ -12,7 +11,6 @@ use yii\filters\auth\HttpBearerAuth;
|
||||
* @mixin \yii\filters\auth\CompositeAuth
|
||||
*/
|
||||
class Controller extends \yii\rest\Controller {
|
||||
use ApiNormalize;
|
||||
|
||||
public function behaviors() {
|
||||
$parentBehaviors = parent::behaviors();
|
||||
@ -22,10 +20,11 @@ class Controller extends \yii\rest\Controller {
|
||||
'user' => Yii::$app->getUser(),
|
||||
];
|
||||
|
||||
// xml нам не понадобится
|
||||
unset($parentBehaviors['contentNegotiator']['formats']['application/xml']);
|
||||
// rate limiter здесь не применяется
|
||||
unset($parentBehaviors['rateLimiter']);
|
||||
// xml и rate limiter нам не понадобятся
|
||||
unset(
|
||||
$parentBehaviors['contentNegotiator']['formats']['application/xml'],
|
||||
$parentBehaviors['rateLimiter']
|
||||
);
|
||||
|
||||
return $parentBehaviors;
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ class FeedbackController extends Controller {
|
||||
if (!$model->sendMessage()) {
|
||||
return [
|
||||
'success' => false,
|
||||
'errors' => $this->normalizeModelErrors($model->getErrors()),
|
||||
'errors' => $model->getFirstErrors(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -2,13 +2,12 @@
|
||||
namespace api\controllers;
|
||||
|
||||
use api\filters\ActiveUserRule;
|
||||
use common\components\oauth\Exception\AcceptRequiredException;
|
||||
use common\components\oauth\Exception\AccessDeniedException;
|
||||
use api\components\OAuth2\Exception\AcceptRequiredException;
|
||||
use api\components\OAuth2\Exception\AccessDeniedException;
|
||||
use common\models\Account;
|
||||
use common\models\OauthClient;
|
||||
use common\models\OauthScope;
|
||||
use League\OAuth2\Server\Exception\OAuthException;
|
||||
use League\OAuth2\Server\Grant\RefreshTokenGrant;
|
||||
use Yii;
|
||||
use yii\filters\AccessControl;
|
||||
use yii\helpers\ArrayHelper;
|
||||
@ -18,16 +17,12 @@ class OauthController extends Controller {
|
||||
public function behaviors() {
|
||||
return ArrayHelper::merge(parent::behaviors(), [
|
||||
'authenticator' => [
|
||||
'except' => ['validate', 'token'],
|
||||
'only' => ['complete'],
|
||||
],
|
||||
'access' => [
|
||||
'class' => AccessControl::class,
|
||||
'only' => ['complete'],
|
||||
'rules' => [
|
||||
[
|
||||
'actions' => ['validate', 'token'],
|
||||
'allow' => true,
|
||||
'roles' => ['?'],
|
||||
],
|
||||
[
|
||||
'class' => ActiveUserRule::class,
|
||||
'actions' => ['complete'],
|
||||
@ -186,7 +181,7 @@ class OauthController extends Controller {
|
||||
}
|
||||
|
||||
$scopes = $codeModel->getScopes();
|
||||
if (array_search(OauthScope::OFFLINE_ACCESS, array_keys($scopes)) === false) {
|
||||
if (array_search(OauthScope::OFFLINE_ACCESS, array_keys($scopes), true) === false) {
|
||||
return;
|
||||
}
|
||||
} elseif ($grantType === 'refresh_token') {
|
||||
@ -195,7 +190,10 @@ class OauthController extends Controller {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->getServer()->addGrantType(new RefreshTokenGrant());
|
||||
$grantClass = Yii::$app->oauth->grantMap['refresh_token'];
|
||||
$grant = new $grantClass;
|
||||
|
||||
$this->getServer()->addGrantType($grant);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace api\controllers;
|
||||
|
||||
use api\filters\NginxCache;
|
||||
use Yii;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
@ -11,6 +12,12 @@ class OptionsController extends Controller {
|
||||
'authenticator' => [
|
||||
'except' => ['index'],
|
||||
],
|
||||
'nginxCache' => [
|
||||
'class' => NginxCache::class,
|
||||
'rules' => [
|
||||
'index' => 3600, // 1h
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ class SignupController extends Controller {
|
||||
if (!$model->signup()) {
|
||||
return [
|
||||
'success' => false,
|
||||
'errors' => $this->normalizeModelErrors($model->getErrors()),
|
||||
'errors' => $model->getFirstErrors(),
|
||||
];
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ class SignupController extends Controller {
|
||||
if (!$model->sendRepeatMessage()) {
|
||||
$response = [
|
||||
'success' => false,
|
||||
'errors' => $this->normalizeModelErrors($model->getErrors()),
|
||||
'errors' => $model->getFirstErrors(),
|
||||
];
|
||||
|
||||
if (ArrayHelper::getValue($response['errors'], 'email') === E::RECENTLY_SENT_MESSAGE) {
|
||||
@ -83,7 +83,7 @@ class SignupController extends Controller {
|
||||
if (!($result = $model->confirm())) {
|
||||
return [
|
||||
'success' => false,
|
||||
'errors' => $this->normalizeModelErrors($model->getErrors()),
|
||||
'errors' => $model->getFirstErrors(),
|
||||
];
|
||||
}
|
||||
|
||||
|
35
api/filters/NginxCache.php
Normal file
35
api/filters/NginxCache.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
namespace api\filters;
|
||||
|
||||
use Yii;
|
||||
use yii\base\ActionFilter;
|
||||
|
||||
class NginxCache extends ActionFilter {
|
||||
|
||||
/**
|
||||
* @var array|callable массив или callback, содержащий пары роут -> сколько кэшировать.
|
||||
*
|
||||
* Период можно задавать 2-умя путями:
|
||||
* - если значение начинается с префикса @, оно задаёт абсолютное время в unix timestamp,
|
||||
* до которого ответ может быть закэширован.
|
||||
* - в ином случае значение интерпретируется как количество секунд, на которое необходимо
|
||||
* закэшировать ответ
|
||||
*/
|
||||
public $rules;
|
||||
|
||||
public function afterAction($action, $result) {
|
||||
$rule = $this->rules[$action->id] ?? null;
|
||||
if ($rule !== null) {
|
||||
if (is_callable($rule)) {
|
||||
$cacheTime = $rule($action);
|
||||
} else {
|
||||
$cacheTime = $rule;
|
||||
}
|
||||
|
||||
Yii::$app->response->headers->set('X-Accel-Expires', $cacheTime);
|
||||
}
|
||||
|
||||
return parent::afterAction($action, $result);
|
||||
}
|
||||
|
||||
}
|
@ -28,15 +28,19 @@ class ChangeUsernameForm extends ApiForm {
|
||||
];
|
||||
}
|
||||
|
||||
public function change() {
|
||||
public function change() : bool {
|
||||
if (!$this->validate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$transaction = Yii::$app->db->beginTransaction();
|
||||
$account = $this->getAccount();
|
||||
$oldNickname = $account->username;
|
||||
if ($this->username === $account->username) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$transaction = Yii::$app->db->beginTransaction();
|
||||
try {
|
||||
$oldNickname = $account->username;
|
||||
$account->username = $this->username;
|
||||
if (!$account->save()) {
|
||||
throw new ErrorException('Cannot save account model with new username');
|
||||
|
@ -25,6 +25,7 @@ class RateLimiter extends \yii\filters\RateLimiter {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws TooManyRequestsHttpException
|
||||
*/
|
||||
public function beforeAction($action) {
|
||||
$this->checkRateLimit(
|
||||
@ -39,6 +40,7 @@ class RateLimiter extends \yii\filters\RateLimiter {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws TooManyRequestsHttpException
|
||||
*/
|
||||
public function checkRateLimit($user, $request, $response, $action) {
|
||||
if (parse_url($request->getHostInfo(), PHP_URL_HOST) === $this->authserverDomain) {
|
||||
@ -54,7 +56,7 @@ class RateLimiter extends \yii\filters\RateLimiter {
|
||||
$key = $this->buildKey($ip);
|
||||
|
||||
$redis = $this->getRedis();
|
||||
$countRequests = intval($redis->executeCommand('INCR', [$key]));
|
||||
$countRequests = (int)$redis->incr($key);
|
||||
if ($countRequests === 1) {
|
||||
$redis->executeCommand('EXPIRE', [$key, $this->limitTime]);
|
||||
}
|
||||
@ -65,7 +67,7 @@ class RateLimiter extends \yii\filters\RateLimiter {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \yii\redis\Connection
|
||||
* @return \common\components\Redis\Connection
|
||||
*/
|
||||
public function getRedis() {
|
||||
return Yii::$app->redis;
|
||||
|
@ -128,7 +128,7 @@ class JoinForm extends Model {
|
||||
$account = $accessModel->account;
|
||||
}
|
||||
|
||||
/** @var MinecraftAccessKey|\common\models\OauthAccessToken $accessModel */
|
||||
/** @var MinecraftAccessKey|\api\components\OAuth2\Entities\AccessTokenEntity $accessModel */
|
||||
if ($accessModel->isExpired()) {
|
||||
Session::error("User with access_token = '{$accessToken}' failed join by expired access_token.");
|
||||
throw new ForbiddenOperationException('Expired access_token.');
|
||||
|
@ -1,26 +0,0 @@
|
||||
<?php
|
||||
namespace api\traits;
|
||||
|
||||
|
||||
trait ApiNormalize {
|
||||
|
||||
/**
|
||||
* Метод убирает все ошибки для поля, кроме первой и возвращает значения в формате
|
||||
* [
|
||||
* 'field1' => 'first_error_of_field1',
|
||||
* 'field2' => 'first_error_of_field2',
|
||||
* ]
|
||||
*
|
||||
* @param array $errors
|
||||
* @return array
|
||||
*/
|
||||
public function normalizeModelErrors(array $errors) {
|
||||
$normalized = [];
|
||||
foreach($errors as $attribute => $attrErrors) {
|
||||
$normalized[$attribute] = $attrErrors[0];
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
}
|
@ -17,10 +17,11 @@ class Yii extends \yii\BaseYii {
|
||||
* Used for properties that are identical for both WebApplication and ConsoleApplication
|
||||
*
|
||||
* @property \yii\swiftmailer\Mailer $mailer
|
||||
* @property \yii\redis\Connection $redis
|
||||
* @property \common\components\Redis\Connection $redis
|
||||
* @property \common\components\RabbitMQ\Component $amqp
|
||||
* @property \GuzzleHttp\Client $guzzle
|
||||
* @property \common\components\EmailRenderer $emailRenderer
|
||||
* @property \mito\sentry\Component $sentry
|
||||
*/
|
||||
abstract class BaseApplication extends yii\base\Application {
|
||||
}
|
||||
@ -29,10 +30,10 @@ abstract class BaseApplication extends yii\base\Application {
|
||||
* Class WebApplication
|
||||
* Include only Web application related components here
|
||||
*
|
||||
* @property \api\components\User\Component $user User component.
|
||||
* @property \api\components\ApiUser\Component $apiUser Api 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 \common\components\oauth\Component $oauth
|
||||
* @property \api\components\OAuth2\Component $oauth
|
||||
*
|
||||
* @method \api\components\User\Component getUser()
|
||||
*/
|
||||
|
@ -111,8 +111,8 @@ class Component extends \yii\base\Component {
|
||||
public function sendToExchange($exchangeName, $routingKey, $message, $exchangeArgs = [], $publishArgs = []) {
|
||||
$message = $this->prepareMessage($message);
|
||||
$channel = $this->getChannel();
|
||||
call_user_func_array([$channel, 'exchange_declare'], $this->prepareExchangeArgs($exchangeName, $exchangeArgs));
|
||||
call_user_func_array([$channel, 'basic_publish'], $this->preparePublishArgs($message, $exchangeName, $routingKey, $publishArgs));
|
||||
$channel->exchange_declare(...$this->prepareExchangeArgs($exchangeName, $exchangeArgs));
|
||||
$channel->basic_publish(...$this->preparePublishArgs($message, $exchangeName, $routingKey, $publishArgs));
|
||||
}
|
||||
|
||||
/**
|
||||
|
13
common/components/Redis/Cache.php
Normal file
13
common/components/Redis/Cache.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace common\components\Redis;
|
||||
|
||||
use yii\di\Instance;
|
||||
|
||||
class Cache extends \yii\redis\Cache {
|
||||
|
||||
public function init() {
|
||||
\yii\caching\Cache::init();
|
||||
$this->redis = Instance::ensure($this->redis, ConnectionInterface::class);
|
||||
}
|
||||
|
||||
}
|
415
common/components/Redis/Connection.php
Normal file
415
common/components/Redis/Connection.php
Normal file
@ -0,0 +1,415 @@
|
||||
<?php
|
||||
namespace common\components\Redis;
|
||||
|
||||
use Predis\Client;
|
||||
use Predis\ClientInterface;
|
||||
use yii\base\Component;
|
||||
|
||||
/**
|
||||
* Interface defining a client able to execute commands against Redis.
|
||||
*
|
||||
* All the commands exposed by the client generally have the same signature as
|
||||
* described by the Redis documentation, but some of them offer an additional
|
||||
* and more friendly interface to ease programming which is described in the
|
||||
* following list of methods:
|
||||
*
|
||||
* @method int del(array $keys)
|
||||
* @method string dump($key)
|
||||
* @method int exists($key)
|
||||
* @method int expire($key, $seconds)
|
||||
* @method int expireat($key, $timestamp)
|
||||
* @method array keys($pattern)
|
||||
* @method int move($key, $db)
|
||||
* @method mixed object($subcommand, $key)
|
||||
* @method int persist($key)
|
||||
* @method int pexpire($key, $milliseconds)
|
||||
* @method int pexpireat($key, $timestamp)
|
||||
* @method int pttl($key)
|
||||
* @method string randomkey()
|
||||
* @method mixed rename($key, $target)
|
||||
* @method int renamenx($key, $target)
|
||||
* @method array scan($cursor, array $options = null)
|
||||
* @method array sort($key, array $options = null)
|
||||
* @method int ttl($key)
|
||||
* @method mixed type($key)
|
||||
* @method int append($key, $value)
|
||||
* @method int bitcount($key, $start = null, $end = null)
|
||||
* @method int bitop($operation, $destkey, $key)
|
||||
* @method int decr($key)
|
||||
* @method int decrby($key, $decrement)
|
||||
* @method string get($key)
|
||||
* @method int getbit($key, $offset)
|
||||
* @method string getrange($key, $start, $end)
|
||||
* @method string getset($key, $value)
|
||||
* @method int incr($key)
|
||||
* @method int incrby($key, $increment)
|
||||
* @method string incrbyfloat($key, $increment)
|
||||
* @method array mget(array $keys)
|
||||
* @method mixed mset(array $dictionary)
|
||||
* @method int msetnx(array $dictionary)
|
||||
* @method mixed psetex($key, $milliseconds, $value)
|
||||
* @method mixed set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
|
||||
* @method int setbit($key, $offset, $value)
|
||||
* @method int setex($key, $seconds, $value)
|
||||
* @method int setnx($key, $value)
|
||||
* @method int setrange($key, $offset, $value)
|
||||
* @method int strlen($key)
|
||||
* @method int hdel($key, array $fields)
|
||||
* @method int hexists($key, $field)
|
||||
* @method string hget($key, $field)
|
||||
* @method array hgetall($key)
|
||||
* @method int hincrby($key, $field, $increment)
|
||||
* @method string hincrbyfloat($key, $field, $increment)
|
||||
* @method array hkeys($key)
|
||||
* @method int hlen($key)
|
||||
* @method array hmget($key, array $fields)
|
||||
* @method mixed hmset($key, array $dictionary)
|
||||
* @method array hscan($key, $cursor, array $options = null)
|
||||
* @method int hset($key, $field, $value)
|
||||
* @method int hsetnx($key, $field, $value)
|
||||
* @method array hvals($key)
|
||||
* @method array blpop(array $keys, $timeout)
|
||||
* @method array brpop(array $keys, $timeout)
|
||||
* @method array brpoplpush($source, $destination, $timeout)
|
||||
* @method string lindex($key, $index)
|
||||
* @method int linsert($key, $whence, $pivot, $value)
|
||||
* @method int llen($key)
|
||||
* @method string lpop($key)
|
||||
* @method int lpush($key, array $values)
|
||||
* @method int lpushx($key, $value)
|
||||
* @method array lrange($key, $start, $stop)
|
||||
* @method int lrem($key, $count, $value)
|
||||
* @method mixed lset($key, $index, $value)
|
||||
* @method mixed ltrim($key, $start, $stop)
|
||||
* @method string rpop($key)
|
||||
* @method string rpoplpush($source, $destination)
|
||||
* @method int rpush($key, array $values)
|
||||
* @method int rpushx($key, $value)
|
||||
* @method int sadd($key, array $members)
|
||||
* @method int scard($key)
|
||||
* @method array sdiff(array $keys)
|
||||
* @method int sdiffstore($destination, array $keys)
|
||||
* @method array sinter(array $keys)
|
||||
* @method int sinterstore($destination, array $keys)
|
||||
* @method int sismember($key, $member)
|
||||
* @method array smembers($key)
|
||||
* @method int smove($source, $destination, $member)
|
||||
* @method string spop($key)
|
||||
* @method string srandmember($key, $count = null)
|
||||
* @method int srem($key, $member)
|
||||
* @method array sscan($key, $cursor, array $options = null)
|
||||
* @method array sunion(array $keys)
|
||||
* @method int sunionstore($destination, array $keys)
|
||||
* @method int zadd($key, array $membersAndScoresDictionary)
|
||||
* @method int zcard($key)
|
||||
* @method string zcount($key, $min, $max)
|
||||
* @method string zincrby($key, $increment, $member)
|
||||
* @method int zinterstore($destination, array $keys, array $options = null)
|
||||
* @method array zrange($key, $start, $stop, array $options = null)
|
||||
* @method array zrangebyscore($key, $min, $max, array $options = null)
|
||||
* @method int zrank($key, $member)
|
||||
* @method int zrem($key, $member)
|
||||
* @method int zremrangebyrank($key, $start, $stop)
|
||||
* @method int zremrangebyscore($key, $min, $max)
|
||||
* @method array zrevrange($key, $start, $stop, array $options = null)
|
||||
* @method array zrevrangebyscore($key, $min, $max, array $options = null)
|
||||
* @method int zrevrank($key, $member)
|
||||
* @method int zunionstore($destination, array $keys, array $options = null)
|
||||
* @method string zscore($key, $member)
|
||||
* @method array zscan($key, $cursor, array $options = null)
|
||||
* @method array zrangebylex($key, $start, $stop, array $options = null)
|
||||
* @method int zremrangebylex($key, $min, $max)
|
||||
* @method int zlexcount($key, $min, $max)
|
||||
* @method int pfadd($key, array $elements)
|
||||
* @method mixed pfmerge($destinationKey, array $sourceKeys)
|
||||
* @method int pfcount(array $keys)
|
||||
* @method mixed pubsub($subcommand, $argument)
|
||||
* @method int publish($channel, $message)
|
||||
* @method mixed discard()
|
||||
* @method array exec()
|
||||
* @method mixed multi()
|
||||
* @method mixed unwatch()
|
||||
* @method mixed watch($key)
|
||||
* @method mixed eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
|
||||
* @method mixed evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
|
||||
* @method mixed script($subcommand, $argument = null)
|
||||
* @method mixed auth($password)
|
||||
* @method string echo($message)
|
||||
* @method mixed ping($message = null)
|
||||
* @method mixed select($database)
|
||||
* @method mixed bgrewriteaof()
|
||||
* @method mixed bgsave()
|
||||
* @method mixed client($subcommand, $argument = null)
|
||||
* @method mixed config($subcommand, $argument = null)
|
||||
* @method int dbsize()
|
||||
* @method mixed flushall()
|
||||
* @method mixed flushdb()
|
||||
* @method array info($section = null)
|
||||
* @method int lastsave()
|
||||
* @method mixed save()
|
||||
* @method mixed slaveof($host, $port)
|
||||
* @method mixed slowlog($subcommand, $argument = null)
|
||||
* @method array time()
|
||||
* @method array command()
|
||||
*/
|
||||
class Connection extends Component implements ConnectionInterface {
|
||||
|
||||
/**
|
||||
* @var array List of available redis commands http://redis.io/commands
|
||||
*/
|
||||
const REDIS_COMMANDS = [
|
||||
'BLPOP', // key [key ...] timeout Remove and get the first element in a list, or block until one is available
|
||||
'BRPOP', // key [key ...] timeout Remove and get the last element in a list, or block until one is available
|
||||
'BRPOPLPUSH', // source destination timeout Pop a value from a list, push it to another list and return it; or block until one is available
|
||||
'CLIENT KILL', // ip:port Kill the connection of a client
|
||||
'CLIENT LIST', // Get the list of client connections
|
||||
'CLIENT GETNAME', // Get the current connection name
|
||||
'CLIENT SETNAME', // connection-name Set the current connection name
|
||||
'CONFIG GET', // parameter Get the value of a configuration parameter
|
||||
'CONFIG SET', // parameter value Set a configuration parameter to the given value
|
||||
'CONFIG RESETSTAT', // Reset the stats returned by INFO
|
||||
'DBSIZE', // Return the number of keys in the selected database
|
||||
'DEBUG OBJECT', // key Get debugging information about a key
|
||||
'DEBUG SEGFAULT', // Make the server crash
|
||||
'DECR', // key Decrement the integer value of a key by one
|
||||
'DECRBY', // key decrement Decrement the integer value of a key by the given number
|
||||
'DEL', // key [key ...] Delete a key
|
||||
'DISCARD', // Discard all commands issued after MULTI
|
||||
'DUMP', // key Return a serialized version of the value stored at the specified key.
|
||||
'ECHO', // message Echo the given string
|
||||
'EVAL', // script numkeys key [key ...] arg [arg ...] Execute a Lua script server side
|
||||
'EVALSHA', // sha1 numkeys key [key ...] arg [arg ...] Execute a Lua script server side
|
||||
'EXEC', // Execute all commands issued after MULTI
|
||||
'EXISTS', // key Determine if a key exists
|
||||
'EXPIRE', // key seconds Set a key's time to live in seconds
|
||||
'EXPIREAT', // key timestamp Set the expiration for a key as a UNIX timestamp
|
||||
'FLUSHALL', // Remove all keys from all databases
|
||||
'FLUSHDB', // Remove all keys from the current database
|
||||
'GET', // key Get the value of a key
|
||||
'GETBIT', // key offset Returns the bit value at offset in the string value stored at key
|
||||
'GETRANGE', // key start end Get a substring of the string stored at a key
|
||||
'GETSET', // key value Set the string value of a key and return its old value
|
||||
'HDEL', // key field [field ...] Delete one or more hash fields
|
||||
'HEXISTS', // key field Determine if a hash field exists
|
||||
'HGET', // key field Get the value of a hash field
|
||||
'HGETALL', // key Get all the fields and values in a hash
|
||||
'HINCRBY', // key field increment Increment the integer value of a hash field by the given number
|
||||
'HINCRBYFLOAT', // key field increment Increment the float value of a hash field by the given amount
|
||||
'HKEYS', // key Get all the fields in a hash
|
||||
'HLEN', // key Get the number of fields in a hash
|
||||
'HMGET', // key field [field ...] Get the values of all the given hash fields
|
||||
'HMSET', // key field value [field value ...] Set multiple hash fields to multiple values
|
||||
'HSET', // key field value Set the string value of a hash field
|
||||
'HSETNX', // key field value Set the value of a hash field, only if the field does not exist
|
||||
'HVALS', // key Get all the values in a hash
|
||||
'INCR', // key Increment the integer value of a key by one
|
||||
'INCRBY', // key increment Increment the integer value of a key by the given amount
|
||||
'INCRBYFLOAT', // key increment Increment the float value of a key by the given amount
|
||||
'INFO', // [section] Get information and statistics about the server
|
||||
'KEYS', // pattern Find all keys matching the given pattern
|
||||
'LASTSAVE', // Get the UNIX time stamp of the last successful save to disk
|
||||
'LINDEX', // key index Get an element from a list by its index
|
||||
'LINSERT', // key BEFORE|AFTER pivot value Insert an element before or after another element in a list
|
||||
'LLEN', // key Get the length of a list
|
||||
'LPOP', // key Remove and get the first element in a list
|
||||
'LPUSH', // key value [value ...] Prepend one or multiple values to a list
|
||||
'LPUSHX', // key value Prepend a value to a list, only if the list exists
|
||||
'LRANGE', // key start stop Get a range of elements from a list
|
||||
'LREM', // key count value Remove elements from a list
|
||||
'LSET', // key index value Set the value of an element in a list by its index
|
||||
'LTRIM', // key start stop Trim a list to the specified range
|
||||
'MGET', // key [key ...] Get the values of all the given keys
|
||||
'MIGRATE', // host port key destination-db timeout Atomically transfer a key from a Redis instance to another one.
|
||||
'MONITOR', // Listen for all requests received by the server in real time
|
||||
'MOVE', // key db Move a key to another database
|
||||
'MSET', // key value [key value ...] Set multiple keys to multiple values
|
||||
'MSETNX', // key value [key value ...] Set multiple keys to multiple values, only if none of the keys exist
|
||||
'MULTI', // Mark the start of a transaction block
|
||||
'OBJECT', // subcommand [arguments [arguments ...]] Inspect the internals of Redis objects
|
||||
'PERSIST', // key Remove the expiration from a key
|
||||
'PEXPIRE', // key milliseconds Set a key's time to live in milliseconds
|
||||
'PEXPIREAT', // key milliseconds-timestamp Set the expiration for a key as a UNIX timestamp specified in milliseconds
|
||||
'PING', // Ping the server
|
||||
'PSETEX', // key milliseconds value Set the value and expiration in milliseconds of a key
|
||||
'PSUBSCRIBE', // pattern [pattern ...] Listen for messages published to channels matching the given patterns
|
||||
'PTTL', // key Get the time to live for a key in milliseconds
|
||||
'PUBLISH', // channel message Post a message to a channel
|
||||
'PUNSUBSCRIBE', // [pattern [pattern ...]] Stop listening for messages posted to channels matching the given patterns
|
||||
'QUIT', // Close the connection
|
||||
'RANDOMKEY', // Return a random key from the keyspace
|
||||
'RENAME', // key newkey Rename a key
|
||||
'RENAMENX', // key newkey Rename a key, only if the new key does not exist
|
||||
'RESTORE', // key ttl serialized-value Create a key using the provided serialized value, previously obtained using DUMP.
|
||||
'RPOP', // key Remove and get the last element in a list
|
||||
'RPOPLPUSH', // source destination Remove the last element in a list, append it to another list and return it
|
||||
'RPUSH', // key value [value ...] Append one or multiple values to a list
|
||||
'RPUSHX', // key value Append a value to a list, only if the list exists
|
||||
'SADD', // key member [member ...] Add one or more members to a set
|
||||
'SAVE', // Synchronously save the dataset to disk
|
||||
'SCARD', // key Get the number of members in a set
|
||||
'SCRIPT EXISTS', // script [script ...] Check existence of scripts in the script cache.
|
||||
'SCRIPT FLUSH', // Remove all the scripts from the script cache.
|
||||
'SCRIPT KILL', // Kill the script currently in execution.
|
||||
'SCRIPT LOAD', // script Load the specified Lua script into the script cache.
|
||||
'SDIFF', // key [key ...] Subtract multiple sets
|
||||
'SDIFFSTORE', // destination key [key ...] Subtract multiple sets and store the resulting set in a key
|
||||
'SELECT', // index Change the selected database for the current connection
|
||||
'SET', // key value Set the string value of a key
|
||||
'SETBIT', // key offset value Sets or clears the bit at offset in the string value stored at key
|
||||
'SETEX', // key seconds value Set the value and expiration of a key
|
||||
'SETNX', // key value Set the value of a key, only if the key does not exist
|
||||
'SETRANGE', // key offset value Overwrite part of a string at key starting at the specified offset
|
||||
'SHUTDOWN', // [NOSAVE] [SAVE] Synchronously save the dataset to disk and then shut down the server
|
||||
'SINTER', // key [key ...] Intersect multiple sets
|
||||
'SINTERSTORE', // destination key [key ...] Intersect multiple sets and store the resulting set in a key
|
||||
'SISMEMBER', // key member Determine if a given value is a member of a set
|
||||
'SLAVEOF', // host port Make the server a slave of another instance, or promote it as master
|
||||
'SLOWLOG', // subcommand [argument] Manages the Redis slow queries log
|
||||
'SMEMBERS', // key Get all the members in a set
|
||||
'SMOVE', // source destination member Move a member from one set to another
|
||||
'SORT', // key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination] Sort the elements in a list, set or sorted set
|
||||
'SPOP', // key Remove and return a random member from a set
|
||||
'SRANDMEMBER', // key [count] Get one or multiple random members from a set
|
||||
'SREM', // key member [member ...] Remove one or more members from a set
|
||||
'STRLEN', // key Get the length of the value stored in a key
|
||||
'SUBSCRIBE', // channel [channel ...] Listen for messages published to the given channels
|
||||
'SUNION', // key [key ...] Add multiple sets
|
||||
'SUNIONSTORE', // destination key [key ...] Add multiple sets and store the resulting set in a key
|
||||
'SYNC', // Internal command used for replication
|
||||
'TIME', // Return the current server time
|
||||
'TTL', // key Get the time to live for a key
|
||||
'TYPE', // key Determine the type stored at key
|
||||
'UNSUBSCRIBE', // [channel [channel ...]] Stop listening for messages posted to the given channels
|
||||
'UNWATCH', // Forget about all watched keys
|
||||
'WATCH', // key [key ...] Watch the given keys to determine execution of the MULTI/EXEC block
|
||||
'ZADD', // key score member [score member ...] Add one or more members to a sorted set, or update its score if it already exists
|
||||
'ZCARD', // key Get the number of members in a sorted set
|
||||
'ZCOUNT', // key min max Count the members in a sorted set with scores within the given values
|
||||
'ZINCRBY', // key increment member Increment the score of a member in a sorted set
|
||||
'ZINTERSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Intersect multiple sorted sets and store the resulting sorted set in a new key
|
||||
'ZRANGE', // key start stop [WITHSCORES] Return a range of members in a sorted set, by index
|
||||
'ZRANGEBYSCORE', // key min max [WITHSCORES] [LIMIT offset count] Return a range of members in a sorted set, by score
|
||||
'ZRANK', // key member Determine the index of a member in a sorted set
|
||||
'ZREM', // key member [member ...] Remove one or more members from a sorted set
|
||||
'ZREMRANGEBYRANK', // key start stop Remove all members in a sorted set within the given indexes
|
||||
'ZREMRANGEBYSCORE', // key min max Remove all members in a sorted set within the given scores
|
||||
'ZREVRANGE', // key start stop [WITHSCORES] Return a range of members in a sorted set, by index, with scores ordered from high to low
|
||||
'ZREVRANGEBYSCORE', // key max min [WITHSCORES] [LIMIT offset count] Return a range of members in a sorted set, by score, with scores ordered from high to low
|
||||
'ZREVRANK', // key member Determine the index of a member in a sorted set, with scores ordered from high to low
|
||||
'ZSCORE', // key member Get the score associated with the given member in a sorted set
|
||||
'ZUNIONSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Add multiple sorted sets and store the resulting sorted set in a new key
|
||||
'GEOADD', // key longitude latitude member [longitude latitude member ...] Add point
|
||||
'GEODIST', // key member1 member2 [unit] Return the distance between two members
|
||||
'GEOHASH', // key member [member ...] Return valid Geohash strings
|
||||
'GEOPOS', // key member [member ...] Return the positions (longitude,latitude)
|
||||
'GEORADIUS', // key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] Return the members
|
||||
'GEORADIUSBYMEMBER', // key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string the hostname or ip address to use for connecting to the redis server. Defaults to 'localhost'.
|
||||
* If [[unixSocket]] is specified, hostname and port will be ignored.
|
||||
*/
|
||||
public $hostname = 'localhost';
|
||||
|
||||
/**
|
||||
* @var integer the port to use for connecting to the redis server. Default port is 6379.
|
||||
* If [[unixSocket]] is specified, hostname and port will be ignored.
|
||||
*/
|
||||
public $port = 6379;
|
||||
|
||||
/**
|
||||
* @var string the unix socket path (e.g. `/var/run/redis/redis.sock`) to use for connecting to the redis server.
|
||||
* This can be used instead of [[hostname]] and [[port]] to connect to the server using a unix socket.
|
||||
* If a unix socket path is specified, [[hostname]] and [[port]] will be ignored.
|
||||
*/
|
||||
public $unixSocket;
|
||||
|
||||
/**
|
||||
* @var string the password for establishing DB connection. Defaults to null meaning no AUTH command is send.
|
||||
* See http://redis.io/commands/auth
|
||||
*/
|
||||
public $password;
|
||||
|
||||
/**
|
||||
* @var integer the redis database to use. This is an integer value starting from 0. Defaults to 0.
|
||||
*/
|
||||
public $database = 0;
|
||||
|
||||
/**
|
||||
* @var float timeout to use for connection to redis. If not set the timeout set in php.ini will be used: ini_get("default_socket_timeout")
|
||||
*/
|
||||
public $connectionTimeout;
|
||||
|
||||
/**
|
||||
* @var float timeout to use for redis socket when reading and writing data. If not set the php default value will be used.
|
||||
*/
|
||||
public $dataTimeout;
|
||||
|
||||
/**
|
||||
* @var integer Bitmask field which may be set to any combination of connection flags passed to [stream_socket_client()](http://php.net/manual/en/function.stream-socket-client.php).
|
||||
* Currently the select of connection flags is limited to `STREAM_CLIENT_CONNECT` (default), `STREAM_CLIENT_ASYNC_CONNECT` and `STREAM_CLIENT_PERSISTENT`.
|
||||
* @see http://php.net/manual/en/function.stream-socket-client.php
|
||||
*/
|
||||
public $socketClientFlags = STREAM_CLIENT_CONNECT;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
public $parameters;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $options = [];
|
||||
|
||||
/**
|
||||
* @var ClientInterface
|
||||
*/
|
||||
private $_client;
|
||||
|
||||
public function getConnection() : ClientInterface {
|
||||
if ($this->_client === null) {
|
||||
$this->_client = new Client($this->prepareParams(), $this->options);
|
||||
}
|
||||
|
||||
return $this->_client;
|
||||
}
|
||||
|
||||
public function __call($name, $params) {
|
||||
$redisCommand = mb_strtoupper($name);
|
||||
if (in_array($redisCommand, self::REDIS_COMMANDS)) {
|
||||
return $this->executeCommand($name, $params);
|
||||
}
|
||||
|
||||
return parent::__call($name, $params);
|
||||
}
|
||||
|
||||
public function executeCommand(string $name, array $params = []) {
|
||||
return $this->getConnection()->$name(...$params);
|
||||
}
|
||||
|
||||
private function prepareParams() {
|
||||
if ($this->parameters !== null) {
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
if ($this->unixSocket) {
|
||||
$parameters = [
|
||||
'scheme' => 'unix',
|
||||
'path' => $this->unixSocket,
|
||||
];
|
||||
} else {
|
||||
$parameters = [
|
||||
'scheme' => 'tcp',
|
||||
'host' => $this->hostname,
|
||||
'port' => $this->port,
|
||||
];
|
||||
}
|
||||
|
||||
return array_merge($parameters, [
|
||||
'database' => $this->database,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
19
common/components/Redis/ConnectionInterface.php
Normal file
19
common/components/Redis/ConnectionInterface.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
namespace common\components\Redis;
|
||||
|
||||
interface ConnectionInterface {
|
||||
|
||||
/**
|
||||
* @return ConnectionInterface
|
||||
*/
|
||||
public function getConnection();
|
||||
|
||||
/**
|
||||
* @param string $name Command, that should be executed
|
||||
* @param array $params Arguments for this command
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function executeCommand(string $name, array $params = []);
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace common\components\redis;
|
||||
namespace common\components\Redis;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Yii;
|
||||
@ -9,35 +9,52 @@ class Key {
|
||||
protected $key;
|
||||
|
||||
/**
|
||||
* @return \yii\redis\Connection
|
||||
* @return Connection
|
||||
*/
|
||||
public function getRedis() {
|
||||
return Yii::$app->redis;
|
||||
}
|
||||
|
||||
public function getKey() {
|
||||
public function getKey() : string {
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function getValue() {
|
||||
return json_decode($this->getRedis()->get($this->key), true);
|
||||
return $this->getRedis()->get($this->key);
|
||||
}
|
||||
|
||||
public function setValue($value) {
|
||||
$this->getRedis()->set($this->key, json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
$this->getRedis()->set($this->key, $value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
$this->getRedis()->executeCommand('DEL', [$this->key]);
|
||||
$this->getRedis()->del($this->key);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function expire($ttl) {
|
||||
$this->getRedis()->executeCommand('EXPIRE', [$this->key, $ttl]);
|
||||
public function exists() : bool {
|
||||
return (bool)$this->getRedis()->exists($this->key);
|
||||
}
|
||||
|
||||
public function expire(int $ttl) {
|
||||
$this->getRedis()->expire($this->key, $ttl);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function expireAt(int $unixTimestamp) {
|
||||
$this->getRedis()->expireat($this->key, $unixTimestamp);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function __construct(...$key) {
|
||||
if (empty($key)) {
|
||||
throw new InvalidArgumentException('You must specify at least one key.');
|
||||
}
|
||||
|
||||
$this->key = $this->buildKey($key);
|
||||
}
|
||||
|
||||
private function buildKey(array $parts) {
|
||||
$keyParts = [];
|
||||
foreach($parts as $part) {
|
||||
@ -47,12 +64,4 @@ class Key {
|
||||
return implode(':', $keyParts);
|
||||
}
|
||||
|
||||
public function __construct(...$key) {
|
||||
if (empty($key)) {
|
||||
throw new InvalidArgumentException('You must specify at least one key.');
|
||||
}
|
||||
|
||||
$this->key = $this->buildKey($key);
|
||||
}
|
||||
|
||||
}
|
46
common/components/Redis/Set.php
Normal file
46
common/components/Redis/Set.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace common\components\Redis;
|
||||
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
|
||||
class Set extends Key implements IteratorAggregate {
|
||||
|
||||
public function add($value) {
|
||||
$this->getRedis()->sadd($this->key, $value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function remove($value) {
|
||||
$this->getRedis()->srem($this->key, $value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function members() {
|
||||
return $this->getRedis()->smembers($this->key);
|
||||
}
|
||||
|
||||
public function getValue() {
|
||||
return $this->members();
|
||||
}
|
||||
|
||||
public function exists(string $value = null) : bool {
|
||||
if ($value === null) {
|
||||
return parent::exists();
|
||||
} else {
|
||||
return (bool)$this->getRedis()->sismember($this->key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function diff(array $sets) {
|
||||
return $this->getRedis()->sdiff([$this->key, implode(' ', $sets)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getIterator() {
|
||||
return new ArrayIterator($this->members());
|
||||
}
|
||||
|
||||
}
|
18
common/components/Sentry/Component.php
Normal file
18
common/components/Sentry/Component.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace common\components\Sentry;
|
||||
|
||||
use Yii;
|
||||
|
||||
class Component extends \mito\sentry\Component {
|
||||
|
||||
public $jsNotifier = false;
|
||||
|
||||
public function init() {
|
||||
if (is_array($this->client) && !isset($this->client['release'])) {
|
||||
$this->client['release'] = Yii::$app->version;
|
||||
}
|
||||
|
||||
parent::init();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
<?php
|
||||
namespace common\components\redis;
|
||||
|
||||
use IteratorAggregate;
|
||||
use Yii;
|
||||
|
||||
class Set extends Key implements IteratorAggregate {
|
||||
|
||||
/**
|
||||
* @return \yii\redis\Connection
|
||||
*/
|
||||
public static function getDb() {
|
||||
return Yii::$app->redis;
|
||||
}
|
||||
|
||||
public function add($value) {
|
||||
$this->getDb()->executeCommand('SADD', [$this->key, $value]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function remove($value) {
|
||||
$this->getDb()->executeCommand('SREM', [$this->key, $value]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function members() {
|
||||
return $this->getDb()->executeCommand('SMEMBERS', [$this->key]);
|
||||
}
|
||||
|
||||
public function getValue() {
|
||||
return $this->members();
|
||||
}
|
||||
|
||||
public function exists($value) {
|
||||
return !!$this->getDb()->executeCommand('SISMEMBER', [$this->key, $value]);
|
||||
}
|
||||
|
||||
public function diff(array $sets) {
|
||||
return $this->getDb()->executeCommand('SDIFF', [$this->key, implode(' ', $sets)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getIterator() {
|
||||
return new \ArrayIterator($this->members());
|
||||
}
|
||||
|
||||
}
|
@ -6,22 +6,5 @@ return [
|
||||
'schemaCacheDuration' => 3600,
|
||||
'schemaCache' => 'cache',
|
||||
],
|
||||
'mailer' => [
|
||||
'useFileTransport' => false,
|
||||
'transport' => [
|
||||
'class' => Swift_SmtpTransport::class,
|
||||
'host' => 'ely.by',
|
||||
'username' => getenv('SMTP_USER'),
|
||||
'password' => getenv('SMTP_PASS'),
|
||||
'port' => 587,
|
||||
'encryption' => 'tls',
|
||||
'streamOptions' => [
|
||||
'ssl' => [
|
||||
'allow_self_signed' => true,
|
||||
'verify_peer' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
@ -1,16 +1,17 @@
|
||||
<?php
|
||||
return [
|
||||
'version' => '1.1.3',
|
||||
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
|
||||
'components' => [
|
||||
'cache' => [
|
||||
'class' => yii\redis\Cache::class,
|
||||
'class' => common\components\Redis\Cache::class,
|
||||
'redis' => 'redis',
|
||||
],
|
||||
'db' => [
|
||||
'class' => yii\db\Connection::class,
|
||||
'dsn' => 'mysql:host=db;dbname=' . getenv('MYSQL_DATABASE'),
|
||||
'username' => getenv('MYSQL_USER'),
|
||||
'password' => getenv('MYSQL_PASSWORD'),
|
||||
'dsn' => 'mysql:host=' . (getenv('DB_HOST') ?: 'db') . ';dbname=' . getenv('DB_DATABASE'),
|
||||
'username' => getenv('DB_USER'),
|
||||
'password' => getenv('DB_PASSWORD'),
|
||||
'charset' => 'utf8',
|
||||
'schemaMap' => [
|
||||
'mysql' => common\db\mysql\Schema::class,
|
||||
@ -19,24 +20,47 @@ return [
|
||||
'mailer' => [
|
||||
'class' => yii\swiftmailer\Mailer::class,
|
||||
'viewPath' => '@common/mail',
|
||||
'transport' => [
|
||||
'class' => Swift_SmtpTransport::class,
|
||||
'host' => 'ely.by',
|
||||
'username' => getenv('SMTP_USER'),
|
||||
'password' => getenv('SMTP_PASS'),
|
||||
'port' => getenv('SMTP_PORT') ?: 587,
|
||||
'encryption' => 'tls',
|
||||
'streamOptions' => [
|
||||
'ssl' => [
|
||||
'allow_self_signed' => true,
|
||||
'verify_peer' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'sentry' => [
|
||||
'class' => common\components\Sentry\Component::class,
|
||||
'enabled' => !empty(getenv('SENTRY_DSN')),
|
||||
'dsn' => getenv('SENTRY_DSN'),
|
||||
'environment' => YII_ENV_DEV ? 'development' : 'production',
|
||||
'client' => [
|
||||
'curl_method' => 'async',
|
||||
],
|
||||
],
|
||||
'security' => [
|
||||
'passwordHashStrategy' => 'password_hash',
|
||||
],
|
||||
'redis' => [
|
||||
'class' => yii\redis\Connection::class,
|
||||
'hostname' => 'redis',
|
||||
'password' => null,
|
||||
'port' => 6379,
|
||||
'database' => 0,
|
||||
'class' => common\components\Redis\Connection::class,
|
||||
'hostname' => getenv('REDIS_HOST') ?: 'redis',
|
||||
'password' => getenv('REDIS_PASS') ?: null,
|
||||
'port' => getenv('REDIS_PORT') ?: 6379,
|
||||
'database' => getenv('REDIS_DATABASE') ?: 0,
|
||||
],
|
||||
'amqp' => [
|
||||
'class' => common\components\RabbitMQ\Component::class,
|
||||
'host' => 'rabbitmq',
|
||||
'port' => 5672,
|
||||
'user' => getenv('RABBITMQ_DEFAULT_USER'),
|
||||
'password' => getenv('RABBITMQ_DEFAULT_PASS'),
|
||||
'vhost' => getenv('RABBITMQ_DEFAULT_VHOST'),
|
||||
'host' => getenv('RABBITMQ_HOST') ?: 'rabbitmq',
|
||||
'port' => getenv('RABBITMQ_PORT') ?: 5672,
|
||||
'user' => getenv('RABBITMQ_USER'),
|
||||
'password' => getenv('RABBITMQ_PASS'),
|
||||
'vhost' => getenv('RABBITMQ_VHOST'),
|
||||
],
|
||||
'guzzle' => [
|
||||
'class' => GuzzleHttp\Client::class,
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace common\models;
|
||||
|
||||
use common\components\redis\Set;
|
||||
use common\components\Redis\Set;
|
||||
use yii\db\ActiveRecord;
|
||||
|
||||
/**
|
||||
@ -15,6 +15,7 @@ use yii\db\ActiveRecord;
|
||||
*
|
||||
* Отношения:
|
||||
* @property OauthSession $session
|
||||
* @deprecated
|
||||
*/
|
||||
class OauthAccessToken extends ActiveRecord {
|
||||
|
||||
|
@ -1,21 +1,20 @@
|
||||
<?php
|
||||
namespace common\models;
|
||||
|
||||
use yii\db\ActiveRecord;
|
||||
|
||||
/**
|
||||
* Поля:
|
||||
* @property string $id
|
||||
*/
|
||||
class OauthScope extends ActiveRecord {
|
||||
class OauthScope {
|
||||
|
||||
const OFFLINE_ACCESS = 'offline_access';
|
||||
const MINECRAFT_SERVER_SESSION = 'minecraft_server_session';
|
||||
const ACCOUNT_INFO = 'account_info';
|
||||
const ACCOUNT_EMAIL = 'account_email';
|
||||
|
||||
public static function tableName() {
|
||||
return '{{%oauth_scopes}}';
|
||||
public static function getScopes() : array {
|
||||
return [
|
||||
self::OFFLINE_ACCESS,
|
||||
self::MINECRAFT_SERVER_SESSION,
|
||||
self::ACCOUNT_INFO,
|
||||
self::ACCOUNT_EMAIL,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,22 +1,23 @@
|
||||
<?php
|
||||
namespace common\models;
|
||||
|
||||
use common\components\redis\Set;
|
||||
use common\components\Redis\Set;
|
||||
use Yii;
|
||||
use yii\base\ErrorException;
|
||||
use yii\db\ActiveRecord;
|
||||
|
||||
/**
|
||||
* Поля:
|
||||
* @property integer $id
|
||||
* @property string $owner_type
|
||||
* @property string $owner_id
|
||||
* @property string $client_id
|
||||
* @property string $client_redirect_uri
|
||||
* @property integer $id
|
||||
* @property string $owner_type
|
||||
* @property string $owner_id
|
||||
* @property string $client_id
|
||||
* @property string $client_redirect_uri
|
||||
*
|
||||
* Отношения
|
||||
* @property OauthAccessToken[] $accessTokens
|
||||
* @property OauthClient $client
|
||||
* @property Account $account
|
||||
* @property Set $scopes
|
||||
* @property OauthClient $client
|
||||
* @property Account $account
|
||||
* @property Set $scopes
|
||||
*/
|
||||
class OauthSession extends ActiveRecord {
|
||||
|
||||
@ -25,7 +26,7 @@ class OauthSession extends ActiveRecord {
|
||||
}
|
||||
|
||||
public function getAccessTokens() {
|
||||
return $this->hasMany(OauthAccessToken::class, ['session_id' => 'id']);
|
||||
throw new ErrorException('This method is possible, but not implemented');
|
||||
}
|
||||
|
||||
public function getClient() {
|
||||
@ -46,6 +47,14 @@ class OauthSession extends ActiveRecord {
|
||||
}
|
||||
|
||||
$this->getScopes()->delete();
|
||||
/** @var \api\components\OAuth2\Storage\RefreshTokenStorage $refreshTokensStorage */
|
||||
$refreshTokensStorage = Yii::$app->oauth->getAuthServer()->getRefreshTokenStorage();
|
||||
$refreshTokensSet = $refreshTokensStorage->sessionHash($this->id);
|
||||
foreach ($refreshTokensSet->members() as $refreshTokenId) {
|
||||
$refreshTokensStorage->delete($refreshTokensStorage->get($refreshTokenId));
|
||||
}
|
||||
|
||||
$refreshTokensSet->delete();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -15,17 +15,19 @@
|
||||
"minimum-stability": "stable",
|
||||
"require": {
|
||||
"php": "^7.0.6",
|
||||
"yiisoft/yii2": "2.0.9",
|
||||
"yiisoft/yii2": "2.0.10",
|
||||
"yiisoft/yii2-swiftmailer": "*",
|
||||
"ramsey/uuid": "^3.5.0",
|
||||
"league/oauth2-server": "~4.1.5",
|
||||
"league/oauth2-server": "dev-improvements#b9277ccd664dcb80a766b73674d21de686cb9dda",
|
||||
"yiisoft/yii2-redis": "~2.0.0",
|
||||
"guzzlehttp/guzzle": "^6.0.0",
|
||||
"php-amqplib/php-amqplib": "^2.6.2",
|
||||
"ely/yii2-tempmail-validator": "~1.0.0",
|
||||
"emarref/jwt": "~1.0.3",
|
||||
"ely/amqp-controller": "dev-master#d7f8cdbc66c45e477c9c7d5d509bc0c1b11fd3ec",
|
||||
"ely/email-renderer": "dev-master#38a148cd5081147acc31125ddc49966b149f65cf"
|
||||
"ely/email-renderer": "dev-master#38a148cd5081147acc31125ddc49966b149f65cf",
|
||||
"predis/predis": "^1.0",
|
||||
"mito/yii2-sentry": "dev-fix_init#27f00805cb906f73b2c6f8181c1c655decb9be70"
|
||||
},
|
||||
"require-dev": {
|
||||
"yiisoft/yii2-codeception": "*",
|
||||
@ -35,8 +37,7 @@
|
||||
"codeception/codeception": "~2.2.4",
|
||||
"codeception/specify": "*",
|
||||
"codeception/verify": "*",
|
||||
"phploc/phploc": "^3.0.1",
|
||||
"predis/predis": "^1.0"
|
||||
"phploc/phploc": "^3.0.1"
|
||||
},
|
||||
"config": {
|
||||
"process-timeout": 1800
|
||||
@ -53,6 +54,14 @@
|
||||
{
|
||||
"type": "git",
|
||||
"url": "git@gitlab.com:elyby/email-renderer.git"
|
||||
},
|
||||
{
|
||||
"type": "git",
|
||||
"url": "git@gitlab.ely.by:elyby/oauth2-server.git"
|
||||
},
|
||||
{
|
||||
"type": "git",
|
||||
"url": "git@github.com:erickskrauch/yii2-sentry.git"
|
||||
}
|
||||
],
|
||||
"scripts": {
|
||||
|
@ -13,6 +13,10 @@ return [
|
||||
'components' => [
|
||||
'log' => [
|
||||
'targets' => [
|
||||
[
|
||||
'class' => mito\sentry\Target::class,
|
||||
'levels' => ['error', 'warning'],
|
||||
],
|
||||
[
|
||||
'class' => yii\log\FileTarget::class,
|
||||
'levels' => ['error', 'warning'],
|
||||
|
22
console/controllers/CleanupController.php
Normal file
22
console/controllers/CleanupController.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace console\controllers;
|
||||
|
||||
use common\models\OauthAccessToken;
|
||||
use yii\console\Controller;
|
||||
|
||||
class CleanupController extends Controller {
|
||||
|
||||
public function actionAccessTokens() {
|
||||
$accessTokens = OauthAccessToken::find()
|
||||
->andWhere(['<', 'expire_time', time()])
|
||||
->each(1000);
|
||||
|
||||
foreach($accessTokens as $token) {
|
||||
/** @var OauthAccessToken $token */
|
||||
$token->delete();
|
||||
}
|
||||
|
||||
return self::EXIT_CODE_NORMAL;
|
||||
}
|
||||
|
||||
}
|
25
console/migrations/m161127_145211_remove_oauth_scopes.php
Normal file
25
console/migrations/m161127_145211_remove_oauth_scopes.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use console\db\Migration;
|
||||
|
||||
class m161127_145211_remove_oauth_scopes extends Migration {
|
||||
|
||||
public function safeUp() {
|
||||
$this->dropTable('{{%oauth_scopes}}');
|
||||
}
|
||||
|
||||
public function safeDown() {
|
||||
$this->createTable('{{%oauth_scopes}}', [
|
||||
'id' => $this->string(64),
|
||||
$this->primary('id'),
|
||||
]);
|
||||
|
||||
$this->batchInsert('{{%oauth_scopes}}', ['id'], [
|
||||
['offline_access'],
|
||||
['minecraft_server_session'],
|
||||
['account_info'],
|
||||
['account_email'],
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
@ -13,7 +13,7 @@ services:
|
||||
env_file: .env
|
||||
|
||||
web:
|
||||
build: ./docker/nginx
|
||||
image: registry.ely.by/elyby/accounts-nginx:latest
|
||||
volumes_from:
|
||||
- app
|
||||
links:
|
||||
|
@ -9,7 +9,7 @@ services:
|
||||
env_file: .env
|
||||
|
||||
web:
|
||||
build: ./docker/nginx
|
||||
image: registry.ely.by/elyby/accounts-nginx:1.0.2
|
||||
volumes_from:
|
||||
- app
|
||||
links:
|
||||
|
0
docker/cron/.gitkeep
Normal file
0
docker/cron/.gitkeep
Normal file
@ -1,11 +0,0 @@
|
||||
FROM nginx:1.11-alpine
|
||||
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
COPY account.ely.by.conf.template /etc/nginx/conf.d/account.ely.by.conf.template
|
||||
COPY run.sh /run.sh
|
||||
|
||||
RUN rm /etc/nginx/conf.d/default.conf \
|
||||
&& chmod a+x /run.sh
|
||||
|
||||
ENTRYPOINT ["/run.sh"]
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
@ -1,78 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
root $root_path;
|
||||
charset utf-8;
|
||||
index index.html;
|
||||
etag on;
|
||||
|
||||
# Это можно раскоментить для целей отладки
|
||||
# rewrite_log on;
|
||||
# error_log /var/log/nginx/error.log debug;
|
||||
|
||||
set $root_path '/var/www/html';
|
||||
set $frontend_path '${root_path}/frontend/dist';
|
||||
|
||||
set $request_url $request_uri;
|
||||
set $host_with_uri '${host}${request_uri}';
|
||||
|
||||
if ($host_with_uri ~ '^${AUTHSERVER_HOST}/auth') {
|
||||
set $request_url '/api/authserver${request_uri}';
|
||||
rewrite ^/auth /api/authserver$uri last;
|
||||
}
|
||||
|
||||
if ($host_with_uri ~ '^${AUTHSERVER_HOST}/session') {
|
||||
set $request_url '/api/minecraft${request_uri}';
|
||||
rewrite ^/session /api/minecraft$uri last;
|
||||
}
|
||||
|
||||
if ($host_with_uri ~ '^${AUTHSERVER_HOST}/api/(user|profiles)') {
|
||||
set $request_url '/api/mojang${request_uri}';
|
||||
rewrite ^/api/(user|profiles) /api/mojang$uri last;
|
||||
}
|
||||
|
||||
location / {
|
||||
alias $frontend_path;
|
||||
try_files $uri /index.html =404;
|
||||
}
|
||||
|
||||
location /api {
|
||||
try_files $uri $uri /api/web/index.php$is_args$args;
|
||||
}
|
||||
|
||||
location ~* \.php$ {
|
||||
fastcgi_pass php:9000;
|
||||
include fastcgi_params;
|
||||
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param SERVER_NAME $host;
|
||||
fastcgi_param REQUEST_URI $request_url;
|
||||
fastcgi_param REMOTE_ADDR $http_x_real_ip;
|
||||
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# html файлы идут отдельно, для них будет применяться E-Tag кэширование
|
||||
location ~* \.html$ {
|
||||
root $frontend_path;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# Раздача статики для frontend с указанием max-кэша. Сброс будет по #hash после ребилда webpackом
|
||||
location ~* ^.+\.(jpg|jpeg|gif|png|svg|js|json|css|zip|rar|eot|ttf|woff|woff2|ico)$ {
|
||||
root $frontend_path;
|
||||
expires max;
|
||||
etag off;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# Запросы к статике для email, их нужно запустить внутрь vendor
|
||||
location ^~ /images/emails/assets {
|
||||
rewrite ^/images/emails/assets/(.+)$ /vendor/ely/emails-renderer/dist/assets/$1 last;
|
||||
}
|
||||
|
||||
location ^~ /vendor/ely/emails-renderer/dist/assets {
|
||||
alias '${root_path}/vendor/ely/email-renderer/dist/assets';
|
||||
try_files $uri =404;
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
user nginx;
|
||||
worker_processes 1;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
keepalive_timeout 10;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
envsubst '$AUTHSERVER_HOST' < /etc/nginx/conf.d/account.ely.by.conf.template > /etc/nginx/conf.d/default.conf
|
||||
|
||||
exec "$@"
|
@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -n "$API_TOKEN" ]
|
||||
then
|
||||
php /usr/local/bin/composer.phar config -g github-oauth.github.com $API_TOKEN
|
||||
fi
|
||||
|
||||
exec php /usr/local/bin/composer.phar "$@"
|
@ -1,38 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd /var/www/html
|
||||
|
||||
if [ "$1" = "bash" ] || [ "$1" = "composer" ]
|
||||
then
|
||||
exec "$@"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Переносим vendor, если его нету или он изменился (или затёрся силами volume)
|
||||
if ! cmp -s ./../vendor/autoload.php ./vendor/autoload.php
|
||||
then
|
||||
echo "vendor have diffs..."
|
||||
echo "removing exists vendor"
|
||||
rm -rf ./vendor
|
||||
echo "copying new one"
|
||||
cp -r ./../vendor ./vendor
|
||||
fi
|
||||
|
||||
# Переносим dist, если его нету или он изменился (или затёрся силами volume)
|
||||
if ! cmp -s ./../dist/index.html ./frontend/dist/index.html
|
||||
then
|
||||
echo "frontend dist have diffs..."
|
||||
echo "removing exists dist"
|
||||
rm -rf ./frontend/dist
|
||||
echo "copying new one"
|
||||
cp -r ./../dist ./frontend/dist
|
||||
fi
|
||||
|
||||
if [ "$YII_ENV" != "test" ]
|
||||
then
|
||||
wait-for-it db:3306 -s -- "php /var/www/html/yii migrate/up --interactive=0"
|
||||
else
|
||||
wait-for-it testdb:3306 -s -- "php /var/www/html/tests/codeception/bin/yii migrate/up --interactive=0"
|
||||
fi
|
||||
|
||||
exec "$@"
|
@ -1,2 +0,0 @@
|
||||
error_reporting = E_ALL;
|
||||
display_errors = On;
|
@ -1,36 +0,0 @@
|
||||
[supervisord]
|
||||
logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log)
|
||||
logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)
|
||||
logfile_backups=10 ; (num of main logfile rotation backups;default 10)
|
||||
loglevel=info ; (log level;default info; others: debug,warn,trace)
|
||||
pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
|
||||
nodaemon=false ; (start in foreground if true;default false)
|
||||
minfds=1024 ; (min. avail startup file descriptors;default 1024)
|
||||
minprocs=200 ; (min. avail process descriptors;default 200)
|
||||
user=root
|
||||
|
||||
; the below section must remain in the config file for RPC
|
||||
; (supervisorctl/web interface) to work, additional interfaces may be
|
||||
; added by defining them in separate rpcinterface: sections
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///dev/shm/supervisor.sock ; use a unix:// URL for a unix socket
|
||||
|
||||
[program:php-fpm]
|
||||
command=php-fpm
|
||||
autostart=true
|
||||
autorestart=true
|
||||
priority=5
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:account-queue-worker]
|
||||
directory=/var/www/html
|
||||
command=wait-for-it rabbitmq:5672 -- php yii account-queue
|
||||
autostart=true
|
||||
autorestart=true
|
||||
priority=10
|
6
docker/supervisor/account-queue-worker.conf
Normal file
6
docker/supervisor/account-queue-worker.conf
Normal file
@ -0,0 +1,6 @@
|
||||
[program:account-queue-worker]
|
||||
directory=/var/www/html
|
||||
command=wait-for-it rabbitmq:5672 -- php yii account-queue
|
||||
autostart=true
|
||||
autorestart=true
|
||||
priority=10
|
@ -1,5 +1,6 @@
|
||||
namespace: tests\codeception\api
|
||||
actor: Tester
|
||||
params: [env]
|
||||
paths:
|
||||
tests: .
|
||||
log: _output
|
||||
|
@ -4,23 +4,16 @@ modules:
|
||||
- Filesystem
|
||||
- Yii2
|
||||
- tests\codeception\common\_support\FixtureHelper
|
||||
- tests\codeception\common\_support\amqp\Helper
|
||||
- Redis
|
||||
- AMQP
|
||||
- Asserts
|
||||
- REST:
|
||||
depends: Yii2
|
||||
config:
|
||||
Yii2:
|
||||
configFile: '../config/api/functional.php'
|
||||
cleanup: true
|
||||
cleanup: false
|
||||
Redis:
|
||||
host: testredis
|
||||
host: "%REDIS_HOST%"
|
||||
port: 6379
|
||||
database: 0
|
||||
AMQP:
|
||||
host: testrabbit
|
||||
port: 5672
|
||||
username: 'ely-accounts-tester'
|
||||
password: 'tester-password'
|
||||
vhost: '/account.ely.by/tests'
|
||||
queues: ['account-operations']
|
||||
|
@ -81,7 +81,7 @@ class OauthAuthCodeCest {
|
||||
public function testCompleteActionOnWrongConditions(FunctionalTester $I) {
|
||||
$I->loggedInAsActiveAccount();
|
||||
|
||||
$I->wantTo('get accept_required if I dom\'t require any scope, but this is first time request');
|
||||
$I->wantTo('get accept_required if I don\'t require any scope, but this is first time request');
|
||||
$this->route->complete($this->buildQueryParams(
|
||||
'ely',
|
||||
'http://ely.by',
|
||||
|
@ -23,14 +23,7 @@ class OauthRefreshTokenCest {
|
||||
'ely',
|
||||
'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM'
|
||||
));
|
||||
$I->canSeeResponseCodeIs(200);
|
||||
$I->canSeeResponseIsJson();
|
||||
$I->canSeeResponseContainsJson([
|
||||
'token_type' => 'Bearer',
|
||||
]);
|
||||
$I->canSeeResponseJsonMatchesJsonPath('$.access_token');
|
||||
$I->canSeeResponseJsonMatchesJsonPath('$.refresh_token');
|
||||
$I->canSeeResponseJsonMatchesJsonPath('$.expires_in');
|
||||
$this->canSeeRefreshTokenSuccess($I);
|
||||
}
|
||||
|
||||
public function testRefreshTokenWithSameScopes(OauthSteps $I) {
|
||||
@ -41,14 +34,26 @@ class OauthRefreshTokenCest {
|
||||
'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM',
|
||||
[S::MINECRAFT_SERVER_SESSION, S::OFFLINE_ACCESS]
|
||||
));
|
||||
$I->canSeeResponseCodeIs(200);
|
||||
$I->canSeeResponseIsJson();
|
||||
$I->canSeeResponseContainsJson([
|
||||
'token_type' => 'Bearer',
|
||||
]);
|
||||
$I->canSeeResponseJsonMatchesJsonPath('$.access_token');
|
||||
$I->canSeeResponseJsonMatchesJsonPath('$.refresh_token');
|
||||
$I->canSeeResponseJsonMatchesJsonPath('$.expires_in');
|
||||
$this->canSeeRefreshTokenSuccess($I);
|
||||
}
|
||||
|
||||
public function testRefreshTokenTwice(OauthSteps $I) {
|
||||
$refreshToken = $I->getRefreshToken([S::MINECRAFT_SERVER_SESSION]);
|
||||
$this->route->issueToken($this->buildParams(
|
||||
$refreshToken,
|
||||
'ely',
|
||||
'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM',
|
||||
[S::MINECRAFT_SERVER_SESSION, S::OFFLINE_ACCESS]
|
||||
));
|
||||
$this->canSeeRefreshTokenSuccess($I);
|
||||
|
||||
$this->route->issueToken($this->buildParams(
|
||||
$refreshToken,
|
||||
'ely',
|
||||
'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM',
|
||||
[S::MINECRAFT_SERVER_SESSION, S::OFFLINE_ACCESS]
|
||||
));
|
||||
$this->canSeeRefreshTokenSuccess($I);
|
||||
}
|
||||
|
||||
public function testRefreshTokenWithNewScopes(OauthSteps $I) {
|
||||
@ -91,4 +96,15 @@ class OauthRefreshTokenCest {
|
||||
return $params;
|
||||
}
|
||||
|
||||
private function canSeeRefreshTokenSuccess(OauthSteps $I) {
|
||||
$I->canSeeResponseCodeIs(200);
|
||||
$I->canSeeResponseIsJson();
|
||||
$I->canSeeResponseContainsJson([
|
||||
'token_type' => 'Bearer',
|
||||
]);
|
||||
$I->canSeeResponseJsonMatchesJsonPath('$.access_token');
|
||||
$I->canSeeResponseJsonMatchesJsonPath('$.expires_in');
|
||||
$I->cantSeeResponseJsonMatchesJsonPath('$.refresh_token');
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ modules:
|
||||
enabled:
|
||||
- Yii2:
|
||||
part: [orm, email, fixtures]
|
||||
- tests\codeception\common\_support\amqp\Helper
|
||||
config:
|
||||
Yii2:
|
||||
configFile: '../config/api/unit.php'
|
||||
cleanup: false
|
||||
|
@ -16,7 +16,6 @@ use tests\codeception\common\_support\ProtectedCaller;
|
||||
use tests\codeception\common\fixtures\AccountFixture;
|
||||
use tests\codeception\common\fixtures\AccountSessionFixture;
|
||||
use Yii;
|
||||
use yii\web\HeaderCollection;
|
||||
use yii\web\Request;
|
||||
|
||||
class ComponentTest extends TestCase {
|
||||
@ -24,7 +23,7 @@ class ComponentTest extends TestCase {
|
||||
use ProtectedCaller;
|
||||
|
||||
/**
|
||||
* @var Component
|
||||
* @var Component|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
private $component;
|
||||
|
||||
@ -40,6 +39,46 @@ class ComponentTest extends TestCase {
|
||||
];
|
||||
}
|
||||
|
||||
public function testGetIdentity() {
|
||||
$this->specify('getIdentity should return null, if not authorization header', function() {
|
||||
$this->mockAuthorizationHeader(null);
|
||||
$this->assertNull($this->component->getIdentity());
|
||||
});
|
||||
|
||||
$this->specify('getIdentity should return null, if passed bearer token don\'t return any account', function() {
|
||||
$this->mockAuthorizationHeader('some-auth');
|
||||
/** @var Component|\PHPUnit_Framework_MockObject_MockObject $component */
|
||||
$component = $this->getMockBuilder(Component::class)
|
||||
->setMethods(['loginByAccessToken'])
|
||||
->setConstructorArgs([$this->getComponentArguments()])
|
||||
->getMock();
|
||||
|
||||
$component
|
||||
->expects($this->once())
|
||||
->method('loginByAccessToken')
|
||||
->willReturn(null);
|
||||
|
||||
$this->assertNull($component->getIdentity());
|
||||
});
|
||||
|
||||
$this->specify('getIdentity should return identity from loginByAccessToken method', function() {
|
||||
$identity = new AccountIdentity();
|
||||
$this->mockAuthorizationHeader('some-auth');
|
||||
/** @var Component|\PHPUnit_Framework_MockObject_MockObject $component */
|
||||
$component = $this->getMockBuilder(Component::class)
|
||||
->setMethods(['loginByAccessToken'])
|
||||
->setConstructorArgs([$this->getComponentArguments()])
|
||||
->getMock();
|
||||
|
||||
$component
|
||||
->expects($this->once())
|
||||
->method('loginByAccessToken')
|
||||
->willReturn($identity);
|
||||
|
||||
$this->assertEquals($identity, $component->getIdentity());
|
||||
});
|
||||
}
|
||||
|
||||
public function testLogin() {
|
||||
$this->mockRequest();
|
||||
$this->specify('success get LoginResult object without session value', function() {
|
||||
@ -117,30 +156,9 @@ class ComponentTest extends TestCase {
|
||||
$component
|
||||
->expects($this->any())
|
||||
->method('getIsGuest')
|
||||
->will($this->returnValue(false));
|
||||
->willReturn(false);
|
||||
|
||||
/** @var HeaderCollection|\PHPUnit_Framework_MockObject_MockObject $headersCollection */
|
||||
$headersCollection = $this->getMockBuilder(HeaderCollection::class)
|
||||
->setMethods(['get'])
|
||||
->getMock();
|
||||
|
||||
$headersCollection
|
||||
->expects($this->any())
|
||||
->method('get')
|
||||
->with($this->equalTo('Authorization'))
|
||||
->will($this->returnValue('Bearer ' . $result->getJwt()));
|
||||
|
||||
/** @var Request|\PHPUnit_Framework_MockObject_MockObject $request */
|
||||
$request = $this->getMockBuilder(Request::class)
|
||||
->setMethods(['getHeaders'])
|
||||
->getMock();
|
||||
|
||||
$request
|
||||
->expects($this->any())
|
||||
->method('getHeaders')
|
||||
->will($this->returnValue($headersCollection));
|
||||
|
||||
Yii::$app->set('request', $request);
|
||||
$this->mockAuthorizationHeader($result->getJwt());
|
||||
|
||||
$session = $component->getActiveSession();
|
||||
expect($session)->isInstanceOf(AccountSession::class);
|
||||
@ -203,6 +221,17 @@ class ComponentTest extends TestCase {
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $bearerToken
|
||||
*/
|
||||
private function mockAuthorizationHeader($bearerToken = null) {
|
||||
if ($bearerToken !== null) {
|
||||
$bearerToken = 'Bearer ' . $bearerToken;
|
||||
}
|
||||
|
||||
Yii::$app->request->headers->set('Authorization', $bearerToken);
|
||||
}
|
||||
|
||||
private function getComponentArguments() {
|
||||
return [
|
||||
'identityClass' => AccountIdentity::class,
|
||||
|
57
tests/codeception/api/unit/filters/NginxCacheTest.php
Normal file
57
tests/codeception/api/unit/filters/NginxCacheTest.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
namespace tests\codeception\api\unit\filters;
|
||||
|
||||
use api\filters\NginxCache;
|
||||
use tests\codeception\api\unit\TestCase;
|
||||
use Yii;
|
||||
use yii\base\Action;
|
||||
use yii\web\Controller;
|
||||
use yii\web\HeaderCollection;
|
||||
use yii\web\Request;
|
||||
|
||||
class NginxCacheTest extends TestCase {
|
||||
|
||||
public function testAfterAction() {
|
||||
$this->testAfterActionInternal(3600, 3600);
|
||||
$this->testAfterActionInternal('@' . (time() + 30), '@' . (time() + 30));
|
||||
$this->testAfterActionInternal(function() {
|
||||
return 3000;
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
private function testAfterActionInternal($ruleConfig, $expected) {
|
||||
/** @var HeaderCollection|\PHPUnit_Framework_MockObject_MockObject $headers */
|
||||
$headers = $this->getMockBuilder(HeaderCollection::class)
|
||||
->setMethods(['set'])
|
||||
->getMock();
|
||||
|
||||
$headers->expects($this->once())
|
||||
->method('set')
|
||||
->with('X-Accel-Expires', $expected);
|
||||
|
||||
/** @var Request|\PHPUnit_Framework_MockObject_MockObject $request */
|
||||
$request = $this->getMockBuilder(Request::class)
|
||||
->setMethods(['getHeaders'])
|
||||
->getMock();
|
||||
|
||||
$request->expects($this->any())
|
||||
->method('getHeaders')
|
||||
->willReturn($headers);
|
||||
|
||||
Yii::$app->set('response', $request);
|
||||
|
||||
/** @var Controller|\PHPUnit_Framework_MockObject_MockObject $controller */
|
||||
$controller = $this->getMockBuilder(Controller::class)
|
||||
->setConstructorArgs(['mock', Yii::$app])
|
||||
->getMock();
|
||||
|
||||
$component = new NginxCache([
|
||||
'rules' => [
|
||||
'index' => $ruleConfig,
|
||||
],
|
||||
]);
|
||||
|
||||
$component->afterAction(new Action('index', $controller), '');
|
||||
}
|
||||
|
||||
}
|
@ -25,9 +25,15 @@ class ConfirmEmailFormTest extends TestCase {
|
||||
$this->assertInstanceOf(AccountSession::class, $result->getSession(), 'session was generated');
|
||||
$activationExists = EmailActivation::find()->andWhere(['key' => $fixture['key']])->exists();
|
||||
$this->assertFalse($activationExists, 'email activation key is not exist');
|
||||
/** @var Account $user */
|
||||
$user = Account::findOne($fixture['account_id']);
|
||||
$this->assertEquals(Account::STATUS_ACTIVE, $user->status, 'user status changed to active');
|
||||
/** @var Account $account */
|
||||
$account = Account::findOne($fixture['account_id']);
|
||||
$this->assertEquals(Account::STATUS_ACTIVE, $account->status, 'user status changed to active');
|
||||
|
||||
$message = $this->tester->grabLastSentAmqpMessage('events');
|
||||
$body = json_decode($message->getBody(), true);
|
||||
$this->assertEquals($account->id, $body['accountId']);
|
||||
$this->assertEquals($account->username, $body['newUsername']);
|
||||
$this->assertNull($body['oldUsername']);
|
||||
}
|
||||
|
||||
private function createModel($key) {
|
||||
|
@ -18,9 +18,8 @@ class ConfirmNewEmailFormTest extends TestCase {
|
||||
}
|
||||
|
||||
public function testChangeEmail() {
|
||||
$accountId = $this->tester->grabFixture('accounts', 'account-with-change-email-finish-state')['id'];
|
||||
/** @var Account $account */
|
||||
$account = Account::findOne($accountId);
|
||||
$account = Account::findOne($this->getAccountId());
|
||||
$newEmailConfirmationFixture = $this->tester->grabFixture('emailActivations', 'newEmailConfirmation');
|
||||
$model = new ConfirmNewEmailForm($account, [
|
||||
'key' => $newEmailConfirmationFixture['key'],
|
||||
@ -32,6 +31,23 @@ class ConfirmNewEmailFormTest extends TestCase {
|
||||
]));
|
||||
$data = unserialize($newEmailConfirmationFixture['_data']);
|
||||
$this->assertEquals($data['newEmail'], $account->email);
|
||||
$this->tester->canSeeAmqpMessageIsCreated('events');
|
||||
}
|
||||
|
||||
public function testCreateTask() {
|
||||
/** @var Account $account */
|
||||
$account = Account::findOne($this->getAccountId());
|
||||
$model = new ConfirmNewEmailForm($account);
|
||||
$model->createTask(1, 'test1@ely.by', 'test@ely.by');
|
||||
$message = $this->tester->grabLastSentAmqpMessage('events');
|
||||
$body = json_decode($message->getBody(), true);
|
||||
$this->assertEquals(1, $body['accountId']);
|
||||
$this->assertEquals('test1@ely.by', $body['newEmail']);
|
||||
$this->assertEquals('test@ely.by', $body['oldEmail']);
|
||||
}
|
||||
|
||||
private function getAccountId() {
|
||||
return $this->tester->grabFixture('accounts', 'account-with-change-email-finish-state')['id'];
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ class ChangeUsernameFormTest extends TestCase {
|
||||
$this->assertTrue($model->change());
|
||||
$this->assertEquals('my_new_nickname', Account::findOne($this->getAccountId())->username);
|
||||
$this->assertInstanceOf(UsernameHistory::class, UsernameHistory::findOne(['username' => 'my_new_nickname']));
|
||||
$this->tester->canSeeAmqpMessageIsCreated('events');
|
||||
}
|
||||
|
||||
public function testChangeWithoutChange() {
|
||||
@ -49,7 +50,8 @@ class ChangeUsernameFormTest extends TestCase {
|
||||
'AND',
|
||||
'username' => $username,
|
||||
['>=', 'applied_in', $callTime],
|
||||
]), 'no new UsernameHistory record, if we don\'t change nickname');
|
||||
]), 'no new UsernameHistory record, if we don\'t change username');
|
||||
$this->tester->cantSeeAmqpMessageIsCreated('events');
|
||||
}
|
||||
|
||||
public function testChangeCase() {
|
||||
@ -65,13 +67,17 @@ class ChangeUsernameFormTest extends TestCase {
|
||||
UsernameHistory::findOne(['username' => $newUsername]),
|
||||
'username should change, if we change case of some letters'
|
||||
);
|
||||
$this->tester->canSeeAmqpMessageIsCreated('events');
|
||||
}
|
||||
|
||||
public function testCreateTask() {
|
||||
$model = new ChangeUsernameForm();
|
||||
$model->createEventTask('1', 'test1', 'test');
|
||||
// TODO: у меня пока нет идей о том, чтобы это как-то успешно протестировать, увы
|
||||
// но по крайней мере можно убедиться, что оно не падает где-то на этом шаге
|
||||
$model->createEventTask(1, 'test1', 'test');
|
||||
$message = $this->tester->grabLastSentAmqpMessage('events');
|
||||
$body = json_decode($message->getBody(), true);
|
||||
$this->assertEquals(1, $body['accountId']);
|
||||
$this->assertEquals('test1', $body['newUsername']);
|
||||
$this->assertEquals('test', $body['oldUsername']);
|
||||
}
|
||||
|
||||
private function getAccountId() {
|
||||
|
@ -1,35 +0,0 @@
|
||||
<?php
|
||||
namespace tests\codeception\api\traits;
|
||||
|
||||
use api\traits\ApiNormalize;
|
||||
use tests\codeception\api\unit\TestCase;
|
||||
|
||||
class ApiNormalizeTestClass {
|
||||
use ApiNormalize;
|
||||
}
|
||||
|
||||
class ApiNormalizerTest extends TestCase {
|
||||
|
||||
public function testNormalizeModelErrors() {
|
||||
$object = new ApiNormalizeTestClass();
|
||||
$normalized = $object->normalizeModelErrors([
|
||||
'rulesAgreement' => [
|
||||
'error.you_must_accept_rules',
|
||||
],
|
||||
'email' => [
|
||||
'error.email_required',
|
||||
],
|
||||
'username' => [
|
||||
'error.username_too_short',
|
||||
'error.username_not_unique',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals([
|
||||
'rulesAgreement' => 'error.you_must_accept_rules',
|
||||
'email' => 'error.email_required',
|
||||
'username' => 'error.username_too_short',
|
||||
], $normalized);
|
||||
}
|
||||
|
||||
}
|
94
tests/codeception/common/_support/amqp/Helper.php
Normal file
94
tests/codeception/common/_support/amqp/Helper.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
namespace tests\codeception\common\_support\amqp;
|
||||
|
||||
use Codeception\Exception\ModuleException;
|
||||
use Codeception\Module;
|
||||
use Codeception\Module\Yii2;
|
||||
|
||||
class Helper extends Module {
|
||||
|
||||
/**
|
||||
* Checks that message is created.
|
||||
*
|
||||
* ```php
|
||||
* <?php
|
||||
* // check that at least 1 message was created
|
||||
* $I->seeAmqpMessageIsCreated();
|
||||
*
|
||||
* // check that only 3 messages were created
|
||||
* $I->seeAmqpMessageIsCreated(3);
|
||||
* ```
|
||||
*
|
||||
* @param string|null $exchange
|
||||
* @param int|null $num
|
||||
*/
|
||||
public function seeAmqpMessageIsCreated($exchange = null, $num = null) {
|
||||
if ($num === null) {
|
||||
$this->assertNotEmpty($this->grabSentAmqpMessages($exchange), 'message were created');
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: заменить на assertCount() после релиза Codeception 2.2.7
|
||||
// https://github.com/Codeception/Codeception/pull/3802
|
||||
/** @noinspection PhpUnitTestsInspection */
|
||||
$this->assertEquals(
|
||||
$num,
|
||||
count($this->grabSentAmqpMessages($exchange)),
|
||||
'number of created messages is equal to ' . $num
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that no messages was created
|
||||
*
|
||||
* @param string|null $exchange
|
||||
*/
|
||||
public function dontSeeAmqpMessageIsCreated($exchange = null) {
|
||||
$this->seeAmqpMessageIsCreated($exchange, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns last sent message
|
||||
*
|
||||
* @param string|null $exchange
|
||||
* @return \PhpAmqpLib\Message\AMQPMessage
|
||||
*/
|
||||
public function grabLastSentAmqpMessage($exchange = null) {
|
||||
$this->seeAmqpMessageIsCreated();
|
||||
$messages = $this->grabSentAmqpMessages($exchange);
|
||||
|
||||
return end($messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of all sent amqp messages.
|
||||
* Each message is `\PhpAmqpLib\Message\AMQPMessage` instance.
|
||||
* Useful to perform additional checks using `Asserts` module.
|
||||
*
|
||||
* @param string|null $exchange
|
||||
* @return \PhpAmqpLib\Message\AMQPMessage[]
|
||||
* @throws ModuleException
|
||||
*/
|
||||
public function grabSentAmqpMessages($exchange = null) {
|
||||
$amqp = $this->grabComponent('amqp');
|
||||
if (!$amqp instanceof TestComponent) {
|
||||
throw new ModuleException($this, 'AMQP module is not mocked, can\'t test messages');
|
||||
}
|
||||
|
||||
return $amqp->getSentMessages($exchange);
|
||||
}
|
||||
|
||||
private function grabComponent(string $component) {
|
||||
return $this->getYii2()->grabComponent($component);
|
||||
}
|
||||
|
||||
private function getYii2() : Yii2 {
|
||||
$yii2 = $this->getModule('Yii2');
|
||||
if (!$yii2 instanceof Yii2) {
|
||||
throw new ModuleException($this, 'Yii2 module must be configured');
|
||||
}
|
||||
|
||||
return $yii2;
|
||||
}
|
||||
|
||||
}
|
58
tests/codeception/common/_support/amqp/TestComponent.php
Normal file
58
tests/codeception/common/_support/amqp/TestComponent.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
namespace tests\codeception\common\_support\amqp;
|
||||
|
||||
use common\components\RabbitMQ\Component;
|
||||
use PhpAmqpLib\Connection\AbstractConnection;
|
||||
|
||||
class TestComponent extends Component {
|
||||
|
||||
private $sentMessages = [];
|
||||
|
||||
public function init() {
|
||||
\yii\base\Component::init();
|
||||
}
|
||||
|
||||
public function getConnection() {
|
||||
/** @noinspection MagicMethodsValidityInspection */
|
||||
/** @noinspection PhpMissingParentConstructorInspection */
|
||||
return new class extends AbstractConnection {
|
||||
public function __construct(
|
||||
$user,
|
||||
$password,
|
||||
$vhost,
|
||||
$insist,
|
||||
$login_method,
|
||||
$login_response,
|
||||
$locale,
|
||||
\PhpAmqpLib\Wire\IO\AbstractIO $io,
|
||||
$heartbeat
|
||||
) {
|
||||
// ничего не делаем
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public function sendToExchange($exchangeName, $routingKey, $message, $exchangeArgs = [], $publishArgs = []) {
|
||||
$this->sentMessages[$exchangeName][] = $this->prepareMessage($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $exchangeName
|
||||
* @return \PhpAmqpLib\Message\AMQPMessage[]
|
||||
*/
|
||||
public function getSentMessages(string $exchangeName = null) : array {
|
||||
if ($exchangeName !== null) {
|
||||
return $this->sentMessages[$exchangeName] ?? [];
|
||||
} else {
|
||||
$messages = [];
|
||||
foreach($this->sentMessages as $exchangeGroup) {
|
||||
foreach ($exchangeGroup as $message) {
|
||||
$messages[] = $message;
|
||||
}
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
namespace: tests\codeception\common
|
||||
actor: Tester
|
||||
params: [env]
|
||||
paths:
|
||||
tests: .
|
||||
log: _output
|
||||
|
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace tests\codeception\common\fixtures;
|
||||
|
||||
use common\models\OauthAccessToken;
|
||||
use yii\test\ActiveFixture;
|
||||
|
||||
class OauthAccessTokenFixture extends ActiveFixture {
|
||||
|
||||
public $modelClass = OauthAccessToken::class;
|
||||
|
||||
public $dataFile = '@tests/codeception/common/fixtures/data/oauth-access-tokens.php';
|
||||
|
||||
public $depends = [
|
||||
OauthSessionFixture::class,
|
||||
];
|
||||
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
namespace tests\codeception\common\fixtures;
|
||||
|
||||
use common\models\OauthScope;
|
||||
use common\models\OauthSession;
|
||||
use yii\test\ActiveFixture;
|
||||
|
||||
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
return [
|
||||
'admin-test1' => [
|
||||
'access_token' => '07541285-831e-1e47-e314-b950309a6fca',
|
||||
'session_id' => 1,
|
||||
'expire_time' => time() + 3600,
|
||||
],
|
||||
'admin-test1-expired' => [
|
||||
'access_token' => '2977ec21-3022-96f8-544db-2e1df936908',
|
||||
'session_id' => 1,
|
||||
'expire_time' => time() - 3600,
|
||||
],
|
||||
];
|
@ -6,7 +6,7 @@ return [
|
||||
'name' => 'Ely.by',
|
||||
'description' => 'Всем знакомое елуби',
|
||||
'redirect_uri' => 'http://ely.by',
|
||||
'account_id' => NULL,
|
||||
'account_id' => null,
|
||||
'is_trusted' => 0,
|
||||
'created_at' => 1455309271,
|
||||
],
|
||||
@ -16,8 +16,18 @@ return [
|
||||
'name' => 'TLauncher',
|
||||
'description' => 'Лучший альтернативный лаунчер для Minecraft с большим количеством версий и их модификаций, а также возмоностью входа как с лицензионным аккаунтом, так и без него.',
|
||||
'redirect_uri' => '',
|
||||
'account_id' => NULL,
|
||||
'account_id' => null,
|
||||
'is_trusted' => 0,
|
||||
'created_at' => 1455318468,
|
||||
],
|
||||
'test1' => [
|
||||
'id' => 'test1',
|
||||
'secret' => 'eEvrKHF47sqiaX94HsX-xXzdGiz3mcsq',
|
||||
'name' => 'Test1',
|
||||
'description' => 'Some description',
|
||||
'redirect_uri' => 'http://test1.net',
|
||||
'account_id' => null,
|
||||
'is_trusted' => 0,
|
||||
'created_at' => 1479937982,
|
||||
],
|
||||
];
|
||||
|
@ -1,3 +1,10 @@
|
||||
<?php
|
||||
return [
|
||||
'admin-test1' => [
|
||||
'id' => 1,
|
||||
'owner_type' => 'user',
|
||||
'owner_id' => 1,
|
||||
'client_id' => 'test1',
|
||||
'client_redirect_uri' => 'http://test1.net/oauth',
|
||||
],
|
||||
];
|
||||
|
@ -6,3 +6,4 @@ modules:
|
||||
config:
|
||||
Yii2:
|
||||
configFile: '../config/common/unit.php'
|
||||
cleanup: false
|
||||
|
@ -10,29 +10,18 @@ return [
|
||||
],
|
||||
],
|
||||
'components' => [
|
||||
'db' => [
|
||||
'dsn' => 'mysql:host=testdb;dbname=ely_accounts_test',
|
||||
'username' => 'ely_accounts_tester',
|
||||
'password' => 'ely_accounts_tester_password',
|
||||
],
|
||||
'mailer' => [
|
||||
'useFileTransport' => true,
|
||||
],
|
||||
'urlManager' => [
|
||||
'showScriptName' => true,
|
||||
],
|
||||
'redis' => [
|
||||
'hostname' => 'testredis',
|
||||
],
|
||||
'amqp' => [
|
||||
'host' => 'testrabbit',
|
||||
'user' => 'ely-accounts-tester',
|
||||
'password' => 'tester-password',
|
||||
'vhost' => '/account.ely.by/tests',
|
||||
],
|
||||
'security' => [
|
||||
// Для тестов нам не сильно важна безопасность, а вот время прохождения тестов значительно сокращается
|
||||
'passwordHashCost' => 4,
|
||||
],
|
||||
'amqp' => [
|
||||
'class' => tests\codeception\common\_support\amqp\TestComponent::class,
|
||||
],
|
||||
'sentry' => [
|
||||
'enabled' => false,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
@ -1,5 +1,6 @@
|
||||
namespace: tests\codeception\console
|
||||
actor: Tester
|
||||
params: [env]
|
||||
paths:
|
||||
tests: .
|
||||
log: _output
|
||||
|
@ -6,3 +6,4 @@ modules:
|
||||
config:
|
||||
Yii2:
|
||||
configFile: '../config/console/unit.php'
|
||||
cleanup: false
|
||||
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
namespace codeception\console\unit\controllers;
|
||||
|
||||
use common\models\OauthAccessToken;
|
||||
use console\controllers\CleanupController;
|
||||
use tests\codeception\common\fixtures\OauthAccessTokenFixture;
|
||||
use tests\codeception\console\unit\TestCase;
|
||||
use Yii;
|
||||
|
||||
class CleanupControllerTest extends TestCase {
|
||||
|
||||
public function _fixtures() {
|
||||
return [
|
||||
'accessTokens' => OauthAccessTokenFixture::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function testActionAccessTokens() {
|
||||
/** @var OauthAccessToken $validAccessToken */
|
||||
$validAccessToken = $this->tester->grabFixture('accessTokens', 'admin-test1');
|
||||
/** @var OauthAccessToken $expiredAccessToken */
|
||||
$expiredAccessToken = $this->tester->grabFixture('accessTokens', 'admin-test1-expired');
|
||||
|
||||
$controller = new CleanupController('cleanup', Yii::$app);
|
||||
$this->assertEquals(0, $controller->actionAccessTokens());
|
||||
|
||||
$this->tester->canSeeRecord(OauthAccessToken::class, ['access_token' => $validAccessToken->access_token]);
|
||||
$this->tester->cantSeeRecord(OauthAccessToken::class, ['access_token' => $expiredAccessToken->access_token]);
|
||||
}
|
||||
|
||||
}
|
@ -9,18 +9,21 @@ services:
|
||||
depends_on:
|
||||
- testdb
|
||||
- testredis
|
||||
- testrabbit
|
||||
volumes:
|
||||
- ./codeception/_output:/var/www/html/tests/codeception/_output
|
||||
- ./codeception/api/_output:/var/www/html/tests/codeception/api/_output
|
||||
- ./codeception/common/_output:/var/www/html/tests/codeception/common/_output
|
||||
- ./codeception/console/_output:/var/www/html/tests/codeception/console/_output
|
||||
- ./..:/var/www/html
|
||||
environment:
|
||||
- YII_DEBUG=true
|
||||
- YII_ENV=test
|
||||
YII_DEBUG: "true"
|
||||
YII_ENV: "test"
|
||||
# DB config
|
||||
DB_HOST: "testdb"
|
||||
DB_DATABASE: "ely_accounts_test"
|
||||
DB_USER: "ely_accounts_tester"
|
||||
DB_PASSWORD: "ely_accounts_tester_password"
|
||||
# Redis config
|
||||
REDIS_HOST: "testredis"
|
||||
# Это я потом, когда-нибудь, уберу
|
||||
- XDEBUG_CONFIG=remote_host=10.254.254.254
|
||||
- PHP_IDE_CONFIG=serverName=docker
|
||||
XDEBUG_CONFIG: "remote_host=10.254.254.254"
|
||||
PHP_IDE_CONFIG: "serverName=docker"
|
||||
|
||||
testdb:
|
||||
container_name: accountelyby_testdb
|
||||
@ -36,11 +39,3 @@ services:
|
||||
testredis:
|
||||
container_name: accountelyby_testredis
|
||||
image: redis:3.0-alpine
|
||||
|
||||
testrabbit:
|
||||
container_name: accountelyby_testrabbit
|
||||
image: rabbitmq:3.6
|
||||
environment:
|
||||
RABBITMQ_DEFAULT_USER: "ely-accounts-tester"
|
||||
RABBITMQ_DEFAULT_PASS: "tester-password"
|
||||
RABBITMQ_DEFAULT_VHOST: "/account.ely.by/tests"
|
||||
|
Loading…
Reference in New Issue
Block a user