mirror of
https://github.com/elyby/accounts.git
synced 2024-11-23 05:33:20 +05:30
Merge branch 'develop'
This commit is contained in:
commit
425322f431
12
.env-dist
12
.env-dist
@ -29,13 +29,6 @@ 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
|
||||
|
||||
## Параметры Statsd
|
||||
STATSD_HOST=statsd.ely.by
|
||||
STATSD_PORT=8125
|
||||
@ -59,8 +52,3 @@ MYSQL_ROOT_PASSWORD=
|
||||
MYSQL_DATABASE=ely_accounts
|
||||
MYSQL_USER=ely_accounts_user
|
||||
MYSQL_PASSWORD=ely_accounts_password
|
||||
|
||||
# RabbitMQ
|
||||
RABBITMQ_DEFAULT_USER=ely-accounts-app
|
||||
RABBITMQ_DEFAULT_PASS=ely-accounts-app-password
|
||||
RABBITMQ_DEFAULT_VHOST=/ely.by
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -19,3 +19,7 @@ npm-debug*
|
||||
|
||||
# id_rsa
|
||||
/id_rsa
|
||||
|
||||
# PHP-CS-Fixer
|
||||
.php_cs
|
||||
.php_cs.cache
|
||||
|
@ -6,39 +6,52 @@ stages:
|
||||
variables:
|
||||
CONTAINER_IMAGE: "registry.ely.by/elyby/accounts"
|
||||
|
||||
test:backend:
|
||||
image: docker:18.02
|
||||
check backend codestyle:
|
||||
image: edbizarro/gitlab-ci-pipeline-php:7.2-alpine
|
||||
stage: test
|
||||
cache:
|
||||
key: backend-vendor
|
||||
paths:
|
||||
- vendor
|
||||
script:
|
||||
- composer install
|
||||
- vendor/bin/php-cs-fixer fix -v --dry-run
|
||||
|
||||
test backend:
|
||||
image: edbizarro/gitlab-ci-pipeline-php:7.2-alpine
|
||||
services:
|
||||
- mariadb:10.2.11
|
||||
- redis:3.0-alpine
|
||||
- name: redis:4.0.10-alpine
|
||||
alias: redis
|
||||
- name: mariadb:10.2.11
|
||||
alias: db
|
||||
variables:
|
||||
# mariadb config
|
||||
# App config
|
||||
DB_HOST: "db"
|
||||
DB_DATABASE: "ely_accounts_test"
|
||||
DB_USER: "ely_accounts_tester"
|
||||
DB_PASSWORD: "ely_accounts_tester_password"
|
||||
REDIS_HOST: "redis"
|
||||
REDIS_PORT: "6379"
|
||||
# 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
|
||||
cache:
|
||||
key: backend-vendor
|
||||
paths:
|
||||
- vendor
|
||||
before_script:
|
||||
- docker login -u gitlab-ci -p $CI_BUILD_TOKEN registry.ely.by
|
||||
- echo "$SSH_PRIVATE_KEY" > id_rsa
|
||||
# While we not counting coverage, xdebug only slow down tests
|
||||
- sudo rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
|
||||
script:
|
||||
- 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
|
||||
- composer install
|
||||
- php tests/codeception/bin/yii rbac/generate
|
||||
- ./docker/php/wait-for-it.sh "${DB_HOST}:3306" -s -t 0 -- "php tests/codeception/bin/yii migrate/up --interactive=0"
|
||||
- vendor/bin/codecept run -c tests
|
||||
|
||||
test:frontend:
|
||||
test frontend:
|
||||
image: node:9.2.1-alpine
|
||||
stage: test
|
||||
cache:
|
||||
@ -47,10 +60,10 @@ test:frontend:
|
||||
before_script:
|
||||
# Enable SSL support for wget
|
||||
- apk add --update openssl
|
||||
# https://github.com/facebook/flow/issues/3649#issuecomment-308070179
|
||||
- wget -O /etc/apk/keys/sgerrand.rsa.pub https://raw.githubusercontent.com/sgerrand/alpine-pkg-glibc/master/sgerrand.rsa.pub
|
||||
- wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.25-r0/glibc-2.25-r0.apk
|
||||
- apk add glibc-2.25-r0.apk
|
||||
# https://github.com/facebook/flow/issues/3649#issuecomment-414691014
|
||||
- wget -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
|
||||
- wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.28-r0/glibc-2.28-r0.apk
|
||||
- apk add glibc-2.28-r0.apk
|
||||
script:
|
||||
- cd frontend
|
||||
- yarn run build:install
|
||||
@ -79,7 +92,7 @@ build:production:
|
||||
- sed -i"" -e "s/{{PLACE_VERSION_HERE}}/$VERSION/g" common/config/config.php
|
||||
script:
|
||||
- export IMAGE_NAME="$CONTAINER_IMAGE:latest"
|
||||
- docker build --pull -t $IMAGE_NAME .
|
||||
- docker build --pull --build-arg build_env=prod -t $IMAGE_NAME .
|
||||
only:
|
||||
- develop
|
||||
- tags
|
||||
|
16
.php_cs.dist
Normal file
16
.php_cs.dist
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
$finder = \PhpCsFixer\Finder::create()
|
||||
->in(__DIR__)
|
||||
->exclude('data')
|
||||
->exclude('docker')
|
||||
->exclude('frontend')
|
||||
->notPath('common/emails/views')
|
||||
->notPath('/.*\/runtime/')
|
||||
->notPath('autocompletion.php')
|
||||
->notPath('tests/codeception/_output')
|
||||
->notPath('/tests\/codeception\/.*\/_output/')
|
||||
->notPath('/tests\/codeception\/.*\/_support\/_generated/')
|
||||
->name('yii');
|
||||
|
||||
return \Ely\CS\Config::create()
|
||||
->setFinder($finder);
|
119
Dockerfile
119
Dockerfile
@ -1,55 +1,86 @@
|
||||
FROM registry.ely.by/elyby/accounts-php:1.7.0
|
||||
FROM node:9.11.2-alpine as frontend
|
||||
|
||||
# bootstrap скрипт для проекта
|
||||
COPY docker/php/bootstrap.sh /bootstrap.sh
|
||||
# Вносим конфигурации для крона и воркеров
|
||||
COPY docker/cron/* /etc/cron.d/
|
||||
COPY docker/supervisor/* /etc/supervisor/conf.d/
|
||||
WORKDIR /app
|
||||
|
||||
COPY id_rsa /root/.ssh/id_rsa
|
||||
COPY ./frontend/package.json ./
|
||||
COPY ./frontend/scripts ./scripts
|
||||
COPY ./frontend/webpack-utils ./webpack-utils
|
||||
COPY ./frontend/yarn.lock ./
|
||||
RUN yarn build:install
|
||||
|
||||
# Включаем поддержку ssh
|
||||
RUN chmod 400 ~/.ssh/id_rsa \
|
||||
&& eval $(ssh-agent -s) \
|
||||
&& ssh-add /root/.ssh/id_rsa \
|
||||
&& touch /root/.ssh/known_hosts \
|
||||
&& ssh-keyscan github.com gitlab.ely.by >> /root/.ssh/known_hosts
|
||||
COPY ./frontend .
|
||||
RUN yarn build:quiet
|
||||
|
||||
# Копируем списки зависимостей composer в родительскую директорию, которая не будет синкаться
|
||||
# с хостом через volume на dev окружении. В entrypoint эта папка будет скопирована обратно
|
||||
COPY ./composer.json /var/www/composer.json
|
||||
COPY ./composer.lock /var/www/composer.lock
|
||||
|
||||
# Устанавливаем зависимости PHP
|
||||
RUN cd .. \
|
||||
&& composer install --no-interaction --no-suggest --no-dev --optimize-autoloader \
|
||||
&& cd -
|
||||
FROM php:7.2.7-fpm-alpine3.7
|
||||
|
||||
# Устанавливаем зависимости для Node.js
|
||||
# Делаем это отдельно, чтобы можно было воспользоваться кэшем, если от предыдущего билда
|
||||
# ничего не менялось в зависимостях
|
||||
RUN mkdir -p /var/www/frontend
|
||||
# bash needed to support wait-for-it script
|
||||
RUN apk add --update --no-cache \
|
||||
git \
|
||||
bash \
|
||||
openssh \
|
||||
dcron \
|
||||
zlib-dev \
|
||||
icu-dev \
|
||||
libintl \
|
||||
imagemagick-dev \
|
||||
imagemagick \
|
||||
&& docker-php-ext-install \
|
||||
zip \
|
||||
pdo_mysql \
|
||||
intl \
|
||||
pcntl \
|
||||
opcache \
|
||||
&& apk add --no-cache --virtual ".phpize-deps" $PHPIZE_DEPS \
|
||||
&& yes | pecl install xdebug-2.6.0 \
|
||||
&& yes | pecl install imagick \
|
||||
&& docker-php-ext-enable imagick \
|
||||
&& apk del ".phpize-deps" \
|
||||
&& rm -rf /usr/share/man \
|
||||
&& rm -rf /tmp/* \
|
||||
&& mkdir /etc/cron.d
|
||||
|
||||
COPY ./frontend/package.json /var/www/frontend/
|
||||
COPY ./frontend/yarn.lock /var/www/frontend/
|
||||
COPY ./frontend/scripts /var/www/frontend/scripts
|
||||
COPY ./frontend/webpack-utils /var/www/frontend/webpack-utils
|
||||
COPY --from=composer:1.6.5 /usr/bin/composer /usr/bin/composer
|
||||
COPY --from=node:9.11.2-alpine /usr/local/bin/node /usr/bin/
|
||||
COPY --from=node:9.11.2-alpine /usr/lib/libgcc* /usr/lib/libstdc* /usr/lib/* /usr/lib/
|
||||
|
||||
RUN cd /var/www/frontend \
|
||||
&& yarn run build:install \
|
||||
&& cd -
|
||||
# ENV variables for composer
|
||||
ENV COMPOSER_NO_INTERACTION 1
|
||||
ENV COMPOSER_ALLOW_SUPERUSER 1
|
||||
|
||||
# Удаляем ключи из production контейнера на всякий случай
|
||||
RUN rm -rf /root/.ssh
|
||||
RUN mkdir /root/.composer \
|
||||
&& echo '{"github-oauth": {"github.com": "***REMOVED***"}}' > ~/.composer/auth.json \
|
||||
&& composer global require --no-progress "hirak/prestissimo:^0.3.7" \
|
||||
&& composer clear-cache
|
||||
|
||||
# Наконец переносим все сорцы внутрь контейнера
|
||||
COPY . /var/www/html
|
||||
COPY ./docker/php/wait-for-it.sh /usr/local/bin/wait-for-it
|
||||
|
||||
# Билдим фронт
|
||||
RUN cd frontend \
|
||||
&& ln -s /var/www/frontend/node_modules $PWD/node_modules \
|
||||
&& yarn run build:quiet \
|
||||
&& rm node_modules \
|
||||
# Копируем билд наружу, чтобы его не затёрло volume в dev режиме
|
||||
&& cp -r ./dist /var/www/dist \
|
||||
&& cd -
|
||||
COPY ./composer.* /var/www/html/
|
||||
|
||||
ARG build_env=prod
|
||||
ENV YII_ENV=$build_env
|
||||
|
||||
RUN if [ "$build_env" = "prod" ] ; then \
|
||||
composer install --no-interaction --no-suggest --no-dev --optimize-autoloader; \
|
||||
else \
|
||||
composer install --no-interaction --no-suggest; \
|
||||
fi \
|
||||
&& composer clear-cache
|
||||
|
||||
COPY ./docker/php/*.ini /usr/local/etc/php/conf.d/
|
||||
COPY ./docker/php/docker-entrypoint.sh /usr/local/bin/
|
||||
COPY ./docker/cron/* /etc/cron.d/
|
||||
|
||||
COPY --from=frontend /app/dist /var/www/html/frontend/dist
|
||||
|
||||
COPY ./api /var/www/html/api/
|
||||
COPY ./common /var/www/html/common/
|
||||
COPY ./console /var/www/html/console/
|
||||
COPY ./yii /var/www/html/yii
|
||||
|
||||
# Expose everything under /var/www/html to share it with nginx
|
||||
VOLUME ["/var/www/html"]
|
||||
|
||||
WORKDIR /var/www/html
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
CMD ["php-fpm"]
|
||||
|
@ -1,52 +0,0 @@
|
||||
FROM registry.ely.by/elyby/accounts-php:1.7.0-dev
|
||||
|
||||
# bootstrap скрипт для проекта
|
||||
COPY docker/php/bootstrap.sh /bootstrap.sh
|
||||
# Вносим конфигурации для крона и воркеров
|
||||
COPY docker/cron/* /etc/cron.d/
|
||||
COPY docker/supervisor/* /etc/supervisor/conf.d/
|
||||
|
||||
COPY id_rsa /root/.ssh/id_rsa
|
||||
|
||||
# Включаем поддержку ssh
|
||||
RUN chmod 400 ~/.ssh/id_rsa \
|
||||
&& eval $(ssh-agent -s) \
|
||||
&& ssh-add /root/.ssh/id_rsa \
|
||||
&& touch /root/.ssh/known_hosts \
|
||||
&& ssh-keyscan github.com gitlab.ely.by >> /root/.ssh/known_hosts
|
||||
|
||||
# Копируем списки зависимостей composer в родительскую директорию, которая не будет синкаться
|
||||
# с хостом через volume на dev окружении. В entrypoint эта папка будет скопирована обратно
|
||||
COPY ./composer.json /var/www/composer.json
|
||||
COPY ./composer.lock /var/www/composer.lock
|
||||
|
||||
# Устанавливаем зависимости PHP
|
||||
RUN cd .. \
|
||||
&& composer install --no-interaction --no-suggest \
|
||||
&& cd -
|
||||
|
||||
# Устанавливаем зависимости для Node.js
|
||||
# Делаем это отдельно, чтобы можно было воспользоваться кэшем, если от предыдущего билда
|
||||
# ничего не менялось в зависимостях
|
||||
RUN mkdir -p /var/www/frontend
|
||||
|
||||
COPY ./frontend/package.json /var/www/frontend/
|
||||
COPY ./frontend/yarn.lock /var/www/frontend/
|
||||
COPY ./frontend/scripts /var/www/frontend/scripts
|
||||
COPY ./frontend/webpack-utils /var/www/frontend/webpack-utils
|
||||
|
||||
RUN cd /var/www/frontend \
|
||||
&& yarn run build:install \
|
||||
&& cd -
|
||||
|
||||
# Наконец переносим все сорцы внутрь контейнера
|
||||
COPY . /var/www/html
|
||||
|
||||
# Билдим фронт
|
||||
RUN cd frontend \
|
||||
&& ln -s /var/www/frontend/node_modules $PWD/node_modules \
|
||||
&& yarn run build:quiet \
|
||||
&& rm node_modules \
|
||||
# Копируем билд наружу, чтобы его не затёрло volume в dev режиме
|
||||
&& cp -r ./dist /var/www/dist \
|
||||
&& cd -
|
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
namespace api\aop;
|
||||
|
||||
use api\aop\aspects;
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Go\Core\AspectContainer;
|
||||
use Go\Core\AspectKernel as BaseAspectKernel;
|
||||
|
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
namespace api\components\OAuth2;
|
||||
|
||||
use api\components\OAuth2\Storage;
|
||||
use League\OAuth2\Server\AuthorizationServer;
|
||||
use League\OAuth2\Server\Storage\AccessTokenInterface;
|
||||
use League\OAuth2\Server\Storage\RefreshTokenInterface;
|
||||
|
@ -9,11 +9,11 @@ class RefreshTokenEntity extends \League\OAuth2\Server\Entity\RefreshTokenEntity
|
||||
|
||||
private $sessionId;
|
||||
|
||||
public function isExpired() : bool {
|
||||
public function isExpired(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getSession() : SessionEntity {
|
||||
public function getSession(): SessionEntity {
|
||||
if ($this->session instanceof SessionEntity) {
|
||||
return $this->session;
|
||||
}
|
||||
@ -26,18 +26,18 @@ class RefreshTokenEntity extends \League\OAuth2\Server\Entity\RefreshTokenEntity
|
||||
return $sessionStorage->getById($this->sessionId);
|
||||
}
|
||||
|
||||
public function getSessionId() : int {
|
||||
public function getSessionId(): int {
|
||||
return $this->sessionId;
|
||||
}
|
||||
|
||||
public function setSession(OriginalSessionEntity $session) {
|
||||
public function setSession(OriginalSessionEntity $session): self {
|
||||
parent::setSession($session);
|
||||
$this->setSessionId($session->getId());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setSessionId(int $sessionId) {
|
||||
public function setSessionId(int $sessionId): void {
|
||||
$this->sessionId = $sessionId;
|
||||
}
|
||||
|
||||
|
@ -133,8 +133,10 @@ class AuthCodeGrant extends AbstractGrant {
|
||||
throw new Exception\InvalidRequestException('client_id');
|
||||
}
|
||||
|
||||
$clientSecret = $this->server->getRequest()->request->get('client_secret',
|
||||
$this->server->getRequest()->getPassword());
|
||||
$clientSecret = $this->server->getRequest()->request->get(
|
||||
'client_secret',
|
||||
$this->server->getRequest()->getPassword()
|
||||
);
|
||||
if ($clientSecret === null && $this->shouldRequireClientSecret()) {
|
||||
throw new Exception\InvalidRequestException('client_secret');
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ class AccessTokenStorage extends AbstractStorage implements AccessTokenInterface
|
||||
public function getScopes(OriginalAccessTokenEntity $token) {
|
||||
$scopes = $this->scopes($token->getId());
|
||||
$entities = [];
|
||||
foreach($scopes as $scope) {
|
||||
foreach ($scopes as $scope) {
|
||||
if ($this->server->getScopeStorage()->get($scope) !== null) {
|
||||
$entities[] = (new ScopeEntity($this->server))->hydrate(['id' => $scope]);
|
||||
}
|
||||
@ -59,11 +59,11 @@ class AccessTokenStorage extends AbstractStorage implements AccessTokenInterface
|
||||
$this->scopes($token->getId())->delete();
|
||||
}
|
||||
|
||||
private function key(string $token) : Key {
|
||||
private function key(string $token): Key {
|
||||
return new Key($this->dataTable, $token);
|
||||
}
|
||||
|
||||
private function scopes(string $token) : Set {
|
||||
private function scopes(string $token): Set {
|
||||
return new Set($this->dataTable, $token, 'scopes');
|
||||
}
|
||||
|
||||
|
@ -61,11 +61,11 @@ class AuthCodeStorage extends AbstractStorage implements AuthCodeInterface {
|
||||
$this->scopes($token->getId())->delete();
|
||||
}
|
||||
|
||||
private function key(string $token) : Key {
|
||||
private function key(string $token): Key {
|
||||
return new Key($this->dataTable, $token);
|
||||
}
|
||||
|
||||
private function scopes(string $token) : Set {
|
||||
private function scopes(string $token): Set {
|
||||
return new Set($this->dataTable, $token, 'scopes');
|
||||
}
|
||||
|
||||
|
@ -11,8 +11,8 @@ use yii\helpers\StringHelper;
|
||||
|
||||
class ClientStorage extends AbstractStorage implements ClientInterface {
|
||||
|
||||
const REDIRECT_STATIC_PAGE = 'static_page';
|
||||
const REDIRECT_STATIC_PAGE_WITH_CODE = 'static_page_with_code';
|
||||
private const REDIRECT_STATIC_PAGE = 'static_page';
|
||||
private const REDIRECT_STATIC_PAGE_WITH_CODE = 'static_page_with_code';
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
@ -66,7 +66,7 @@ class ClientStorage extends AbstractStorage implements ClientInterface {
|
||||
return $this->hydrate($model);
|
||||
}
|
||||
|
||||
private function hydrate(OauthClient $model) : ClientEntity {
|
||||
private function hydrate(OauthClient $model): ClientEntity {
|
||||
$entity = new ClientEntity($this->server);
|
||||
$entity->setId($model->id);
|
||||
$entity->setName($model->name);
|
||||
|
@ -51,12 +51,12 @@ class RefreshTokenStorage extends AbstractStorage implements RefreshTokenInterfa
|
||||
$this->sessionHash($token->getSessionId())->remove($token->getId());
|
||||
}
|
||||
|
||||
public function sessionHash(string $sessionId) : Set {
|
||||
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 {
|
||||
private function key(string $token): Key {
|
||||
return new Key($this->dataTable, $token);
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ class SessionStorage extends AbstractStorage implements SessionInterface {
|
||||
$this->getSessionModel($session->getId())->getScopes()->add($scope->getId());
|
||||
}
|
||||
|
||||
private function getSessionModel(string $sessionId) : OauthSession {
|
||||
private function getSessionModel(string $sessionId): OauthSession {
|
||||
$session = OauthSession::findOne($sessionId);
|
||||
if ($session === null) {
|
||||
throw new ErrorException('Cannot find oauth session');
|
||||
|
@ -214,7 +214,7 @@ class Component extends YiiUserComponent {
|
||||
|
||||
protected function createToken(Account $account): Token {
|
||||
$token = new Token();
|
||||
foreach($this->getClaims($account) as $claim) {
|
||||
foreach ($this->getClaims($account) as $claim) {
|
||||
$token->addClaim($claim);
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,10 @@ class Identity implements IdentityInterface {
|
||||
*/
|
||||
private $_accessToken;
|
||||
|
||||
private function __construct(AccessTokenEntity $accessToken) {
|
||||
$this->_accessToken = $accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws \yii\web\UnauthorizedHttpException
|
||||
@ -73,10 +77,6 @@ class Identity implements IdentityInterface {
|
||||
throw new NotSupportedException('This method used for cookie auth, except we using Bearer auth');
|
||||
}
|
||||
|
||||
private function __construct(AccessTokenEntity $accessToken) {
|
||||
$this->_accessToken = $accessToken;
|
||||
}
|
||||
|
||||
private function getSession(): OauthSession {
|
||||
return OauthSession::findOne($this->_accessToken->getSessionId());
|
||||
}
|
||||
|
@ -23,6 +23,11 @@ class JwtIdentity implements IdentityInterface {
|
||||
*/
|
||||
private $token;
|
||||
|
||||
private function __construct(string $rawToken, Token $token) {
|
||||
$this->rawToken = $rawToken;
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
public static function findIdentityByAccessToken($rawToken, $type = null): IdentityInterface {
|
||||
/** @var \api\components\User\Component $component */
|
||||
$component = Yii::$app->user;
|
||||
@ -86,9 +91,4 @@ class JwtIdentity implements IdentityInterface {
|
||||
throw new NotSupportedException('This method used for cookie auth, except we using Bearer auth');
|
||||
}
|
||||
|
||||
private function __construct(string $rawToken, Token $token) {
|
||||
$this->rawToken = $rawToken;
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use Emarref\Jwt\Claim\AbstractClaim;
|
||||
|
||||
class ScopesClaim extends AbstractClaim {
|
||||
|
||||
const NAME = 'ely-scopes';
|
||||
public const NAME = 'ely-scopes';
|
||||
|
||||
/**
|
||||
* ScopesClaim constructor.
|
||||
|
@ -21,7 +21,7 @@ class SubjectPrefixVerifier implements VerifierInterface {
|
||||
$subject = ($subjectClaim === null) ? null : $subjectClaim->getValue();
|
||||
|
||||
if (!StringHelper::startsWith($subject, $this->subjectPrefix)) {
|
||||
throw new InvalidSubjectException;
|
||||
throw new InvalidSubjectException();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,6 +58,7 @@ return [
|
||||
],
|
||||
'request' => [
|
||||
'baseUrl' => '/api',
|
||||
'enableCsrfCookie' => false,
|
||||
'parsers' => [
|
||||
'*' => api\request\RequestParser::class,
|
||||
],
|
||||
|
@ -2,8 +2,8 @@
|
||||
namespace api\controllers;
|
||||
|
||||
use api\models\authentication\ConfirmEmailForm;
|
||||
use api\models\authentication\RepeatAccountActivationForm;
|
||||
use api\models\authentication\RegistrationForm;
|
||||
use api\models\authentication\RepeatAccountActivationForm;
|
||||
use common\helpers\Error as E;
|
||||
use Yii;
|
||||
use yii\filters\AccessControl;
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
namespace api\models;
|
||||
|
||||
use common\helpers\Error as E;
|
||||
use api\models\base\ApiForm;
|
||||
use common\helpers\Error as E;
|
||||
use common\models\Account;
|
||||
use Yii;
|
||||
use yii\base\ErrorException;
|
||||
@ -29,7 +29,7 @@ class FeedbackForm extends ApiForm {
|
||||
];
|
||||
}
|
||||
|
||||
public function sendMessage() : bool {
|
||||
public function sendMessage(): bool {
|
||||
if (!$this->validate()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace api\models\authentication;
|
||||
|
||||
use api\aop\annotations\CollectModelMetrics;
|
||||
use api\models\base\ApiForm;
|
||||
use api\modules\accounts\models\ChangeUsernameForm;
|
||||
use api\validators\EmailActivationKeyValidator;
|
||||
use common\models\Account;
|
||||
use common\models\EmailActivation;
|
||||
@ -44,9 +45,6 @@ class ConfirmEmailForm extends ApiForm {
|
||||
throw new ErrorException('Unable activate user account.');
|
||||
}
|
||||
|
||||
$changeUsernameForm = new ChangeUsernameForm($account);
|
||||
$changeUsernameForm->createEventTask($account->id, $account->username, null);
|
||||
|
||||
$transaction->commit();
|
||||
|
||||
return Yii::$app->user->createJwtAuthenticationToken($account, true);
|
||||
|
@ -4,9 +4,9 @@ namespace api\models\authentication;
|
||||
use api\aop\annotations\CollectModelMetrics;
|
||||
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
|
||||
use api\models\base\ApiForm;
|
||||
use common\helpers\Error as E;
|
||||
use api\traits\AccountFinder;
|
||||
use common\components\UserFriendlyRandomKey;
|
||||
use common\helpers\Error as E;
|
||||
use common\models\Account;
|
||||
use common\models\confirmations\ForgotPassword;
|
||||
use common\models\EmailActivation;
|
||||
|
@ -3,9 +3,9 @@ namespace api\models\authentication;
|
||||
|
||||
use api\aop\annotations\CollectModelMetrics;
|
||||
use api\models\base\ApiForm;
|
||||
use api\traits\AccountFinder;
|
||||
use api\validators\TotpValidator;
|
||||
use common\helpers\Error as E;
|
||||
use api\traits\AccountFinder;
|
||||
use common\models\Account;
|
||||
use Yii;
|
||||
|
||||
@ -13,8 +13,11 @@ class LoginForm extends ApiForm {
|
||||
use AccountFinder;
|
||||
|
||||
public $login;
|
||||
|
||||
public $password;
|
||||
|
||||
public $totp;
|
||||
|
||||
public $rememberMe = false;
|
||||
|
||||
public function rules(): array {
|
||||
|
@ -11,7 +11,7 @@ class LogoutForm extends ApiForm {
|
||||
* @CollectModelMetrics(prefix="authentication.logout")
|
||||
* @return bool
|
||||
*/
|
||||
public function logout() : bool {
|
||||
public function logout(): bool {
|
||||
$component = Yii::$app->user;
|
||||
$session = $component->getActiveSession();
|
||||
if ($session === null) {
|
||||
|
@ -4,8 +4,8 @@ namespace api\models\authentication;
|
||||
use api\aop\annotations\CollectModelMetrics;
|
||||
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
|
||||
use api\models\base\ApiForm;
|
||||
use common\helpers\Error as E;
|
||||
use common\components\UserFriendlyRandomKey;
|
||||
use common\helpers\Error as E;
|
||||
use common\models\Account;
|
||||
use common\models\confirmations\RegistrationConfirmation;
|
||||
use common\models\UsernameHistory;
|
||||
@ -126,7 +126,7 @@ class RegistrationForm extends ApiForm {
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function canContinue(array $errors) : bool {
|
||||
protected function canContinue(array $errors): bool {
|
||||
if (ArrayHelper::getValue($errors, 'username') === E::USERNAME_NOT_AVAILABLE) {
|
||||
$duplicatedUsername = Account::findOne([
|
||||
'username' => $this->username,
|
||||
|
@ -5,8 +5,8 @@ use api\aop\annotations\CollectModelMetrics;
|
||||
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
|
||||
use api\exceptions\ThisShouldNotHappenException;
|
||||
use api\models\base\ApiForm;
|
||||
use common\helpers\Error as E;
|
||||
use common\components\UserFriendlyRandomKey;
|
||||
use common\helpers\Error as E;
|
||||
use common\models\Account;
|
||||
use common\models\confirmations\RegistrationConfirmation;
|
||||
use common\models\EmailActivation;
|
||||
|
@ -21,8 +21,6 @@ abstract class BaseAccountAction extends Action {
|
||||
return $this->formatSuccessResult($model);
|
||||
}
|
||||
|
||||
abstract protected function getFormClassName(): string;
|
||||
|
||||
public function getRequestData(): array {
|
||||
return Yii::$app->request->post();
|
||||
}
|
||||
@ -35,6 +33,8 @@ abstract class BaseAccountAction extends Action {
|
||||
return [];
|
||||
}
|
||||
|
||||
abstract protected function getFormClassName(): string;
|
||||
|
||||
private function formatFailedResult(AccountActionForm $model): array {
|
||||
$response = [
|
||||
'success' => false,
|
||||
|
@ -6,10 +6,6 @@ use api\modules\accounts\models\ChangeEmailForm;
|
||||
|
||||
class ChangeEmailAction extends BaseAccountAction {
|
||||
|
||||
protected function getFormClassName(): string {
|
||||
return ChangeEmailForm::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ChangeEmailForm|AccountActionForm $model
|
||||
* @return array
|
||||
@ -20,4 +16,8 @@ class ChangeEmailAction extends BaseAccountAction {
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormClassName(): string {
|
||||
return ChangeEmailForm::class;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,10 +7,6 @@ use common\helpers\Error as E;
|
||||
|
||||
class EmailVerificationAction extends BaseAccountAction {
|
||||
|
||||
protected function getFormClassName(): string {
|
||||
return SendEmailVerificationForm::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SendEmailVerificationForm|AccountActionForm $model
|
||||
* @return array
|
||||
@ -29,4 +25,8 @@ class EmailVerificationAction extends BaseAccountAction {
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormClassName(): string {
|
||||
return SendEmailVerificationForm::class;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,11 @@
|
||||
<?php
|
||||
namespace api\modules\accounts\models;
|
||||
|
||||
use api\exceptions\ThisShouldNotHappenException;
|
||||
use api\modules\internal\helpers\Error as E;
|
||||
use common\helpers\Amqp;
|
||||
use common\models\Account;
|
||||
use common\models\amqp\AccountBanned;
|
||||
use PhpAmqpLib\Message\AMQPMessage;
|
||||
use common\tasks\ClearAccountSessions;
|
||||
use Yii;
|
||||
use yii\base\ErrorException;
|
||||
|
||||
class BanAccountForm extends AccountActionForm {
|
||||
|
||||
@ -38,7 +36,7 @@ class BanAccountForm extends AccountActionForm {
|
||||
];
|
||||
}
|
||||
|
||||
public function validateAccountActivity() {
|
||||
public function validateAccountActivity(): void {
|
||||
if ($this->getAccount()->status === Account::STATUS_BANNED) {
|
||||
$this->addError('account', E::ACCOUNT_ALREADY_BANNED);
|
||||
}
|
||||
@ -54,27 +52,14 @@ class BanAccountForm extends AccountActionForm {
|
||||
$account = $this->getAccount();
|
||||
$account->status = Account::STATUS_BANNED;
|
||||
if (!$account->save()) {
|
||||
throw new ErrorException('Cannot ban account');
|
||||
throw new ThisShouldNotHappenException('Cannot ban account');
|
||||
}
|
||||
|
||||
$this->createTask();
|
||||
Yii::$app->queue->push(ClearAccountSessions::createFromAccount($account));
|
||||
|
||||
$transaction->commit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function createTask(): void {
|
||||
$model = new AccountBanned();
|
||||
$model->accountId = $this->getAccount()->id;
|
||||
$model->duration = $this->duration;
|
||||
$model->message = $this->message;
|
||||
|
||||
$message = Amqp::getInstance()->prepareMessage($model, [
|
||||
'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
|
||||
]);
|
||||
|
||||
Amqp::sendToEventsExchange('accounts.account-banned', $message);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,13 +2,10 @@
|
||||
namespace api\modules\accounts\models;
|
||||
|
||||
use api\aop\annotations\CollectModelMetrics;
|
||||
use api\exceptions\ThisShouldNotHappenException;
|
||||
use api\validators\EmailActivationKeyValidator;
|
||||
use common\helpers\Amqp;
|
||||
use common\models\amqp\EmailChanged;
|
||||
use common\models\EmailActivation;
|
||||
use PhpAmqpLib\Message\AMQPMessage;
|
||||
use Yii;
|
||||
use yii\base\ErrorException;
|
||||
|
||||
class ChangeEmailForm extends AccountActionForm {
|
||||
|
||||
@ -35,30 +32,14 @@ class ChangeEmailForm extends AccountActionForm {
|
||||
$activation->delete();
|
||||
|
||||
$account = $this->getAccount();
|
||||
$oldEmail = $account->email;
|
||||
$account->email = $activation->newEmail;
|
||||
if (!$account->save()) {
|
||||
throw new ErrorException('Cannot save new account email value');
|
||||
throw new ThisShouldNotHappenException('Cannot save new account email value');
|
||||
}
|
||||
|
||||
$this->createTask($account->id, $account->email, $oldEmail);
|
||||
|
||||
$transaction->commit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function createTask(int $accountId, string $newEmail, string $oldEmail): void {
|
||||
$model = new EmailChanged;
|
||||
$model->accountId = $accountId;
|
||||
$model->oldEmail = $oldEmail;
|
||||
$model->newEmail = $newEmail;
|
||||
|
||||
$message = Amqp::getInstance()->prepareMessage($model, [
|
||||
'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
|
||||
]);
|
||||
|
||||
Amqp::sendToEventsExchange('accounts.email-changed', $message);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,13 +4,10 @@ namespace api\modules\accounts\models;
|
||||
use api\aop\annotations\CollectModelMetrics;
|
||||
use api\exceptions\ThisShouldNotHappenException;
|
||||
use api\validators\PasswordRequiredValidator;
|
||||
use common\helpers\Amqp;
|
||||
use common\models\amqp\UsernameChanged;
|
||||
use common\models\UsernameHistory;
|
||||
use common\tasks\PullMojangUsername;
|
||||
use common\validators\UsernameValidator;
|
||||
use PhpAmqpLib\Message\AMQPMessage;
|
||||
use Yii;
|
||||
use yii\base\ErrorException;
|
||||
|
||||
class ChangeUsernameForm extends AccountActionForm {
|
||||
|
||||
@ -42,7 +39,6 @@ class ChangeUsernameForm extends AccountActionForm {
|
||||
|
||||
$transaction = Yii::$app->db->beginTransaction();
|
||||
|
||||
$oldNickname = $account->username;
|
||||
$account->username = $this->username;
|
||||
if (!$account->save()) {
|
||||
throw new ThisShouldNotHappenException('Cannot save account model with new username');
|
||||
@ -52,36 +48,14 @@ class ChangeUsernameForm extends AccountActionForm {
|
||||
$usernamesHistory->account_id = $account->id;
|
||||
$usernamesHistory->username = $account->username;
|
||||
if (!$usernamesHistory->save()) {
|
||||
throw new ErrorException('Cannot save username history record');
|
||||
throw new ThisShouldNotHappenException('Cannot save username history record');
|
||||
}
|
||||
|
||||
$this->createEventTask($account->id, $account->username, $oldNickname);
|
||||
Yii::$app->queue->push(PullMojangUsername::createFromAccount($account));
|
||||
|
||||
$transaction->commit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: вынести это в отдельную сущность, т.к. эта команда используется внутри формы регистрации
|
||||
*
|
||||
* @param integer $accountId
|
||||
* @param string $newNickname
|
||||
* @param string $oldNickname
|
||||
*
|
||||
* @throws \PhpAmqpLib\Exception\AMQPExceptionInterface|\yii\base\Exception
|
||||
*/
|
||||
public function createEventTask($accountId, $newNickname, $oldNickname): void {
|
||||
$model = new UsernameChanged();
|
||||
$model->accountId = $accountId;
|
||||
$model->oldUsername = $oldNickname;
|
||||
$model->newUsername = $newNickname;
|
||||
|
||||
$message = Amqp::getInstance()->prepareMessage($model, [
|
||||
'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
|
||||
]);
|
||||
|
||||
Amqp::sendToEventsExchange('accounts.username-changed', $message);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,10 @@
|
||||
<?php
|
||||
namespace api\modules\accounts\models;
|
||||
|
||||
use api\exceptions\ThisShouldNotHappenException;
|
||||
use api\modules\internal\helpers\Error as E;
|
||||
use common\helpers\Amqp;
|
||||
use common\models\Account;
|
||||
use common\models\amqp\AccountPardoned;
|
||||
use PhpAmqpLib\Message\AMQPMessage;
|
||||
use Yii;
|
||||
use yii\base\ErrorException;
|
||||
|
||||
class PardonAccountForm extends AccountActionForm {
|
||||
|
||||
@ -33,25 +30,12 @@ class PardonAccountForm extends AccountActionForm {
|
||||
$account = $this->getAccount();
|
||||
$account->status = Account::STATUS_ACTIVE;
|
||||
if (!$account->save()) {
|
||||
throw new ErrorException('Cannot pardon account');
|
||||
throw new ThisShouldNotHappenException('Cannot pardon account');
|
||||
}
|
||||
|
||||
$this->createTask();
|
||||
|
||||
$transaction->commit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function createTask(): void {
|
||||
$model = new AccountPardoned();
|
||||
$model->accountId = $this->getAccount()->id;
|
||||
|
||||
$message = Amqp::getInstance()->prepareMessage($model, [
|
||||
'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
|
||||
]);
|
||||
|
||||
Amqp::sendToEventsExchange('accounts.account-pardoned', $message);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ use OTPHP\TOTP;
|
||||
|
||||
trait TotpHelper {
|
||||
|
||||
abstract public function getAccount(): Account;
|
||||
|
||||
protected function getTotp(): TOTP {
|
||||
$account = $this->getAccount();
|
||||
$totp = TOTP::create($account->otp_secret);
|
||||
@ -15,6 +17,4 @@ trait TotpHelper {
|
||||
return $totp;
|
||||
}
|
||||
|
||||
abstract public function getAccount(): Account;
|
||||
|
||||
}
|
||||
|
@ -17,10 +17,10 @@ class AuthenticationController extends Controller {
|
||||
public function verbs() {
|
||||
return [
|
||||
'authenticate' => ['POST'],
|
||||
'refresh' => ['POST'],
|
||||
'validate' => ['POST'],
|
||||
'signout' => ['POST'],
|
||||
'invalidate' => ['POST'],
|
||||
'refresh' => ['POST'],
|
||||
'validate' => ['POST'],
|
||||
'signout' => ['POST'],
|
||||
'invalidate' => ['POST'],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -14,11 +14,11 @@ class AuthenticateData {
|
||||
$this->minecraftAccessKey = $minecraftAccessKey;
|
||||
}
|
||||
|
||||
public function getMinecraftAccessKey() : MinecraftAccessKey {
|
||||
public function getMinecraftAccessKey(): MinecraftAccessKey {
|
||||
return $this->minecraftAccessKey;
|
||||
}
|
||||
|
||||
public function getResponseData(bool $includeAvailableProfiles = false) : array {
|
||||
public function getResponseData(bool $includeAvailableProfiles = false): array {
|
||||
$accessKey = $this->minecraftAccessKey;
|
||||
$account = $accessKey->account;
|
||||
|
||||
|
@ -14,7 +14,9 @@ use common\models\MinecraftAccessKey;
|
||||
class AuthenticationForm extends ApiForm {
|
||||
|
||||
public $username;
|
||||
|
||||
public $password;
|
||||
|
||||
public $clientToken;
|
||||
|
||||
public function rules() {
|
||||
@ -41,13 +43,15 @@ class AuthenticationForm extends ApiForm {
|
||||
if (isset($errors['totp'])) {
|
||||
Authserver::error("User with login = '{$this->username}' protected by two factor auth.");
|
||||
throw new ForbiddenOperationException('Account protected with two factor auth.');
|
||||
} elseif (isset($errors['login'])) {
|
||||
}
|
||||
|
||||
if (isset($errors['login'])) {
|
||||
if ($errors['login'] === E::ACCOUNT_BANNED) {
|
||||
Authserver::error("User with login = '{$this->username}' is banned");
|
||||
throw new ForbiddenOperationException('This account has been suspended.');
|
||||
} else {
|
||||
Authserver::error("Cannot find user by login = '{$this->username}'");
|
||||
}
|
||||
|
||||
Authserver::error("Cannot find user by login = '{$this->username}'");
|
||||
} elseif (isset($errors['password'])) {
|
||||
Authserver::error("User with login = '{$this->username}' passed wrong password.");
|
||||
}
|
||||
@ -72,7 +76,7 @@ class AuthenticationForm extends ApiForm {
|
||||
return $dataModel;
|
||||
}
|
||||
|
||||
protected function createMinecraftAccessToken(Account $account) : MinecraftAccessKey {
|
||||
protected function createMinecraftAccessToken(Account $account): MinecraftAccessKey {
|
||||
/** @var MinecraftAccessKey|null $accessTokenModel */
|
||||
$accessTokenModel = MinecraftAccessKey::findOne([
|
||||
'account_id' => $account->id,
|
||||
@ -92,7 +96,7 @@ class AuthenticationForm extends ApiForm {
|
||||
return $accessTokenModel;
|
||||
}
|
||||
|
||||
protected function createLoginForm() : LoginForm {
|
||||
protected function createLoginForm(): LoginForm {
|
||||
return new LoginForm();
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ use common\models\MinecraftAccessKey;
|
||||
class InvalidateForm extends ApiForm {
|
||||
|
||||
public $accessToken;
|
||||
|
||||
public $clientToken;
|
||||
|
||||
public function rules() {
|
||||
@ -20,7 +21,7 @@ class InvalidateForm extends ApiForm {
|
||||
* @return bool
|
||||
* @throws \api\modules\authserver\exceptions\AuthserverException
|
||||
*/
|
||||
public function invalidateToken() : bool {
|
||||
public function invalidateToken(): bool {
|
||||
$this->validate();
|
||||
|
||||
$token = MinecraftAccessKey::findOne([
|
||||
|
@ -10,6 +10,7 @@ use common\models\MinecraftAccessKey;
|
||||
class RefreshTokenForm extends ApiForm {
|
||||
|
||||
public $accessToken;
|
||||
|
||||
public $clientToken;
|
||||
|
||||
public function rules() {
|
||||
|
@ -12,6 +12,7 @@ use Yii;
|
||||
class SignoutForm extends ApiForm {
|
||||
|
||||
public $username;
|
||||
|
||||
public $password;
|
||||
|
||||
public function rules() {
|
||||
@ -20,7 +21,7 @@ class SignoutForm extends ApiForm {
|
||||
];
|
||||
}
|
||||
|
||||
public function signout() : bool {
|
||||
public function signout(): bool {
|
||||
$this->validate();
|
||||
|
||||
$loginForm = new LoginForm();
|
||||
|
@ -16,7 +16,7 @@ class ValidateForm extends ApiForm {
|
||||
];
|
||||
}
|
||||
|
||||
public function validateToken() : bool {
|
||||
public function validateToken(): bool {
|
||||
$this->validate();
|
||||
|
||||
/** @var MinecraftAccessKey|null $result */
|
||||
|
@ -54,7 +54,7 @@ class ApiController extends Controller {
|
||||
public function actionUsernamesByUuid($uuid) {
|
||||
try {
|
||||
$uuid = Uuid::fromString($uuid)->toString();
|
||||
} catch(\InvalidArgumentException $e) {
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return $this->illegalArgumentResponse('Invalid uuid format.');
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ class ApiController extends Controller {
|
||||
->all();
|
||||
|
||||
$data = [];
|
||||
foreach($usernameHistory as $record) {
|
||||
foreach ($usernameHistory as $record) {
|
||||
$data[] = [
|
||||
'name' => $record->username,
|
||||
'changedToAt' => $record->applied_in * 1000,
|
||||
@ -94,7 +94,7 @@ class ApiController extends Controller {
|
||||
return $this->illegalArgumentResponse('Not more that 100 profile name per call is allowed.');
|
||||
}
|
||||
|
||||
foreach($usernames as $username) {
|
||||
foreach ($usernames as $username) {
|
||||
if (empty($username) || is_array($username)) {
|
||||
return $this->illegalArgumentResponse('profileName can not be null, empty or array key.');
|
||||
}
|
||||
@ -108,7 +108,7 @@ class ApiController extends Controller {
|
||||
->all();
|
||||
|
||||
$responseData = [];
|
||||
foreach($accounts as $account) {
|
||||
foreach ($accounts as $account) {
|
||||
$responseData[] = [
|
||||
'id' => str_replace('-', '', $account->uuid),
|
||||
'name' => $account->username,
|
||||
|
@ -38,7 +38,7 @@ class AuthorizationController extends Controller {
|
||||
return [
|
||||
'validate' => ['GET'],
|
||||
'complete' => ['POST'],
|
||||
'token' => ['POST'],
|
||||
'token' => ['POST'],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -136,7 +136,8 @@ class ClientsController extends Controller {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
$clients = $account->oauthClients;
|
||||
/** @var OauthClient[] $clients */
|
||||
$clients = $account->getOauthClients()->orderBy(['created_at' => SORT_ASC])->all();
|
||||
$result = array_map(function(OauthClient $client) {
|
||||
return $this->formatClient($client);
|
||||
}, $clients);
|
||||
@ -152,13 +153,13 @@ class ClientsController extends Controller {
|
||||
'name' => $client->name,
|
||||
'websiteUrl' => $client->website_url,
|
||||
'createdAt' => $client->created_at,
|
||||
'countUsers' => (int)$client->getSessions()->count(),
|
||||
];
|
||||
|
||||
switch ($client->type) {
|
||||
case OauthClient::TYPE_APPLICATION:
|
||||
$result['description'] = $client->description;
|
||||
$result['redirectUri'] = $client->redirect_uri;
|
||||
$result['countUsers'] = (int)$client->getSessions()->count();
|
||||
break;
|
||||
case OauthClient::TYPE_MINECRAFT_SERVER:
|
||||
$result['minecraftServerIp'] = $client->minecraft_server_ip;
|
||||
|
@ -4,4 +4,5 @@ declare(strict_types=1);
|
||||
namespace api\modules\oauth\exceptions;
|
||||
|
||||
interface OauthException {
|
||||
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ class ApplicationType extends BaseOauthClientType {
|
||||
public function rules(): array {
|
||||
return ArrayHelper::merge(parent::rules(), [
|
||||
['redirectUri', 'required', 'message' => E::REDIRECT_URI_REQUIRED],
|
||||
['redirectUri', 'url', 'validSchemes' => ['[\w]+'], 'message' => E::REDIRECT_URI_INVALID],
|
||||
['redirectUri', 'url', 'validSchemes' => ['[\w]+'], 'message' => E::REDIRECT_URI_INVALID],
|
||||
['description', 'string'],
|
||||
]);
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ class SessionController extends Controller {
|
||||
public function actionProfile($uuid) {
|
||||
try {
|
||||
$uuid = Uuid::fromString($uuid)->toString();
|
||||
} catch(\InvalidArgumentException $e) {
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new IllegalArgumentException('Invalid uuid format.');
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace api\modules\session\exceptions;
|
||||
|
||||
class ForbiddenOperationException extends SessionServerException {
|
||||
class ForbiddenOperationException extends SessionServerException {
|
||||
|
||||
public function __construct($message, $code = 0, \Exception $previous = null) {
|
||||
parent::__construct($status = 401, $message, $code, $previous);
|
||||
|
@ -10,6 +10,7 @@ use yii\web\TooManyRequestsHttpException;
|
||||
class RateLimiter extends \yii\filters\RateLimiter {
|
||||
|
||||
public $limit = 180;
|
||||
|
||||
public $limitTime = 3600; // 1h
|
||||
|
||||
public $authserverDomain;
|
||||
@ -55,10 +56,9 @@ class RateLimiter extends \yii\filters\RateLimiter {
|
||||
$ip = $request->getUserIP();
|
||||
$key = $this->buildKey($ip);
|
||||
|
||||
$redis = $this->getRedis();
|
||||
$countRequests = (int)$redis->incr($key);
|
||||
$countRequests = (int)Yii::$app->redis->incr($key);
|
||||
if ($countRequests === 1) {
|
||||
$redis->executeCommand('EXPIRE', [$key, $this->limitTime]);
|
||||
Yii::$app->redis->expire($key, $this->limitTime);
|
||||
}
|
||||
|
||||
if ($countRequests > $this->limit) {
|
||||
@ -66,13 +66,6 @@ class RateLimiter extends \yii\filters\RateLimiter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \common\components\Redis\Connection
|
||||
*/
|
||||
public function getRedis() {
|
||||
return Yii::$app->redis;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return OauthClient|null
|
||||
@ -100,7 +93,7 @@ class RateLimiter extends \yii\filters\RateLimiter {
|
||||
return $this->server;
|
||||
}
|
||||
|
||||
protected function buildKey($ip) : string {
|
||||
protected function buildKey($ip): string {
|
||||
return 'sessionserver:ratelimit:' . $ip;
|
||||
}
|
||||
|
||||
|
@ -7,9 +7,9 @@ use api\modules\session\models\protocols\JoinInterface;
|
||||
use api\modules\session\Module as Session;
|
||||
use api\modules\session\validators\RequiredValidator;
|
||||
use common\helpers\StringHelper;
|
||||
use common\rbac\Permissions as P;
|
||||
use common\models\Account;
|
||||
use common\models\MinecraftAccessKey;
|
||||
use common\rbac\Permissions as P;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Yii;
|
||||
use yii\base\ErrorException;
|
||||
@ -19,7 +19,9 @@ use yii\web\UnauthorizedHttpException;
|
||||
class JoinForm extends Model {
|
||||
|
||||
public $accessToken;
|
||||
|
||||
public $selectedProfile;
|
||||
|
||||
public $serverId;
|
||||
|
||||
/**
|
||||
|
@ -6,7 +6,7 @@ use Yii;
|
||||
|
||||
class SessionModel {
|
||||
|
||||
const KEY_TIME = 120; // 2 min
|
||||
private const KEY_TIME = 120; // 2 min
|
||||
|
||||
public $username;
|
||||
|
||||
@ -19,7 +19,7 @@ class SessionModel {
|
||||
|
||||
public static function find(string $username, string $serverId): ?self {
|
||||
$key = static::buildKey($username, $serverId);
|
||||
$result = Yii::$app->redis->executeCommand('GET', [$key]);
|
||||
$result = Yii::$app->redis->get($key);
|
||||
if (!$result) {
|
||||
return null;
|
||||
}
|
||||
@ -36,11 +36,11 @@ class SessionModel {
|
||||
'serverId' => $this->serverId,
|
||||
]);
|
||||
|
||||
return Yii::$app->redis->executeCommand('SETEX', [$key, self::KEY_TIME, $data]);
|
||||
return Yii::$app->redis->setex($key, self::KEY_TIME, $data);
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
return Yii::$app->redis->executeCommand('DEL', [static::buildKey($this->username, $this->serverId)]);
|
||||
return Yii::$app->redis->del(static::buildKey($this->username, $this->serverId));
|
||||
}
|
||||
|
||||
public function getAccount(): ?Account {
|
||||
|
@ -4,6 +4,7 @@ namespace api\modules\session\models\protocols;
|
||||
abstract class BaseHasJoined implements HasJoinedInterface {
|
||||
|
||||
private $username;
|
||||
|
||||
private $serverId;
|
||||
|
||||
public function __construct(string $username, string $serverId) {
|
||||
|
@ -4,10 +4,13 @@ namespace api\modules\session\models\protocols;
|
||||
class LegacyJoin extends BaseJoin {
|
||||
|
||||
private $user;
|
||||
|
||||
private $sessionId;
|
||||
|
||||
private $serverId;
|
||||
|
||||
private $accessToken;
|
||||
|
||||
private $uuid;
|
||||
|
||||
public function __construct(string $user, string $sessionId, string $serverId) {
|
||||
@ -18,7 +21,7 @@ class LegacyJoin extends BaseJoin {
|
||||
$this->parseSessionId($this->sessionId);
|
||||
}
|
||||
|
||||
public function getAccessToken() : string {
|
||||
public function getAccessToken(): string {
|
||||
return $this->accessToken;
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,9 @@ namespace api\modules\session\models\protocols;
|
||||
class ModernJoin extends BaseJoin {
|
||||
|
||||
private $accessToken;
|
||||
|
||||
private $selectedProfile;
|
||||
|
||||
private $serverId;
|
||||
|
||||
public function __construct(string $accessToken, string $selectedProfile, string $serverId) {
|
||||
|
@ -7,7 +7,7 @@ trait AccountFinder {
|
||||
|
||||
private $account;
|
||||
|
||||
public abstract function getLogin(): string;
|
||||
abstract public function getLogin(): string;
|
||||
|
||||
public function getAccount(): ?Account {
|
||||
if ($this->account === null) {
|
||||
|
@ -16,16 +16,15 @@ class Yii extends \yii\BaseYii {
|
||||
* Class BaseApplication
|
||||
* Used for properties that are identical for both WebApplication and ConsoleApplication
|
||||
*
|
||||
* @property \yii\db\Connection $unbufferedDb
|
||||
* @property \yii\swiftmailer\Mailer $mailer
|
||||
* @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
|
||||
* @property \api\components\OAuth2\Component $oauth
|
||||
* @property \common\components\StatsD $statsd
|
||||
* @property \yii\queue\Queue $queue
|
||||
* @property \yii\db\Connection $unbufferedDb
|
||||
* @property \yii\swiftmailer\Mailer $mailer
|
||||
* @property \yii\redis\Connection $redis
|
||||
* @property \GuzzleHttp\Client $guzzle
|
||||
* @property \common\components\EmailRenderer $emailRenderer
|
||||
* @property \mito\sentry\Component $sentry
|
||||
* @property \api\components\OAuth2\Component $oauth
|
||||
* @property \common\components\StatsD $statsd
|
||||
* @property \yii\queue\Queue $queue
|
||||
*/
|
||||
abstract class BaseApplication extends yii\base\Application {
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ use yii\base\Behavior;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
class DataBehavior extends Behavior {
|
||||
|
||||
/**
|
||||
* @var string имя атрибута, к которому будет применяться поведение
|
||||
*/
|
||||
|
@ -30,7 +30,7 @@ class EmailActivationExpirationBehavior extends Behavior {
|
||||
* @see EmailActivation::compareTime()
|
||||
* @return bool
|
||||
*/
|
||||
public function canRepeat() : bool {
|
||||
public function canRepeat(): bool {
|
||||
return $this->compareTime($this->repeatTimeout);
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ class EmailActivationExpirationBehavior extends Behavior {
|
||||
* @see EmailActivation::compareTime()
|
||||
* @return bool
|
||||
*/
|
||||
public function isExpired() : bool {
|
||||
public function isExpired(): bool {
|
||||
return $this->compareTime($this->expirationTimeout);
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@ class EmailActivationExpirationBehavior extends Behavior {
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function canRepeatIn() : int {
|
||||
public function canRepeatIn(): int {
|
||||
return $this->calculateTime($this->repeatTimeout);
|
||||
}
|
||||
|
||||
@ -62,11 +62,11 @@ class EmailActivationExpirationBehavior extends Behavior {
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function expireIn() : int {
|
||||
public function expireIn(): int {
|
||||
return $this->calculateTime($this->expirationTimeout);
|
||||
}
|
||||
|
||||
protected function compareTime(int $value) : bool {
|
||||
protected function compareTime(int $value): bool {
|
||||
if ($value < 0) {
|
||||
return false;
|
||||
}
|
||||
@ -78,7 +78,7 @@ class EmailActivationExpirationBehavior extends Behavior {
|
||||
return time() > $this->calculateTime($value);
|
||||
}
|
||||
|
||||
protected function calculateTime(int $value) : int {
|
||||
protected function calculateTime(int $value): int {
|
||||
return $this->owner->created_at + $value;
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ class PrimaryKeyValueBehavior extends Behavior {
|
||||
];
|
||||
}
|
||||
|
||||
public function setPrimaryKeyValue() : bool {
|
||||
public function setPrimaryKeyValue(): bool {
|
||||
if ($this->owner->getPrimaryKey() === null) {
|
||||
$this->refreshPrimaryKeyValue();
|
||||
}
|
||||
@ -40,15 +40,15 @@ class PrimaryKeyValueBehavior extends Behavior {
|
||||
$this->owner->{$this->getPrimaryKeyName()} = $key;
|
||||
}
|
||||
|
||||
protected function generateValue() : string {
|
||||
protected function generateValue(): string {
|
||||
return (string)call_user_func($this->value);
|
||||
}
|
||||
|
||||
protected function isValueExists(string $key) : bool {
|
||||
protected function isValueExists(string $key): bool {
|
||||
return $this->owner->find()->andWhere([$this->getPrimaryKeyName() => $key])->exists();
|
||||
}
|
||||
|
||||
protected function getPrimaryKeyName() : string {
|
||||
protected function getPrimaryKeyName(): string {
|
||||
$owner = $this->owner;
|
||||
$primaryKeys = $owner->primaryKey();
|
||||
if (!isset($primaryKeys[0])) {
|
||||
|
@ -43,7 +43,7 @@ class EmailRenderer extends Component {
|
||||
$this->renderer->setBaseDomain($this->buildBasePath());
|
||||
}
|
||||
|
||||
public function getBaseDomain() : string {
|
||||
public function getBaseDomain(): string {
|
||||
return $this->_baseDomain;
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,6 @@ class ElyDecorator implements DecoratorInterface {
|
||||
$topPadding,
|
||||
$multiple
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
private function encodeSvgToBase64(string $filePath): string {
|
||||
|
@ -1,177 +0,0 @@
|
||||
<?php
|
||||
namespace common\components\RabbitMQ;
|
||||
|
||||
use yii\base\Exception;
|
||||
use yii\helpers\Json;
|
||||
use PhpAmqpLib\Channel\AMQPChannel;
|
||||
use PhpAmqpLib\Connection\AMQPStreamConnection;
|
||||
use PhpAmqpLib\Message\AMQPMessage;
|
||||
|
||||
/**
|
||||
* Не гибкий компонент для работы с RabbitMQ, заточенный под нужны текущего проекта
|
||||
*
|
||||
* Компонент основан на расширении Alexey Kuznetsov <mirakuru@webtoucher.ru>
|
||||
*
|
||||
* @property AMQPStreamConnection $connection AMQP connection.
|
||||
* @property AMQPChannel $channel AMQP channel.
|
||||
*/
|
||||
class Component extends \yii\base\Component {
|
||||
|
||||
const TYPE_TOPIC = 'topic';
|
||||
const TYPE_DIRECT = 'direct';
|
||||
const TYPE_HEADERS = 'headers';
|
||||
const TYPE_FANOUT = 'fanout';
|
||||
|
||||
/**
|
||||
* @var AMQPStreamConnection
|
||||
*/
|
||||
protected $amqpConnection;
|
||||
|
||||
/**
|
||||
* @var AMQPChannel[]
|
||||
*/
|
||||
protected $channels = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $host = '127.0.0.1';
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $port = 5672;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $password;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $vhost = '/';
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function init() {
|
||||
parent::init();
|
||||
if (empty($this->user)) {
|
||||
throw new Exception("Parameter 'user' was not set for AMQP connection.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AMQPStreamConnection
|
||||
*/
|
||||
public function getConnection() {
|
||||
if (!$this->amqpConnection) {
|
||||
$this->amqpConnection = new AMQPStreamConnection(
|
||||
$this->host,
|
||||
$this->port,
|
||||
$this->user,
|
||||
$this->password,
|
||||
$this->vhost
|
||||
);
|
||||
}
|
||||
|
||||
return $this->amqpConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $channel_id
|
||||
* @return AMQPChannel
|
||||
*/
|
||||
public function getChannel($channel_id = null) {
|
||||
$index = $channel_id ?: 'default';
|
||||
if (!array_key_exists($index, $this->channels)) {
|
||||
$this->channels[$index] = $this->getConnection()->channel($channel_id);
|
||||
}
|
||||
|
||||
return $this->channels[$index];
|
||||
}
|
||||
|
||||
// TODO: метод sendToQueue
|
||||
|
||||
/**
|
||||
* Sends message to the exchange.
|
||||
*
|
||||
* @param string $exchangeName
|
||||
* @param string $routingKey
|
||||
* @param string|array $message
|
||||
* @param array $exchangeArgs
|
||||
* @param array $publishArgs
|
||||
*/
|
||||
public function sendToExchange($exchangeName, $routingKey, $message, $exchangeArgs = [], $publishArgs = []) {
|
||||
$message = $this->prepareMessage($message);
|
||||
$channel = $this->getChannel();
|
||||
$channel->exchange_declare(...$this->prepareExchangeArgs($exchangeName, $exchangeArgs));
|
||||
$channel->basic_publish(...$this->preparePublishArgs($message, $exchangeName, $routingKey, $publishArgs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Объединяет переданный набор аргументов с поведением по умолчанию
|
||||
*
|
||||
* @param string $exchangeName
|
||||
* @param array $args
|
||||
* @return array
|
||||
*/
|
||||
protected function prepareExchangeArgs($exchangeName, array $args) {
|
||||
return array_replace([
|
||||
$exchangeName,
|
||||
self::TYPE_FANOUT,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
], $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Объединяет переданный набор аргументов с поведением по умолчанию
|
||||
*
|
||||
* @param AMQPMessage $message
|
||||
* @param string $exchangeName
|
||||
* @param string $routeKey
|
||||
* @param array $args
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function preparePublishArgs($message, $exchangeName, $routeKey, array $args) {
|
||||
return array_replace([
|
||||
$message,
|
||||
$exchangeName,
|
||||
$routeKey,
|
||||
], $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns prepaired AMQP message.
|
||||
*
|
||||
* @param string|array|object $message
|
||||
* @param array $properties
|
||||
* @return AMQPMessage
|
||||
* @throws Exception If message is empty.
|
||||
*/
|
||||
public function prepareMessage($message, $properties = null) {
|
||||
if ($message instanceof AMQPMessage) {
|
||||
return $message;
|
||||
}
|
||||
|
||||
if (empty($message)) {
|
||||
throw new Exception('AMQP message can not be empty');
|
||||
}
|
||||
|
||||
if (is_array($message) || is_object($message)) {
|
||||
$message = Json::encode($message);
|
||||
}
|
||||
|
||||
return new AMQPMessage($message, $properties);
|
||||
}
|
||||
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
<?php
|
||||
namespace common\components\RabbitMQ;
|
||||
|
||||
use Yii;
|
||||
|
||||
class Helper {
|
||||
|
||||
/**
|
||||
* @return Component $amqp
|
||||
*/
|
||||
public static function getInstance() {
|
||||
return Yii::$app->amqp;
|
||||
}
|
||||
|
||||
public static function sendToExchange($exchange, $routingKey, $message, $exchangeArgs = []) {
|
||||
static::getInstance()->sendToExchange($exchange, $routingKey, $message, $exchangeArgs);
|
||||
}
|
||||
|
||||
public static function sendToEventsExchange($routingKey, $message) {
|
||||
static::sendToExchange('events', $routingKey, $message, [
|
||||
1 => Component::TYPE_TOPIC, // type -> topic
|
||||
3 => true, // durable -> true
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
|
||||
}
|
@ -1,415 +0,0 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?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 = []);
|
||||
|
||||
}
|
@ -16,45 +16,41 @@ class Key {
|
||||
$this->key = $this->buildKey($key);
|
||||
}
|
||||
|
||||
public function getRedis(): Connection {
|
||||
return Yii::$app->redis;
|
||||
}
|
||||
|
||||
public function getKey(): string {
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function getValue() {
|
||||
return $this->getRedis()->get($this->key);
|
||||
return Yii::$app->redis->get($this->key);
|
||||
}
|
||||
|
||||
public function setValue($value): self {
|
||||
$this->getRedis()->set($this->key, $value);
|
||||
Yii::$app->redis->set($this->key, $value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function delete(): self {
|
||||
$this->getRedis()->del([$this->getKey()]);
|
||||
Yii::$app->redis->del($this->getKey());
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function exists(): bool {
|
||||
return (bool)$this->getRedis()->exists($this->key);
|
||||
return (bool)Yii::$app->redis->exists($this->key);
|
||||
}
|
||||
|
||||
public function expire(int $ttl): self {
|
||||
$this->getRedis()->expire($this->key, $ttl);
|
||||
Yii::$app->redis->expire($this->key, $ttl);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function expireAt(int $unixTimestamp): self {
|
||||
$this->getRedis()->expireat($this->key, $unixTimestamp);
|
||||
Yii::$app->redis->expireat($this->key, $unixTimestamp);
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function buildKey(array $parts): string {
|
||||
$keyParts = [];
|
||||
foreach($parts as $part) {
|
||||
foreach ($parts as $part) {
|
||||
$keyParts[] = str_replace('_', ':', $part);
|
||||
}
|
||||
|
||||
|
@ -3,21 +3,22 @@ namespace common\components\Redis;
|
||||
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
use Yii;
|
||||
|
||||
class Set extends Key implements IteratorAggregate {
|
||||
|
||||
public function add($value): self {
|
||||
$this->getRedis()->sadd($this->getKey(), $value);
|
||||
Yii::$app->redis->sadd($this->getKey(), $value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function remove($value): self {
|
||||
$this->getRedis()->srem($this->getKey(), $value);
|
||||
Yii::$app->redis->srem($this->getKey(), $value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function members(): array {
|
||||
return $this->getRedis()->smembers($this->getKey());
|
||||
return Yii::$app->redis->smembers($this->getKey());
|
||||
}
|
||||
|
||||
public function getValue(): array {
|
||||
@ -29,11 +30,11 @@ class Set extends Key implements IteratorAggregate {
|
||||
return parent::exists();
|
||||
}
|
||||
|
||||
return (bool)$this->getRedis()->sismember($this->getKey(), $value);
|
||||
return (bool)Yii::$app->redis->sismember($this->getKey(), $value);
|
||||
}
|
||||
|
||||
public function diff(array $sets): array {
|
||||
return $this->getRedis()->sdiff([$this->getKey(), implode(' ', $sets)]);
|
||||
return Yii::$app->redis->sdiff([$this->getKey(), implode(' ', $sets)]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -6,7 +6,7 @@ use Yii;
|
||||
|
||||
class Api {
|
||||
|
||||
const BASE_DOMAIN = 'http://skinsystem.ely.by';
|
||||
private const BASE_DOMAIN = 'http://skinsystem.ely.by';
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
|
@ -1,10 +1,11 @@
|
||||
<?php
|
||||
namespace common\components;
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace common\components;
|
||||
|
||||
class UserFriendlyRandomKey {
|
||||
|
||||
public static function make ($length = 18) {
|
||||
public static function make(int $length = 18) {
|
||||
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
|
||||
$numChars = strlen($chars);
|
||||
$key = '';
|
||||
|
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
namespace common\components;
|
||||
|
||||
|
||||
/**
|
||||
* Этот класс был использован для изначальной генерации паролей на Ely.by и сейчас должен быть планомерно выпилен
|
||||
* с проекта с целью заменить этот алгоритм каким-нибудь посерьёзнее.
|
||||
|
@ -13,11 +13,11 @@ class ConfigLoader {
|
||||
$this->application = $application;
|
||||
}
|
||||
|
||||
public function getEnvironment() : string {
|
||||
public function getEnvironment(): string {
|
||||
return YII_ENV;
|
||||
}
|
||||
|
||||
public function getConfig() : array {
|
||||
public function getConfig(): array {
|
||||
$toMerge = [
|
||||
require __DIR__ . '/config.php',
|
||||
];
|
||||
@ -55,7 +55,7 @@ class ConfigLoader {
|
||||
return ArrayHelper::merge(...$toMerge);
|
||||
}
|
||||
|
||||
public static function load(string $application) : array {
|
||||
public static function load(string $application): array {
|
||||
return (new static($application))->getConfig();
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
<?php
|
||||
Yii::setAlias('common', dirname(__DIR__));
|
||||
Yii::setAlias('api', dirname(dirname(__DIR__)) . '/api');
|
||||
Yii::setAlias('console', dirname(dirname(__DIR__)) . '/console');
|
||||
Yii::setAlias('frontend', dirname(dirname(__DIR__)) . '/frontend');
|
||||
Yii::setAlias('api', dirname(__DIR__, 2) . '/api');
|
||||
Yii::setAlias('console', dirname(__DIR__, 2) . '/console');
|
||||
|
@ -4,8 +4,7 @@ return [
|
||||
'vendorPath' => dirname(__DIR__, 2) . '/vendor',
|
||||
'components' => [
|
||||
'cache' => [
|
||||
'class' => common\components\Redis\Cache::class,
|
||||
'redis' => 'redis',
|
||||
'class' => yii\redis\Cache::class,
|
||||
],
|
||||
'db' => [
|
||||
'class' => yii\db\Connection::class,
|
||||
@ -61,20 +60,12 @@ return [
|
||||
'passwordHashStrategy' => 'password_hash',
|
||||
],
|
||||
'redis' => [
|
||||
'class' => common\components\Redis\Connection::class,
|
||||
'class' => yii\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' => 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,
|
||||
],
|
||||
@ -97,15 +88,7 @@ return [
|
||||
'namespace' => getenv('STATSD_NAMESPACE') ?: 'ely.accounts.' . gethostname() . '.app',
|
||||
],
|
||||
'queue' => [
|
||||
'class' => yii\queue\amqp_interop\Queue::class,
|
||||
'driver' => yii\queue\amqp_interop\Queue::ENQUEUE_AMQP_LIB,
|
||||
'host' => getenv('RABBITMQ_HOST') ?: 'rabbitmq',
|
||||
'port' => getenv('RABBITMQ_PORT') ?: 5672,
|
||||
'user' => getenv('RABBITMQ_USER'),
|
||||
'password' => getenv('RABBITMQ_PASS'),
|
||||
'vhost' => getenv('RABBITMQ_VHOST'),
|
||||
'queueName' => 'worker',
|
||||
'exchangeName' => 'tasks',
|
||||
'class' => yii\queue\redis\Queue::class,
|
||||
],
|
||||
],
|
||||
'container' => [
|
||||
@ -115,6 +98,6 @@ return [
|
||||
],
|
||||
'aliases' => [
|
||||
'@bower' => '@vendor/bower-asset',
|
||||
'@npm' => '@vendor/npm-asset',
|
||||
'@npm' => '@vendor/npm-asset',
|
||||
],
|
||||
];
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace common\db\mysql;
|
||||
|
||||
use yii\db\Expression;
|
||||
use yii\db\ExpressionInterface;
|
||||
use yii\db\mysql\QueryBuilder as MysqlQueryBuilder;
|
||||
|
||||
class QueryBuilder extends MysqlQueryBuilder {
|
||||
@ -12,22 +12,21 @@ class QueryBuilder extends MysqlQueryBuilder {
|
||||
}
|
||||
|
||||
$orders = [];
|
||||
foreach($columns as $name => $direction) {
|
||||
if ($direction instanceof Expression) {
|
||||
foreach ($columns as $name => $direction) {
|
||||
if ($direction instanceof ExpressionInterface) {
|
||||
$orders[] = $direction->expression;
|
||||
} elseif (is_array($direction)) {
|
||||
// This is new feature
|
||||
// This condition branch is our custom solution
|
||||
if (empty($direction)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldValues = [];
|
||||
foreach($direction as $fieldValue) {
|
||||
foreach ($direction as $fieldValue) {
|
||||
$fieldValues[] = $this->db->quoteValue($fieldValue);
|
||||
}
|
||||
|
||||
$orders[] = 'FIELD(' . $this->db->quoteColumnName($name) . ',' . implode(',', $fieldValues) . ')';
|
||||
// End of new feature
|
||||
} else {
|
||||
$orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : '');
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ abstract class TemplateWithRenderer extends Template {
|
||||
*/
|
||||
abstract public function getTemplateName(): string;
|
||||
|
||||
protected final function getView() {
|
||||
final protected function getView() {
|
||||
return $this->getTemplateName();
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,12 @@ class ChangeEmailConfirmCurrentEmail extends Template {
|
||||
return 'Ely.by Account change E-mail confirmation';
|
||||
}
|
||||
|
||||
public function getParams(): array {
|
||||
return [
|
||||
'key' => $this->key,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|array
|
||||
*/
|
||||
@ -26,10 +32,4 @@ class ChangeEmailConfirmCurrentEmail extends Template {
|
||||
];
|
||||
}
|
||||
|
||||
public function getParams(): array {
|
||||
return [
|
||||
'key' => $this->key,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,13 @@ class ChangeEmailConfirmNewEmail extends Template {
|
||||
return 'Ely.by Account new E-mail confirmation';
|
||||
}
|
||||
|
||||
public function getParams(): array {
|
||||
return [
|
||||
'key' => $this->key,
|
||||
'username' => $this->username,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|array
|
||||
*/
|
||||
@ -29,11 +36,4 @@ class ChangeEmailConfirmNewEmail extends Template {
|
||||
];
|
||||
}
|
||||
|
||||
public function getParams(): array {
|
||||
return [
|
||||
'key' => $this->key,
|
||||
'username' => $this->username,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,70 +3,70 @@ namespace common\helpers;
|
||||
|
||||
final class Error {
|
||||
|
||||
const USERNAME_REQUIRED = 'error.username_required';
|
||||
const USERNAME_TOO_SHORT = 'error.username_too_short';
|
||||
const USERNAME_TOO_LONG = 'error.username_too_long';
|
||||
const USERNAME_INVALID = 'error.username_invalid';
|
||||
const USERNAME_NOT_AVAILABLE = 'error.username_not_available';
|
||||
public const USERNAME_REQUIRED = 'error.username_required';
|
||||
public const USERNAME_TOO_SHORT = 'error.username_too_short';
|
||||
public const USERNAME_TOO_LONG = 'error.username_too_long';
|
||||
public const USERNAME_INVALID = 'error.username_invalid';
|
||||
public const USERNAME_NOT_AVAILABLE = 'error.username_not_available';
|
||||
|
||||
const EMAIL_REQUIRED = 'error.email_required';
|
||||
const EMAIL_TOO_LONG = 'error.email_too_long';
|
||||
const EMAIL_INVALID = 'error.email_invalid';
|
||||
const EMAIL_IS_TEMPMAIL = 'error.email_is_tempmail';
|
||||
const EMAIL_NOT_AVAILABLE = 'error.email_not_available';
|
||||
const EMAIL_NOT_FOUND = 'error.email_not_found';
|
||||
public const EMAIL_REQUIRED = 'error.email_required';
|
||||
public const EMAIL_TOO_LONG = 'error.email_too_long';
|
||||
public const EMAIL_INVALID = 'error.email_invalid';
|
||||
public const EMAIL_IS_TEMPMAIL = 'error.email_is_tempmail';
|
||||
public const EMAIL_NOT_AVAILABLE = 'error.email_not_available';
|
||||
public const EMAIL_NOT_FOUND = 'error.email_not_found';
|
||||
|
||||
const LOGIN_REQUIRED = 'error.login_required';
|
||||
const LOGIN_NOT_EXIST = 'error.login_not_exist';
|
||||
public const LOGIN_REQUIRED = 'error.login_required';
|
||||
public const LOGIN_NOT_EXIST = 'error.login_not_exist';
|
||||
|
||||
const PASSWORD_REQUIRED = 'error.password_required';
|
||||
const PASSWORD_INCORRECT = 'error.password_incorrect';
|
||||
const PASSWORD_TOO_SHORT = 'error.password_too_short';
|
||||
public const PASSWORD_REQUIRED = 'error.password_required';
|
||||
public const PASSWORD_INCORRECT = 'error.password_incorrect';
|
||||
public const PASSWORD_TOO_SHORT = 'error.password_too_short';
|
||||
|
||||
const KEY_REQUIRED = 'error.key_required';
|
||||
const KEY_NOT_EXISTS = 'error.key_not_exists';
|
||||
const KEY_EXPIRE = 'error.key_expire';
|
||||
public const KEY_REQUIRED = 'error.key_required';
|
||||
public const KEY_NOT_EXISTS = 'error.key_not_exists';
|
||||
public const KEY_EXPIRE = 'error.key_expire';
|
||||
|
||||
const ACCOUNT_BANNED = 'error.account_banned';
|
||||
const ACCOUNT_NOT_ACTIVATED = 'error.account_not_activated';
|
||||
const ACCOUNT_ALREADY_ACTIVATED = 'error.account_already_activated';
|
||||
const ACCOUNT_CANNOT_RESEND_MESSAGE = 'error.account_cannot_resend_message';
|
||||
public const ACCOUNT_BANNED = 'error.account_banned';
|
||||
public const ACCOUNT_NOT_ACTIVATED = 'error.account_not_activated';
|
||||
public const ACCOUNT_ALREADY_ACTIVATED = 'error.account_already_activated';
|
||||
public const ACCOUNT_CANNOT_RESEND_MESSAGE = 'error.account_cannot_resend_message';
|
||||
|
||||
const RECENTLY_SENT_MESSAGE = 'error.recently_sent_message';
|
||||
public const RECENTLY_SENT_MESSAGE = 'error.recently_sent_message';
|
||||
|
||||
const NEW_PASSWORD_REQUIRED = 'error.newPassword_required';
|
||||
const NEW_RE_PASSWORD_REQUIRED = 'error.newRePassword_required';
|
||||
const NEW_RE_PASSWORD_DOES_NOT_MATCH = self::RE_PASSWORD_DOES_NOT_MATCH;
|
||||
public const NEW_PASSWORD_REQUIRED = 'error.newPassword_required';
|
||||
public const NEW_RE_PASSWORD_REQUIRED = 'error.newRePassword_required';
|
||||
public const NEW_RE_PASSWORD_DOES_NOT_MATCH = self::RE_PASSWORD_DOES_NOT_MATCH;
|
||||
|
||||
const REFRESH_TOKEN_REQUIRED = 'error.refresh_token_required';
|
||||
const REFRESH_TOKEN_NOT_EXISTS = 'error.refresh_token_not_exist';
|
||||
public const REFRESH_TOKEN_REQUIRED = 'error.refresh_token_required';
|
||||
public const REFRESH_TOKEN_NOT_EXISTS = 'error.refresh_token_not_exist';
|
||||
|
||||
const CAPTCHA_REQUIRED = 'error.captcha_required';
|
||||
const CAPTCHA_INVALID = 'error.captcha_invalid';
|
||||
public const CAPTCHA_REQUIRED = 'error.captcha_required';
|
||||
public const CAPTCHA_INVALID = 'error.captcha_invalid';
|
||||
|
||||
const RULES_AGREEMENT_REQUIRED = 'error.rulesAgreement_required';
|
||||
public const RULES_AGREEMENT_REQUIRED = 'error.rulesAgreement_required';
|
||||
|
||||
const RE_PASSWORD_REQUIRED = 'error.rePassword_required';
|
||||
const RE_PASSWORD_DOES_NOT_MATCH = 'error.rePassword_does_not_match';
|
||||
public const RE_PASSWORD_REQUIRED = 'error.rePassword_required';
|
||||
public const RE_PASSWORD_DOES_NOT_MATCH = 'error.rePassword_does_not_match';
|
||||
|
||||
const UNSUPPORTED_LANGUAGE = 'error.unsupported_language';
|
||||
public const UNSUPPORTED_LANGUAGE = 'error.unsupported_language';
|
||||
|
||||
const SUBJECT_REQUIRED = 'error.subject_required';
|
||||
const MESSAGE_REQUIRED = 'error.message_required';
|
||||
public const SUBJECT_REQUIRED = 'error.subject_required';
|
||||
public const MESSAGE_REQUIRED = 'error.message_required';
|
||||
|
||||
const TOTP_REQUIRED = 'error.totp_required';
|
||||
const TOTP_INCORRECT = 'error.totp_incorrect';
|
||||
public const TOTP_REQUIRED = 'error.totp_required';
|
||||
public const TOTP_INCORRECT = 'error.totp_incorrect';
|
||||
|
||||
const OTP_ALREADY_ENABLED = 'error.otp_already_enabled';
|
||||
const OTP_NOT_ENABLED = 'error.otp_not_enabled';
|
||||
public const OTP_ALREADY_ENABLED = 'error.otp_already_enabled';
|
||||
public const OTP_NOT_ENABLED = 'error.otp_not_enabled';
|
||||
|
||||
const NAME_REQUIRED = 'error.name_required';
|
||||
public const NAME_REQUIRED = 'error.name_required';
|
||||
|
||||
const REDIRECT_URI_REQUIRED = 'error.redirectUri_required';
|
||||
const REDIRECT_URI_INVALID = 'error.redirectUri_invalid';
|
||||
public const REDIRECT_URI_REQUIRED = 'error.redirectUri_required';
|
||||
public const REDIRECT_URI_INVALID = 'error.redirectUri_invalid';
|
||||
|
||||
const WEBSITE_URL_INVALID = 'error.websiteUrl_invalid';
|
||||
public const WEBSITE_URL_INVALID = 'error.websiteUrl_invalid';
|
||||
|
||||
const MINECRAFT_SERVER_IP_INVALID = 'error.minecraftServerIp_invalid';
|
||||
public const MINECRAFT_SERVER_IP_INVALID = 'error.minecraftServerIp_invalid';
|
||||
|
||||
}
|
||||
|
@ -13,9 +13,9 @@ class StringHelper {
|
||||
|
||||
if ($usernameLength === 1) {
|
||||
$mask = $maskChars;
|
||||
} elseif($usernameLength === 2) {
|
||||
} elseif ($usernameLength === 2) {
|
||||
$mask = mb_substr($username, 0, 1) . $maskChars;
|
||||
} elseif($usernameLength === 3) {
|
||||
} elseif ($usernameLength === 3) {
|
||||
$mask = mb_substr($username, 0, 1) . $maskChars . mb_substr($username, 2, 1);
|
||||
} else {
|
||||
$mask = mb_substr($username, 0, 2) . $maskChars . mb_substr($username, -2, 2);
|
||||
|
@ -1,7 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace common\models;
|
||||
|
||||
use common\components\UserPass;
|
||||
use common\tasks\CreateWebHooksDeliveries;
|
||||
use Yii;
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\behaviors\TimestampBehavior;
|
||||
@ -44,13 +47,13 @@ use const common\LATEST_RULES_VERSION;
|
||||
*/
|
||||
class Account extends ActiveRecord {
|
||||
|
||||
const STATUS_DELETED = -10;
|
||||
const STATUS_BANNED = -1;
|
||||
const STATUS_REGISTERED = 0;
|
||||
const STATUS_ACTIVE = 10;
|
||||
public const STATUS_DELETED = -10;
|
||||
public const STATUS_BANNED = -1;
|
||||
public const STATUS_REGISTERED = 0;
|
||||
public const STATUS_ACTIVE = 10;
|
||||
|
||||
const PASS_HASH_STRATEGY_OLD_ELY = 0;
|
||||
const PASS_HASH_STRATEGY_YII2 = 1;
|
||||
public const PASS_HASH_STRATEGY_OLD_ELY = 0;
|
||||
public const PASS_HASH_STRATEGY_YII2 = 1;
|
||||
|
||||
public static function tableName(): string {
|
||||
return '{{%accounts}}';
|
||||
@ -67,10 +70,9 @@ class Account extends ActiveRecord {
|
||||
$passwordHashStrategy = $this->password_hash_strategy;
|
||||
}
|
||||
|
||||
switch($passwordHashStrategy) {
|
||||
switch ($passwordHashStrategy) {
|
||||
case self::PASS_HASH_STRATEGY_OLD_ELY:
|
||||
$hashedPass = UserPass::make($this->email, $password);
|
||||
return $hashedPass === $this->password_hash;
|
||||
return UserPass::make($this->email, $password) === $this->password_hash;
|
||||
|
||||
case self::PASS_HASH_STRATEGY_YII2:
|
||||
return Yii::$app->security->validatePassword($password, $this->password_hash);
|
||||
@ -153,4 +155,22 @@ class Account extends ActiveRecord {
|
||||
return $this->registration_ip === null ? null : inet_ntop($this->registration_ip);
|
||||
}
|
||||
|
||||
public function afterSave($insert, $changedAttributes) {
|
||||
parent::afterSave($insert, $changedAttributes);
|
||||
|
||||
if ($insert) {
|
||||
return;
|
||||
}
|
||||
|
||||
$meaningfulFields = ['username', 'email', 'uuid', 'status', 'lang'];
|
||||
$meaningfulChangedAttributes = array_filter($changedAttributes, function(string $key) use ($meaningfulFields) {
|
||||
return in_array($key, $meaningfulFields, true);
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
if (empty($meaningfulChangedAttributes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Yii::$app->queue->push(CreateWebHooksDeliveries::createAccountEdit($this, $meaningfulChangedAttributes));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,10 +28,10 @@ use yii\helpers\ArrayHelper;
|
||||
*/
|
||||
class EmailActivation extends ActiveRecord {
|
||||
|
||||
const TYPE_REGISTRATION_EMAIL_CONFIRMATION = 0;
|
||||
const TYPE_FORGOT_PASSWORD_KEY = 1;
|
||||
const TYPE_CURRENT_EMAIL_CONFIRMATION = 2;
|
||||
const TYPE_NEW_EMAIL_CONFIRMATION = 3;
|
||||
public const TYPE_REGISTRATION_EMAIL_CONFIRMATION = 0;
|
||||
public const TYPE_FORGOT_PASSWORD_KEY = 1;
|
||||
public const TYPE_CURRENT_EMAIL_CONFIRMATION = 2;
|
||||
public const TYPE_NEW_EMAIL_CONFIRMATION = 3;
|
||||
|
||||
public static function tableName() {
|
||||
return '{{%email_activations}}';
|
||||
@ -79,15 +79,15 @@ class EmailActivation extends ActiveRecord {
|
||||
throw new InvalidConfigException('Unexpected type');
|
||||
}
|
||||
|
||||
return new $classMap[$type];
|
||||
return new $classMap[$type]();
|
||||
}
|
||||
|
||||
public static function getClassMap() {
|
||||
return [
|
||||
self::TYPE_REGISTRATION_EMAIL_CONFIRMATION => confirmations\RegistrationConfirmation::class,
|
||||
self::TYPE_FORGOT_PASSWORD_KEY => confirmations\ForgotPassword::class,
|
||||
self::TYPE_CURRENT_EMAIL_CONFIRMATION => confirmations\CurrentEmailConfirmation::class,
|
||||
self::TYPE_NEW_EMAIL_CONFIRMATION => confirmations\NewEmailConfirmation::class,
|
||||
self::TYPE_FORGOT_PASSWORD_KEY => confirmations\ForgotPassword::class,
|
||||
self::TYPE_CURRENT_EMAIL_CONFIRMATION => confirmations\CurrentEmailConfirmation::class,
|
||||
self::TYPE_NEW_EMAIL_CONFIRMATION => confirmations\NewEmailConfirmation::class,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ use yii\db\ActiveRecord;
|
||||
*/
|
||||
class MinecraftAccessKey extends ActiveRecord {
|
||||
|
||||
const LIFETIME = 172800; // Ключ актуален в течение 2 дней
|
||||
public const LIFETIME = 172800; // Ключ актуален в течение 2 дней
|
||||
|
||||
public static function tableName(): string {
|
||||
return '{{%minecraft_access_keys}}';
|
||||
|
@ -11,6 +11,10 @@ class OauthScopeQuery {
|
||||
|
||||
private $owner;
|
||||
|
||||
public function __construct(array $scopes) {
|
||||
$this->scopes = $scopes;
|
||||
}
|
||||
|
||||
public function onlyPublic(): self {
|
||||
$this->internal = false;
|
||||
return $this;
|
||||
@ -43,8 +47,4 @@ class OauthScopeQuery {
|
||||
}), 'value');
|
||||
}
|
||||
|
||||
public function __construct(array $scopes) {
|
||||
$this->scopes = $scopes;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ class Textures {
|
||||
}
|
||||
|
||||
public function getMinecraftResponse() {
|
||||
$response = [
|
||||
$response = [
|
||||
'name' => $this->account->username,
|
||||
'id' => str_replace('-', '', $this->account->uuid),
|
||||
'properties' => [
|
||||
@ -54,9 +54,9 @@ class Textures {
|
||||
|
||||
if (!$encrypted) {
|
||||
return $array;
|
||||
} else {
|
||||
return static::encrypt($array);
|
||||
}
|
||||
|
||||
return static::encrypt($array);
|
||||
}
|
||||
|
||||
public function getTextures(): array {
|
||||
|
@ -45,7 +45,7 @@ class UsernameHistory extends ActiveRecord {
|
||||
* @param int $afterTime
|
||||
* @return UsernameHistory|null
|
||||
*/
|
||||
public function findNext(int $afterTime = null) /*: ?UsernameHistory*/ {
|
||||
public function findNext(int $afterTime = null): ?UsernameHistory {
|
||||
return self::find()
|
||||
->andWhere(['account_id' => $this->account_id])
|
||||
->andWhere(['>', 'applied_in', $afterTime ?: $this->applied_in])
|
||||
|
42
common/models/WebHook.php
Normal file
42
common/models/WebHook.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace common\models;
|
||||
|
||||
use yii\behaviors\TimestampBehavior;
|
||||
use yii\db\ActiveQueryInterface;
|
||||
use yii\db\ActiveRecord;
|
||||
|
||||
/**
|
||||
* Fields:
|
||||
* @property int $id
|
||||
* @property string $url
|
||||
* @property string|null $secret
|
||||
* @property int $created_at
|
||||
*
|
||||
* Relations:
|
||||
* @property WebHookEvent[] $events
|
||||
*
|
||||
* Behaviors:
|
||||
* @mixin TimestampBehavior
|
||||
*/
|
||||
class WebHook extends ActiveRecord {
|
||||
|
||||
public static function tableName(): string {
|
||||
return '{{%webhooks}}';
|
||||
}
|
||||
|
||||
public function behaviors(): array {
|
||||
return [
|
||||
[
|
||||
'class' => TimestampBehavior::class,
|
||||
'updatedAtAttribute' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getEvents(): ActiveQueryInterface {
|
||||
return $this->hasMany(WebHookEvent::class, ['webhook_id' => 'id']);
|
||||
}
|
||||
|
||||
}
|
27
common/models/WebHookEvent.php
Normal file
27
common/models/WebHookEvent.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace common\models;
|
||||
|
||||
use yii\db\ActiveQueryInterface;
|
||||
use yii\db\ActiveRecord;
|
||||
|
||||
/**
|
||||
* Fields:
|
||||
* @property int $webhook_id
|
||||
* @property string $event_type
|
||||
*
|
||||
* Relations:
|
||||
* @property WebHook $webhook
|
||||
*/
|
||||
class WebHookEvent extends ActiveRecord {
|
||||
|
||||
public static function tableName(): string {
|
||||
return '{{%webhooks_events}}';
|
||||
}
|
||||
|
||||
public function getWebhook(): ActiveQueryInterface {
|
||||
return $this->hasOne(WebHook::class, ['id' => 'webhook_id']);
|
||||
}
|
||||
|
||||
}
|
@ -13,7 +13,7 @@ class NewEmailConfirmation extends EmailActivation {
|
||||
public function behaviors() {
|
||||
return ArrayHelper::merge(parent::behaviors(), [
|
||||
'expirationBehavior' => [
|
||||
'repeatTimeout' => 5 * 60,
|
||||
'repeatTimeout' => 5 * 60,
|
||||
],
|
||||
'dataBehavior' => [
|
||||
'class' => NewEmailConfirmationBehavior::class,
|
||||
|
@ -8,11 +8,11 @@ use common\behaviors\DataBehavior;
|
||||
*/
|
||||
class NewEmailConfirmationBehavior extends DataBehavior {
|
||||
|
||||
public function getNewEmail() : string {
|
||||
public function getNewEmail(): string {
|
||||
return $this->getKey('newEmail');
|
||||
}
|
||||
|
||||
public function setNewEmail(string $newEmail) {
|
||||
public function setNewEmail(string $newEmail): void {
|
||||
$this->setKey('newEmail', $newEmail);
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ class OauthClientOwner extends Rule {
|
||||
/** @var OauthClient|null $client */
|
||||
$client = OauthClient::findOne($clientId);
|
||||
if ($client === null) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
$identity = Yii::$app->user->findIdentityByAccessToken($accessToken);
|
||||
|
64
common/tasks/ClearAccountSessions.php
Normal file
64
common/tasks/ClearAccountSessions.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace common\tasks;
|
||||
|
||||
use common\models\Account;
|
||||
use Yii;
|
||||
use yii\queue\RetryableJobInterface;
|
||||
|
||||
class ClearAccountSessions implements RetryableJobInterface {
|
||||
|
||||
public $accountId;
|
||||
|
||||
public static function createFromAccount(Account $account): self {
|
||||
$result = new static();
|
||||
$result->accountId = $account->id;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int time to reserve in seconds
|
||||
*/
|
||||
public function getTtr(): int {
|
||||
return 5 * 60;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $attempt number
|
||||
* @param \Exception|\Throwable $error from last execute of the job
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canRetry($attempt, $error): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \yii\queue\Queue $queue which pushed and is handling the job
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function execute($queue): void {
|
||||
$account = Account::findOne($this->accountId);
|
||||
if ($account === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($account->getSessions()->each(100, Yii::$app->unbufferedDb) as $authSession) {
|
||||
/** @var \common\models\AccountSession $authSession */
|
||||
$authSession->delete();
|
||||
}
|
||||
|
||||
foreach ($account->getMinecraftAccessKeys()->each(100, Yii::$app->unbufferedDb) as $key) {
|
||||
/** @var \common\models\MinecraftAccessKey $key */
|
||||
$key->delete();
|
||||
}
|
||||
|
||||
foreach ($account->getOauthSessions()->each(100, Yii::$app->unbufferedDb) as $oauthSession) {
|
||||
/** @var \common\models\OauthSession $oauthSession */
|
||||
$oauthSession->delete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
76
common/tasks/CreateWebHooksDeliveries.php
Normal file
76
common/tasks/CreateWebHooksDeliveries.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace common\tasks;
|
||||
|
||||
use common\models\Account;
|
||||
use common\models\WebHook;
|
||||
use Yii;
|
||||
use yii\queue\RetryableJobInterface;
|
||||
|
||||
class CreateWebHooksDeliveries implements RetryableJobInterface {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $payloads;
|
||||
|
||||
public static function createAccountEdit(Account $account, array $changedAttributes): self {
|
||||
$result = new static();
|
||||
$result->type = 'account.edit';
|
||||
$result->payloads = [
|
||||
'id' => $account->id,
|
||||
'uuid' => $account->uuid,
|
||||
'username' => $account->username,
|
||||
'email' => $account->email,
|
||||
'lang' => $account->lang,
|
||||
'isActive' => $account->status === Account::STATUS_ACTIVE,
|
||||
'registered' => date('c', (int)$account->created_at),
|
||||
'changedAttributes' => $changedAttributes,
|
||||
];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int time to reserve in seconds
|
||||
*/
|
||||
public function getTtr() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $attempt number
|
||||
* @param \Exception|\Throwable $error from last execute of the job
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canRetry($attempt, $error) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \yii\queue\Queue $queue which pushed and is handling the job
|
||||
*/
|
||||
public function execute($queue) {
|
||||
/** @var WebHook[] $targets */
|
||||
$targets = WebHook::find()
|
||||
->joinWith('events e', false)
|
||||
->andWhere(['e.event_type' => $this->type])
|
||||
->all();
|
||||
foreach ($targets as $target) {
|
||||
$job = new DeliveryWebHook();
|
||||
$job->type = $this->type;
|
||||
$job->url = $target->url;
|
||||
$job->secret = $target->secret;
|
||||
$job->payloads = $this->payloads;
|
||||
Yii::$app->queue->push($job);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
111
common/tasks/DeliveryWebHook.php
Normal file
111
common/tasks/DeliveryWebHook.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace common\tasks;
|
||||
|
||||
use GuzzleHttp\Client as GuzzleClient;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use GuzzleHttp\Exception\ServerException;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Middleware;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Yii;
|
||||
use yii\queue\RetryableJobInterface;
|
||||
|
||||
class DeliveryWebHook implements RetryableJobInterface {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $url;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
public $secret;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $payloads;
|
||||
|
||||
/**
|
||||
* @return int time to reserve in seconds
|
||||
*/
|
||||
public function getTtr(): int {
|
||||
return 65;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $attempt number
|
||||
* @param \Exception|\Throwable $error from last execute of the job
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canRetry($attempt, $error): bool {
|
||||
if ($attempt >= 5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($error instanceof ServerException || $error instanceof ConnectException) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \yii\queue\Queue $queue which pushed and is handling the job
|
||||
*
|
||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||
*/
|
||||
public function execute($queue): void {
|
||||
$client = $this->createClient();
|
||||
try {
|
||||
$client->request('POST', $this->url, [
|
||||
'headers' => [
|
||||
'User-Agent' => 'Account-Ely-Hookshot/' . Yii::$app->version,
|
||||
'X-Ely-Accounts-Event' => $this->type,
|
||||
],
|
||||
'form_params' => $this->payloads,
|
||||
]);
|
||||
} catch (ClientException $e) {
|
||||
Yii::info("Delivery for {$this->url} has failed with {$e->getResponse()->getStatusCode()} status.");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected function createClient(): ClientInterface {
|
||||
return new GuzzleClient([
|
||||
'handler' => $this->createStack(),
|
||||
'timeout' => 60,
|
||||
'connect_timeout' => 10,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function createStack(): HandlerStack {
|
||||
$stack = HandlerStack::create();
|
||||
$stack->push(Middleware::mapRequest(function(RequestInterface $request): RequestInterface {
|
||||
if (empty($this->secret)) {
|
||||
return $request;
|
||||
}
|
||||
|
||||
$payload = (string)$request->getBody();
|
||||
$signature = hash_hmac('sha1', $payload, $this->secret);
|
||||
|
||||
/** @noinspection ExceptionsAnnotatingAndHandlingInspection */
|
||||
return $request->withHeader('X-Hub-Signature', 'sha1=' . $signature);
|
||||
}));
|
||||
|
||||
return $stack;
|
||||
}
|
||||
|
||||
}
|
72
common/tasks/PullMojangUsername.php
Normal file
72
common/tasks/PullMojangUsername.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace common\tasks;
|
||||
|
||||
use api\exceptions\ThisShouldNotHappenException;
|
||||
use common\components\Mojang\Api as MojangApi;
|
||||
use common\components\Mojang\exceptions\MojangApiException;
|
||||
use common\components\Mojang\exceptions\NoContentException;
|
||||
use common\models\Account;
|
||||
use common\models\MojangUsername;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Yii;
|
||||
use yii\queue\JobInterface;
|
||||
|
||||
class PullMojangUsername implements JobInterface {
|
||||
|
||||
public $username;
|
||||
|
||||
public static function createFromAccount(Account $account): self {
|
||||
$result = new static();
|
||||
$result->username = $account->username;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \yii\queue\Queue $queue which pushed and is handling the job
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function execute($queue) {
|
||||
Yii::$app->statsd->inc('queue.pullMojangUsername.attempt');
|
||||
$mojangApi = $this->createMojangApi();
|
||||
try {
|
||||
$response = $mojangApi->usernameToUUID($this->username);
|
||||
Yii::$app->statsd->inc('queue.pullMojangUsername.found');
|
||||
} catch (NoContentException $e) {
|
||||
$response = false;
|
||||
Yii::$app->statsd->inc('queue.pullMojangUsername.not_found');
|
||||
} catch (RequestException | MojangApiException $e) {
|
||||
Yii::$app->statsd->inc('queue.pullMojangUsername.error');
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var MojangUsername|null $mojangUsername */
|
||||
$mojangUsername = MojangUsername::findOne($this->username);
|
||||
if ($response === false) {
|
||||
if ($mojangUsername !== null) {
|
||||
$mojangUsername->delete();
|
||||
}
|
||||
} else {
|
||||
if ($mojangUsername === null) {
|
||||
$mojangUsername = new MojangUsername();
|
||||
$mojangUsername->username = $response->name;
|
||||
$mojangUsername->uuid = $response->id;
|
||||
} else {
|
||||
$mojangUsername->uuid = $response->id;
|
||||
$mojangUsername->touch('last_pulled_at');
|
||||
}
|
||||
|
||||
if (!$mojangUsername->save()) {
|
||||
throw new ThisShouldNotHappenException('Cannot save mojang username');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function createMojangApi(): MojangApi {
|
||||
return new MojangApi();
|
||||
}
|
||||
|
||||
}
|
@ -38,6 +38,11 @@ class EmailValidator extends Validator {
|
||||
$tempmail = new TempmailValidator();
|
||||
$tempmail->message = E::EMAIL_IS_TEMPMAIL;
|
||||
|
||||
$idnaDomain = new validators\FilterValidator(['filter' => function(string $value): string {
|
||||
[$name, $domain] = explode('@', $value);
|
||||
return $name . '@' . idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46);
|
||||
}]);
|
||||
|
||||
$unique = new validators\UniqueValidator();
|
||||
$unique->message = E::EMAIL_NOT_AVAILABLE;
|
||||
$unique->targetClass = Account::class;
|
||||
@ -53,12 +58,12 @@ class EmailValidator extends Validator {
|
||||
$this->executeValidation($length, $model, $attribute) &&
|
||||
$this->executeValidation($email, $model, $attribute) &&
|
||||
$this->executeValidation($tempmail, $model, $attribute) &&
|
||||
$this->executeValidation($idnaDomain, $model, $attribute) &&
|
||||
$this->executeValidation($unique, $model, $attribute);
|
||||
}
|
||||
|
||||
protected function executeValidation(Validator $validator, Model $model, string $attribute) {
|
||||
private function executeValidation(Validator $validator, Model $model, string $attribute): bool {
|
||||
$validator->validateAttribute($model, $attribute);
|
||||
|
||||
return !$model->hasErrors($attribute);
|
||||
}
|
||||
|
||||
|
@ -1,39 +1,37 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace common\validators;
|
||||
|
||||
use common\helpers\Error as E;
|
||||
use Yii;
|
||||
use Locale;
|
||||
use ResourceBundle;
|
||||
use yii\validators\Validator;
|
||||
|
||||
class LanguageValidator extends Validator {
|
||||
|
||||
public $message = E::UNSUPPORTED_LANGUAGE;
|
||||
|
||||
protected function validateValue($value) {
|
||||
/**
|
||||
* The idea of this validator belongs to
|
||||
* https://github.com/lunetics/LocaleBundle/blob/1f5ee7f1802/Validator/LocaleValidator.php#L82-L88
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return array|null
|
||||
*/
|
||||
protected function validateValue($value): ?array {
|
||||
if (empty($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$files = $this->getFilesNames();
|
||||
if (in_array($value, $files)) {
|
||||
return null;
|
||||
$primary = Locale::getPrimaryLanguage($value);
|
||||
$region = Locale::getRegion($value);
|
||||
$locales = ResourceBundle::getLocales(''); // http://php.net/manual/ru/resourcebundle.locales.php#115965
|
||||
if (($region !== '' && strtolower($primary) !== strtolower($region)) && !in_array($value, $locales)) {
|
||||
return [$this->message, []];
|
||||
}
|
||||
|
||||
return [$this->message, []];
|
||||
}
|
||||
|
||||
protected function getFilesNames() {
|
||||
$files = array_values(array_filter(scandir($this->getFolderPath()), function(&$value) {
|
||||
return $value !== '..' && $value !== '.';
|
||||
}));
|
||||
|
||||
return array_map(function($value) {
|
||||
return basename($value, '.json');
|
||||
}, $files);
|
||||
}
|
||||
|
||||
protected function getFolderPath() {
|
||||
return Yii::getAlias('@frontend/src/i18n');
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user