diff --git a/.gitignore b/.gitignore index 441d98f..7749dee 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ # Composer /vendor -/composer.lock # Mac DS_Store Files .DS_Store diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5d8220e..a5d6184 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,7 +7,7 @@ variables: CONTAINER_IMAGE: "registry.ely.by/elyby/accounts" test:backend: - image: docker:latest + image: docker:18.02 services: - mariadb:10.2.11 - redis:3.0-alpine @@ -59,12 +59,24 @@ test:frontend: - yarn test build:production: - image: docker:latest + image: docker:18.02 stage: build + variables: + GA_ID: "UA-45299905-3" + SENTRY_CDN: "https://088e7718236a4f91937a81fb319a93f6@sentry.ely.by/2" before_script: - docker login -u gitlab-ci -p $CI_BUILD_TOKEN registry.ely.by + - export VERSION="${CI_COMMIT_TAG:-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA:0:7}}" - echo "$SSH_PRIVATE_KEY" > id_rsa - - echo "$FRONTEND_ENV_CONFIG" > frontend/config/env.js + - | + echo " + module.exports = { + version: '$VERSION', + ga: {id: '$GA_ID'}, + sentryCdn: '$SENTRY_CDN', + }; + " > frontend/config/env.js + - 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 . @@ -73,7 +85,7 @@ build:production: - tags release:latest: - image: docker:latest + image: docker:18.02 stage: release variables: GIT_STRATEGY: none @@ -86,7 +98,7 @@ release:latest: - tags release:tag: - image: docker:latest + image: docker:18.02 stage: release variables: GIT_STRATEGY: none diff --git a/Dockerfile b/Dockerfile index 69ea7c1..a6f7285 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.ely.by/elyby/accounts-php:1.6.0 +FROM registry.ely.by/elyby/accounts-php:1.7.0 # bootstrap скрипт для проекта COPY docker/php/bootstrap.sh /bootstrap.sh @@ -15,9 +15,10 @@ RUN chmod 400 ~/.ssh/id_rsa \ && touch /root/.ssh/known_hosts \ && ssh-keyscan github.com gitlab.ely.by >> /root/.ssh/known_hosts -# Копируем composer.json в родительскую директорию, которая не будет синкаться с хостом через -# volume на dev окружении. В entrypoint эта папка будет скопирована обратно. +# Копируем списки зависимостей composer в родительскую директорию, которая не будет синкаться +# с хостом через volume на dev окружении. В entrypoint эта папка будет скопирована обратно COPY ./composer.json /var/www/composer.json +COPY ./composer.lock /var/www/composer.lock # Устанавливаем зависимости PHP RUN cd .. \ diff --git a/Dockerfile-dev b/Dockerfile-dev index c53dab6..07ec96a 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -1,4 +1,4 @@ -FROM registry.ely.by/elyby/accounts-php:1.6.0-dev +FROM registry.ely.by/elyby/accounts-php:1.7.0-dev # bootstrap скрипт для проекта COPY docker/php/bootstrap.sh /bootstrap.sh @@ -15,9 +15,10 @@ RUN chmod 400 ~/.ssh/id_rsa \ && touch /root/.ssh/known_hosts \ && ssh-keyscan github.com gitlab.ely.by >> /root/.ssh/known_hosts -# Копируем composer.json в родительскую директорию, которая не будет синкаться с хостом через -# volume на dev окружении. В entrypoint эта папка будет скопирована обратно. +# Копируем списки зависимостей composer в родительскую директорию, которая не будет синкаться +# с хостом через volume на dev окружении. В entrypoint эта папка будет скопирована обратно COPY ./composer.json /var/www/composer.json +COPY ./composer.lock /var/www/composer.lock # Устанавливаем зависимости PHP RUN cd .. \ diff --git a/api/components/OAuth2/Storage/ClientStorage.php b/api/components/OAuth2/Storage/ClientStorage.php index 9a339f0..d5979e2 100644 --- a/api/components/OAuth2/Storage/ClientStorage.php +++ b/api/components/OAuth2/Storage/ClientStorage.php @@ -18,14 +18,12 @@ class ClientStorage extends AbstractStorage implements ClientInterface { * @inheritdoc */ public function get($clientId, $clientSecret = null, $redirectUri = null, $grantType = null) { - $query = OauthClient::find()->andWhere(['id' => $clientId]); - if ($clientSecret !== null) { - $query->andWhere(['secret' => $clientSecret]); + $model = $this->findClient($clientId); + if ($model === null) { + return null; } - /** @var OauthClient|null $model */ - $model = $query->one(); - if ($model === null) { + if ($clientSecret !== null && $clientSecret !== $model->secret) { return null; } @@ -60,8 +58,7 @@ class ClientStorage extends AbstractStorage implements ClientInterface { throw new \ErrorException('This module assumes that $session typeof ' . SessionEntity::class); } - /** @var OauthClient|null $model */ - $model = OauthClient::findOne($session->getClientId()); + $model = $this->findClient($session->getClientId()); if ($model === null) { return null; } @@ -80,4 +77,8 @@ class ClientStorage extends AbstractStorage implements ClientInterface { return $entity; } + private function findClient(string $clientId): ?OauthClient { + return OauthClient::findOne($clientId); + } + } diff --git a/api/components/ReCaptcha/Validator.php b/api/components/ReCaptcha/Validator.php index a30b919..737e40f 100644 --- a/api/components/ReCaptcha/Validator.php +++ b/api/components/ReCaptcha/Validator.php @@ -12,7 +12,7 @@ use yii\di\Instance; class Validator extends \yii\validators\Validator { - private const SITE_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify'; + private const SITE_VERIFY_URL = 'https://recaptcha.net/recaptcha/api/siteverify'; private const REPEAT_LIMIT = 3; private const REPEAT_TIMEOUT = 1; diff --git a/api/config/config.php b/api/config/config.php index 8969ef5..5637f01 100644 --- a/api/config/config.php +++ b/api/config/config.php @@ -86,5 +86,6 @@ return [ 'mojang' => api\modules\mojang\Module::class, 'internal' => api\modules\internal\Module::class, 'accounts' => api\modules\accounts\Module::class, + 'oauth' => api\modules\oauth\Module::class, ], ]; diff --git a/api/config/routes.php b/api/config/routes.php index aa624a5..9a385ef 100644 --- a/api/config/routes.php +++ b/api/config/routes.php @@ -3,8 +3,17 @@ * @var array $params */ return [ - '/oauth2/v1/' => 'oauth/', + // Oauth module routes + '/oauth2/v1/' => 'oauth/authorization/', + 'POST /v1/oauth2/' => 'oauth/clients/create', + 'GET /v1/oauth2/' => 'oauth/clients/get', + 'PUT /v1/oauth2/' => 'oauth/clients/update', + 'DELETE /v1/oauth2/' => 'oauth/clients/delete', + 'POST /v1/oauth2//reset' => 'oauth/clients/reset', + 'GET /v1/accounts//oauth2/clients' => 'oauth/clients/get-per-account', + '/account/v1/info' => 'oauth/identity/index', + // Accounts module routes 'GET /v1/accounts/' => 'accounts/default/get', 'GET /v1/accounts//two-factor-auth' => 'accounts/default/get-two-factor-auth-credentials', 'POST /v1/accounts//two-factor-auth' => 'accounts/default/enable-two-factor-auth', @@ -13,6 +22,7 @@ return [ 'DELETE /v1/accounts//ban' => 'accounts/default/pardon', '/v1/accounts//' => 'accounts/default/', + // Legacy accounts endpoints. It should be removed after frontend will be updated. 'GET /accounts/current' => 'accounts/default/get', 'POST /accounts/change-username' => 'accounts/default/username', 'POST /accounts/change-password' => 'accounts/default/password', @@ -25,14 +35,14 @@ return [ 'DELETE /two-factor-auth' => 'accounts/default/disable-two-factor-auth', 'POST /accounts/change-lang' => 'accounts/default/language', - '/account/v1/info' => 'identity-info/index', - + // Session server module routes '/minecraft/session/join' => 'session/session/join', '/minecraft/session/legacy/join' => 'session/session/join-legacy', '/minecraft/session/hasJoined' => 'session/session/has-joined', '/minecraft/session/legacy/hasJoined' => 'session/session/has-joined-legacy', '/minecraft/session/profile/' => 'session/session/profile', + // Mojang API module routes '/mojang/profiles/' => 'mojang/api/uuid-by-username', '/mojang/profiles//names' => 'mojang/api/usernames-by-uuid', 'POST /mojang/profiles' => 'mojang/api/uuids-by-usernames', diff --git a/api/modules/accounts/actions/BaseAccountAction.php b/api/modules/accounts/actions/BaseAccountAction.php index 874602d..6f637a4 100644 --- a/api/modules/accounts/actions/BaseAccountAction.php +++ b/api/modules/accounts/actions/BaseAccountAction.php @@ -9,7 +9,6 @@ use yii\web\NotFoundHttpException; abstract class BaseAccountAction extends Action { - // TODO: вернуть final модификатор метода после того, как в GoAOP добавят поддержку аспектов для final методов public function run(int $id): array { $className = $this->getFormClassName(); /** @var AccountActionForm $model */ diff --git a/api/modules/oauth/Module.php b/api/modules/oauth/Module.php new file mode 100644 index 0000000..2ced001 --- /dev/null +++ b/api/modules/oauth/Module.php @@ -0,0 +1,10 @@ + [ 'only' => ['complete'], ], diff --git a/api/modules/oauth/controllers/ClientsController.php b/api/modules/oauth/controllers/ClientsController.php new file mode 100644 index 0000000..3e7960a --- /dev/null +++ b/api/modules/oauth/controllers/ClientsController.php @@ -0,0 +1,192 @@ + [ + 'class' => AccessControl::class, + 'rules' => [ + [ + 'actions' => ['create'], + 'allow' => true, + 'permissions' => [P::CREATE_OAUTH_CLIENTS], + ], + [ + 'actions' => ['update', 'delete', 'reset'], + 'allow' => true, + 'permissions' => [P::MANAGE_OAUTH_CLIENTS], + 'roleParams' => function() { + return [ + 'clientId' => Yii::$app->request->get('clientId'), + ]; + }, + ], + [ + 'actions' => ['get'], + 'allow' => true, + 'permissions' => [P::VIEW_OAUTH_CLIENTS], + 'roleParams' => function() { + return [ + 'clientId' => Yii::$app->request->get('clientId'), + ]; + }, + ], + [ + 'actions' => ['get-per-account'], + 'allow' => true, + 'permissions' => [P::VIEW_OAUTH_CLIENTS], + 'roleParams' => function() { + return [ + 'accountId' => Yii::$app->request->get('accountId'), + ]; + }, + ], + ], + ], + ]); + } + + public function actionGet(string $clientId): array { + return $this->formatClient($this->findOauthClient($clientId)); + } + + public function actionCreate(string $type): array { + $account = Yii::$app->user->identity->getAccount(); + if ($account === null) { + throw new ThisShouldNotHappenException('This form should not to be executed without associated account'); + } + + $client = new OauthClient(); + $client->account_id = $account->id; + $client->type = $type; + $requestModel = $this->createForm($client); + $requestModel->load(Yii::$app->request->post()); + $form = new OauthClientForm($client); + if (!$form->save($requestModel)) { + return [ + 'success' => false, + 'errors' => $requestModel->getValidationErrors(), + ]; + } + + return [ + 'success' => true, + 'data' => $this->formatClient($client), + ]; + } + + public function actionUpdate(string $clientId): array { + $client = $this->findOauthClient($clientId); + $requestModel = $this->createForm($client); + $requestModel->load(Yii::$app->request->post()); + $form = new OauthClientForm($client); + if (!$form->save($requestModel)) { + return [ + 'success' => false, + 'errors' => $requestModel->getValidationErrors(), + ]; + } + + return [ + 'success' => true, + 'data' => $this->formatClient($client), + ]; + } + + public function actionDelete(string $clientId): array { + $client = $this->findOauthClient($clientId); + (new OauthClientForm($client))->delete(); + + return [ + 'success' => true, + ]; + } + + public function actionReset(string $clientId, string $regenerateSecret = null): array { + $client = $this->findOauthClient($clientId); + $form = new OauthClientForm($client); + $form->reset($regenerateSecret !== null); + + return [ + 'success' => true, + 'data' => $this->formatClient($client), + ]; + } + + public function actionGetPerAccount(int $accountId): array { + /** @var Account|null $account */ + $account = Account::findOne(['id' => $accountId]); + if ($account === null) { + throw new NotFoundHttpException(); + } + + $clients = $account->oauthClients; + $result = array_map(function(OauthClient $client) { + return $this->formatClient($client); + }, $clients); + + return $result; + } + + private function formatClient(OauthClient $client): array { + $result = [ + 'clientId' => $client->id, + 'clientSecret' => $client->secret, + 'type' => $client->type, + '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; + break; + case OauthClient::TYPE_MINECRAFT_SERVER: + $result['minecraftServerIp'] = $client->minecraft_server_ip; + break; + } + + return $result; + } + + private function createForm(OauthClient $client): OauthClientTypeForm { + try { + $model = OauthClientFormFactory::create($client); + } catch (UnsupportedOauthClientType $e) { + Yii::warning('Someone tried use ' . $client->type . ' type of oauth form.'); + throw new NotFoundHttpException(null, 0, $e); + } + + return $model; + } + + private function findOauthClient(string $clientId): OauthClient { + /** @var OauthClient|null $client */ + $client = OauthClient::findOne($clientId); + if ($client === null) { + throw new NotFoundHttpException(); + } + + return $client; + } + +} diff --git a/api/controllers/IdentityInfoController.php b/api/modules/oauth/controllers/IdentityController.php similarity index 75% rename from api/controllers/IdentityInfoController.php rename to api/modules/oauth/controllers/IdentityController.php index bbc73e9..5f0f952 100644 --- a/api/controllers/IdentityInfoController.php +++ b/api/modules/oauth/controllers/IdentityController.php @@ -1,16 +1,17 @@ [ 'class' => AccessControl::class, 'rules' => [ @@ -32,7 +33,7 @@ class IdentityInfoController extends Controller { public function actionIndex(): array { /** @noinspection NullPointerExceptionInspection */ - return (new OauthAccountInfo(Yii::$app->user->getIdentity()->getAccount()))->info(); + return (new IdentityInfo(Yii::$app->user->getIdentity()->getAccount()))->info(); } } diff --git a/api/modules/oauth/exceptions/InvalidOauthClientState.php b/api/modules/oauth/exceptions/InvalidOauthClientState.php new file mode 100644 index 0000000..9a0db4d --- /dev/null +++ b/api/modules/oauth/exceptions/InvalidOauthClientState.php @@ -0,0 +1,8 @@ +type = $type; + } + + public function getType(): string { + return $this->type; + } + +} diff --git a/api/modules/oauth/models/ApplicationType.php b/api/modules/oauth/models/ApplicationType.php new file mode 100644 index 0000000..244bf7c --- /dev/null +++ b/api/modules/oauth/models/ApplicationType.php @@ -0,0 +1,30 @@ + E::REDIRECT_URI_REQUIRED], + ['redirectUri', 'url', 'validSchemes' => ['[\w]+'], 'message' => E::REDIRECT_URI_INVALID], + ['description', 'string'], + ]); + } + + public function applyToClient(OauthClient $client): void { + parent::applyToClient($client); + $client->description = $this->description; + $client->redirect_uri = $this->redirectUri; + } + +} diff --git a/api/modules/oauth/models/BaseOauthClientType.php b/api/modules/oauth/models/BaseOauthClientType.php new file mode 100644 index 0000000..72a97ec --- /dev/null +++ b/api/modules/oauth/models/BaseOauthClientType.php @@ -0,0 +1,40 @@ + E::NAME_REQUIRED], + ['websiteUrl', 'url', 'message' => E::WEBSITE_URL_INVALID], + ]; + } + + public function load($data, $formName = null): bool { + return parent::load($data, $formName); + } + + public function validate($attributeNames = null, $clearErrors = true): bool { + return parent::validate($attributeNames, $clearErrors); + } + + public function getValidationErrors(): array { + return $this->getFirstErrors(); + } + + public function applyToClient(OauthClient $client): void { + $client->name = $this->name; + $client->website_url = $this->websiteUrl; + } + +} diff --git a/api/models/OauthAccountInfo.php b/api/modules/oauth/models/IdentityInfo.php similarity index 85% rename from api/models/OauthAccountInfo.php rename to api/modules/oauth/models/IdentityInfo.php index 55a6f3b..24ff32d 100644 --- a/api/models/OauthAccountInfo.php +++ b/api/modules/oauth/models/IdentityInfo.php @@ -1,11 +1,13 @@ E::MINECRAFT_SERVER_IP_INVALID], + ]); + } + + public function applyToClient(OauthClient $client): void { + parent::applyToClient($client); + $client->minecraft_server_ip = $this->minecraftServerIp; + } + +} diff --git a/api/modules/oauth/models/OauthClientForm.php b/api/modules/oauth/models/OauthClientForm.php new file mode 100644 index 0000000..8321a22 --- /dev/null +++ b/api/modules/oauth/models/OauthClientForm.php @@ -0,0 +1,96 @@ +type === null) { + throw new InvalidOauthClientState('client\'s type field must be set'); + } + + $this->client = $client; + } + + public function getClient(): OauthClient { + return $this->client; + } + + public function save(OauthClientTypeForm $form): bool { + if (!$form->validate()) { + return false; + } + + $client = $this->getClient(); + $form->applyToClient($client); + + if ($client->isNewRecord) { + $baseId = $id = substr(Inflector::slug($client->name), 0, 250); + $i = 0; + while ($this->isClientExists($id)) { + $id = $baseId . ++$i; + } + + $client->id = $id; + $client->generateSecret(); + } + + if (!$client->save()) { + throw new ThisShouldNotHappenException('Cannot save oauth client'); + } + + return true; + } + + public function delete(): bool { + $transaction = Yii::$app->db->beginTransaction(); + + $client = $this->client; + $client->is_deleted = true; + if (!$client->save()) { + throw new ThisShouldNotHappenException('Cannot update oauth client'); + } + + Yii::$app->queue->push(ClearOauthSessions::createFromOauthClient($client)); + + $transaction->commit(); + + return true; + } + + public function reset(bool $regenerateSecret = false): bool { + $transaction = Yii::$app->db->beginTransaction(); + + $client = $this->client; + if ($regenerateSecret) { + $client->generateSecret(); + if (!$client->save()) { + throw new ThisShouldNotHappenException('Cannot update oauth client'); + } + } + + Yii::$app->queue->push(ClearOauthSessions::createFromOauthClient($client, time())); + + $transaction->commit(); + + return true; + } + + protected function isClientExists(string $id): bool { + return OauthClient::find()->andWhere(['id' => $id])->exists(); + } + +} diff --git a/api/modules/oauth/models/OauthClientFormFactory.php b/api/modules/oauth/models/OauthClientFormFactory.php new file mode 100644 index 0000000..62dfeed --- /dev/null +++ b/api/modules/oauth/models/OauthClientFormFactory.php @@ -0,0 +1,37 @@ +type) { + case OauthClient::TYPE_APPLICATION: + return new ApplicationType([ + 'name' => $client->name, + 'websiteUrl' => $client->website_url, + 'description' => $client->description, + 'redirectUri' => $client->redirect_uri, + ]); + case OauthClient::TYPE_MINECRAFT_SERVER: + return new MinecraftServerType([ + 'name' => $client->name, + 'websiteUrl' => $client->website_url, + 'minecraftServerIp' => $client->minecraft_server_ip, + ]); + } + + throw new UnsupportedOauthClientType($client->type); + } + +} diff --git a/api/modules/oauth/models/OauthClientTypeForm.php b/api/modules/oauth/models/OauthClientTypeForm.php new file mode 100644 index 0000000..58d90ef --- /dev/null +++ b/api/modules/oauth/models/OauthClientTypeForm.php @@ -0,0 +1,18 @@ +getAuthorizationCodeGrant()->checkAuthorizeParams(); $client = $authParams->getClient(); - /** @var \common\models\OauthClient $clientModel */ - $clientModel = OauthClient::findOne($client->getId()); + /** @var OauthClient $clientModel */ + $clientModel = $this->findClient($client->getId()); $response = $this->buildSuccessResponse( Yii::$app->request->getQueryParams(), $clientModel, @@ -90,9 +90,10 @@ class OauthProcess { Yii::$app->statsd->inc('oauth.complete.attempt'); $grant = $this->getAuthorizationCodeGrant(); $authParams = $grant->checkAuthorizeParams(); + /** @var Account $account */ $account = Yii::$app->user->identity->getAccount(); /** @var \common\models\OauthClient $clientModel */ - $clientModel = OauthClient::findOne($authParams->getClient()->getId()); + $clientModel = $this->findClient($authParams->getClient()->getId()); if (!$this->canAutoApprove($account, $clientModel, $authParams)) { Yii::$app->statsd->inc('oauth.complete.approve_required'); @@ -164,6 +165,10 @@ class OauthProcess { return $response; } + private function findClient(string $clientId): ?OauthClient { + return OauthClient::findOne($clientId); + } + /** * Метод проверяет, может ли текущий пользователь быть автоматически авторизован * для указанного клиента без запроса доступа к необходимому списку прав diff --git a/api/modules/session/filters/RateLimiter.php b/api/modules/session/filters/RateLimiter.php index 12cd5b7..09b9efe 100644 --- a/api/modules/session/filters/RateLimiter.php +++ b/api/modules/session/filters/RateLimiter.php @@ -85,7 +85,7 @@ class RateLimiter extends \yii\filters\RateLimiter { } if ($this->server === null) { - /** @var OauthClient $server */ + /** @var OauthClient|null $server */ $this->server = OauthClient::findOne($serverId); // TODO: убедится, что это сервер if ($this->server === null) { diff --git a/common/config/config.php b/common/config/config.php index a54c6e4..c8f3632 100644 --- a/common/config/config.php +++ b/common/config/config.php @@ -1,6 +1,6 @@ '1.1.24', + 'version' => '{{PLACE_VERSION_HERE}}', // This will be replaced by build tool 'vendorPath' => dirname(__DIR__, 2) . '/vendor', 'components' => [ 'cache' => [ diff --git a/common/helpers/Error.php b/common/helpers/Error.php index d31a9f1..8e34ba9 100644 --- a/common/helpers/Error.php +++ b/common/helpers/Error.php @@ -60,4 +60,13 @@ final class Error { const OTP_ALREADY_ENABLED = 'error.otp_already_enabled'; const OTP_NOT_ENABLED = 'error.otp_not_enabled'; + const NAME_REQUIRED = 'error.name_required'; + + const REDIRECT_URI_REQUIRED = 'error.redirectUri_required'; + const REDIRECT_URI_INVALID = 'error.redirectUri_invalid'; + + const WEBSITE_URL_INVALID = 'error.websiteUrl_invalid'; + + const MINECRAFT_SERVER_IP_INVALID = 'error.minecraftServerIp_invalid'; + } diff --git a/common/models/Account.php b/common/models/Account.php index 46eddba..a923a63 100644 --- a/common/models/Account.php +++ b/common/models/Account.php @@ -34,6 +34,7 @@ use const common\LATEST_RULES_VERSION; * Отношения: * @property EmailActivation[] $emailActivations * @property OauthSession[] $oauthSessions + * @property OauthClient[] $oauthClients * @property UsernameHistory[] $usernameHistory * @property AccountSession[] $sessions * @property MinecraftAccessKey[] $minecraftAccessKeys @@ -93,6 +94,11 @@ class Account extends ActiveRecord { return $this->hasMany(OauthSession::class, ['owner_id' => 'id'])->andWhere(['owner_type' => 'user']); } + public function getOauthClients(): OauthClientQuery { + /** @noinspection PhpIncompatibleReturnTypeInspection */ + return $this->hasMany(OauthClient::class, ['account_id' => 'id']); + } + public function getUsernameHistory(): ActiveQuery { return $this->hasMany(UsernameHistory::class, ['account_id' => 'id']); } diff --git a/common/models/OauthClient.php b/common/models/OauthClient.php index 58b444b..aabccc7 100644 --- a/common/models/OauthClient.php +++ b/common/models/OauthClient.php @@ -1,48 +1,62 @@ function(self $model) { - return $model->isNewRecord; - }], - [['id'], 'unique', 'when' => function(self $model) { - return $model->isNewRecord; - }], - [['name', 'description'], 'required'], - [['name', 'description'], 'string', 'max' => 255], + [ + 'class' => TimestampBehavior::class, + 'updatedAtAttribute' => false, + ], ]; } - public function getAccount() { + public function generateSecret(): void { + $this->secret = Yii::$app->security->generateRandomString(64); + } + + public function getAccount(): ActiveQuery { return $this->hasOne(Account::class, ['id' => 'account_id']); } - public function getSessions() { + public function getSessions(): ActiveQuery { return $this->hasMany(OauthSession::class, ['client_id' => 'id']); } + public static function find(): OauthClientQuery { + return Yii::createObject(OauthClientQuery::class, [static::class]); + } + } diff --git a/common/models/OauthClientQuery.php b/common/models/OauthClientQuery.php new file mode 100644 index 0000000..cc981b9 --- /dev/null +++ b/common/models/OauthClientQuery.php @@ -0,0 +1,34 @@ +showDeleted = true; + return $this; + } + + public function onlyDeleted(): self { + $this->showDeleted = true; + return $this->andWhere(['is_deleted' => true]); + } + + public function createCommand($db = null): Command { + if ($this->showDeleted === false) { + $this->andWhere(['is_deleted' => false]); + } + + return parent::createCommand($db); + } + +} diff --git a/common/models/OauthSession.php b/common/models/OauthSession.php index acb3049..e654dcd 100644 --- a/common/models/OauthSession.php +++ b/common/models/OauthSession.php @@ -4,6 +4,7 @@ namespace common\models; use common\components\Redis\Set; use Yii; use yii\base\NotSupportedException; +use yii\behaviors\TimestampBehavior; use yii\db\ActiveQuery; use yii\db\ActiveRecord; @@ -14,6 +15,7 @@ use yii\db\ActiveRecord; * @property string|null $owner_id * @property string $client_id * @property string $client_redirect_uri + * @property integer $created_at * * Отношения * @property OauthClient $client @@ -26,6 +28,15 @@ class OauthSession extends ActiveRecord { return '{{%oauth_sessions}}'; } + public function behaviors() { + return [ + [ + 'class' => TimestampBehavior::class, + 'updatedAtAttribute' => false, + ], + ]; + } + public function getClient(): ActiveQuery { return $this->hasOne(OauthClient::class, ['id' => 'client_id']); } diff --git a/common/models/amqp/AccountBanned.php b/common/models/amqp/AccountBanned.php index 1b1198e..b612c34 100644 --- a/common/models/amqp/AccountBanned.php +++ b/common/models/amqp/AccountBanned.php @@ -1,9 +1,9 @@ user->findIdentityByAccessToken($accessToken); diff --git a/common/rbac/rules/OauthClientOwner.php b/common/rbac/rules/OauthClientOwner.php new file mode 100644 index 0000000..e303743 --- /dev/null +++ b/common/rbac/rules/OauthClientOwner.php @@ -0,0 +1,61 @@ +name === P::VIEW_OWN_OAUTH_CLIENTS) { + return (new AccountOwner())->execute($accessToken, $item, ['accountId' => $accountId]); + } + + $clientId = $params['clientId'] ?? null; + if ($clientId === null) { + return false; + } + + /** @var OauthClient|null $client */ + $client = OauthClient::findOne($clientId); + if ($client === null) { + return false; + } + + $identity = Yii::$app->user->findIdentityByAccessToken($accessToken); + if ($identity === null) { + return false; + } + + $account = $identity->getAccount(); + if ($account === null) { + return false; + } + + if ($account->id !== $client->account_id) { + return false; + } + + return true; + } + +} diff --git a/common/tasks/ClearOauthSessions.php b/common/tasks/ClearOauthSessions.php new file mode 100644 index 0000000..4a54733 --- /dev/null +++ b/common/tasks/ClearOauthSessions.php @@ -0,0 +1,69 @@ +clientId = $client->id; + if ($notSince !== null) { + $result->notSince = $notSince; + } + + return $result; + } + + public function getTtr(): int { + return 60/*sec*/ * 5/*min*/; + } + + public function canRetry($attempt, $error): bool { + return true; + } + + /** + * @param \yii\queue\Queue $queue which pushed and is handling the job + * + * @throws \Exception + * @throws \Throwable + * @throws \yii\db\StaleObjectException + */ + public function execute($queue): void { + Yii::$app->statsd->inc('queue.clearOauthSessions.attempt'); + /** @var OauthClient|null $client */ + $client = OauthClient::find() + ->includeDeleted() + ->andWhere(['id' => $this->clientId]) + ->one(); + if ($client === null) { + return; + } + + $sessionsQuery = $client->getSessions(); + if ($this->notSince !== null) { + $sessionsQuery->andWhere(['<=', 'created_at', $this->notSince]); + } + + foreach ($sessionsQuery->each(100, Yii::$app->unbufferedDb) as $session) { + /** @var \common\models\OauthSession $session */ + $session->delete(); + } + } + +} diff --git a/common/validators/MinecraftServerAddressValidator.php b/common/validators/MinecraftServerAddressValidator.php new file mode 100644 index 0000000..2cf98bb --- /dev/null +++ b/common/validators/MinecraftServerAddressValidator.php @@ -0,0 +1,24 @@ +message, []]; + } + +} diff --git a/composer.json b/composer.json index 75f759f..2f89bdd 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "require": { "php": "^7.1", "roave/security-advisories": "dev-master", - "yiisoft/yii2": "2.0.13.1", + "yiisoft/yii2": "2.0.14", "yiisoft/yii2-swiftmailer": "~2.1.0", "ramsey/uuid": "^3.5", "league/oauth2-server": "^4.1", @@ -24,7 +24,7 @@ "bacon/bacon-qr-code": "^1.0", "paragonie/constant_time_encoding": "^2.0", "webmozart/assert": "^1.2.0", - "goaop/framework": "~2.1.2", + "goaop/framework": "~2.2.0", "domnikl/statsd": "^2.6", "yiisoft/yii2-queue": "~2.0.2", "enqueue/amqp-lib": "^0.8.11" @@ -34,7 +34,7 @@ "yiisoft/yii2-faker": "*", "flow/jsonpath": "^0.3.1", "phpunit/phpunit": "^6.0", - "codeception/codeception": "dev-reset_yii2_app#6045eed00f7b163226d04fe40333f076b0f132e3", + "codeception/codeception": "2.3.8", "codeception/specify": "^1.0.0", "codeception/verify": "*", "mockery/mockery": "^1.0.0", @@ -52,10 +52,6 @@ { "type": "git", "url": "git@gitlab.ely.by:elyby/email-renderer.git" - }, - { - "type": "git", - "url": "git@github.com:erickskrauch/Codeception.git" } ], "autoload": { diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..d3642b6 --- /dev/null +++ b/composer.lock @@ -0,0 +1,5575 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "b322fafe1486ee057b633a35d336c1a1", + "packages": [ + { + "name": "bacon/bacon-qr-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "5a91b62b9d37cee635bbf8d553f4546057250bee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/5a91b62b9d37cee635bbf8d553f4546057250bee", + "reference": "5a91b62b9d37cee635bbf8d553f4546057250bee", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^5.4|^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8" + }, + "suggest": { + "ext-gd": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-0": { + "BaconQrCode": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "http://www.dasprids.de", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "time": "2017-10-17T09:59:25+00:00" + }, + { + "name": "beberlei/assert", + "version": "v2.9.2", + "source": { + "type": "git", + "url": "https://github.com/beberlei/assert.git", + "reference": "2d555f72f3f4ff9e72a7bc17cb8a53c86737c8a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/beberlei/assert/zipball/2d555f72f3f4ff9e72a7bc17cb8a53c86737c8a0", + "reference": "2d555f72f3f4ff9e72a7bc17cb8a53c86737c8a0", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.1.1", + "phpunit/phpunit": "^4.8.35|^5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Assert\\": "lib/Assert" + }, + "files": [ + "lib/Assert/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de", + "role": "Lead Developer" + }, + { + "name": "Richard Quadling", + "email": "rquadling@gmail.com", + "role": "Collaborator" + } + ], + "description": "Thin assertion library for input validation in business models.", + "keywords": [ + "assert", + "assertion", + "validation" + ], + "time": "2018-01-25T13:33:16+00:00" + }, + { + "name": "bower-asset/inputmask", + "version": "3.3.11", + "source": { + "type": "git", + "url": "https://github.com/RobinHerbots/Inputmask.git", + "reference": "5e670ad62f50c738388d4dcec78d2888505ad77b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/5e670ad62f50c738388d4dcec78d2888505ad77b", + "reference": "5e670ad62f50c738388d4dcec78d2888505ad77b", + "shasum": null + }, + "require": { + "bower-asset/jquery": ">=1.7" + }, + "type": "bower-asset", + "license": [ + "http://opensource.org/licenses/mit-license.php" + ] + }, + { + "name": "bower-asset/jquery", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/jquery/jquery-dist.git", + "reference": "77d2a51d0520d2ee44173afdf4e40a9201f5964e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/77d2a51d0520d2ee44173afdf4e40a9201f5964e", + "reference": "77d2a51d0520d2ee44173afdf4e40a9201f5964e", + "shasum": null + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "bower-asset/punycode", + "version": "v1.3.2", + "source": { + "type": "git", + "url": "https://github.com/bestiejs/punycode.js.git", + "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3", + "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3", + "shasum": null + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/raven-js", + "version": "3.22.2", + "source": { + "type": "git", + "url": "https://github.com/getsentry/raven-js.git", + "reference": "f8eec063c95f70d8978f895284946bd278748d97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getsentry/raven-js/zipball/f8eec063c95f70d8978f895284946bd278748d97", + "reference": "f8eec063c95f70d8978f895284946bd278748d97", + "shasum": null + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/yii2-pjax", + "version": "2.0.7.1", + "source": { + "type": "git", + "url": "git@github.com:yiisoft/jquery-pjax.git", + "reference": "aef7b953107264f00234902a3880eb50dafc48be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/aef7b953107264f00234902a3880eb50dafc48be", + "reference": "aef7b953107264f00234902a3880eb50dafc48be", + "shasum": null + }, + "require": { + "bower-asset/jquery": ">=1.8" + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "cebe/markdown", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/cebe/markdown.git", + "reference": "25b28bae8a6f185b5030673af77b32e1163d5c6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cebe/markdown/zipball/25b28bae8a6f185b5030673af77b32e1163d5c6e", + "reference": "25b28bae8a6f185b5030673af77b32e1163d5c6e", + "shasum": "" + }, + "require": { + "lib-pcre": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "cebe/indent": "*", + "facebook/xhprof": "*@dev", + "phpunit/phpunit": "4.1.*" + }, + "bin": [ + "bin/markdown" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "cebe\\markdown\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Creator" + } + ], + "description": "A super fast, highly extensible markdown parser for PHP", + "homepage": "https://github.com/cebe/markdown#readme", + "keywords": [ + "extensible", + "fast", + "gfm", + "markdown", + "markdown-extra" + ], + "time": "2017-07-16T21:13:23+00:00" + }, + { + "name": "daveearley/daves-email-validation-tool", + "version": "v0.1.9", + "source": { + "type": "git", + "url": "https://github.com/daveearley/Email-Validation-Tool.git", + "reference": "a5942e5387dfdd259a9f8e3ba53bc089a77cf7ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/daveearley/Email-Validation-Tool/zipball/a5942e5387dfdd259a9f8e3ba53bc089a77cf7ee", + "reference": "a5942e5387dfdd259a9f8e3ba53bc089a77cf7ee", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.9", + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "EmailValidation\\": "src/", + "EmailValidation\\Tests\\": "src/tests" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dave Earley", + "email": "dave@earley.email" + } + ], + "description": "An easy to use, accurate-ish & extensible email validation library for PHP 7+", + "keywords": [ + "disposable email check", + "email", + "email validation", + "email verification", + "validate email" + ], + "time": "2018-02-01T17:47:27+00:00" + }, + { + "name": "doctrine/annotations", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^7.1" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2017-12-06T07:11:42+00:00" + }, + { + "name": "doctrine/cache", + "version": "v1.7.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "b3217d58609e9c8e661cd41357a54d926c4a2a1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/b3217d58609e9c8e661cd41357a54d926c4a2a1a", + "reference": "b3217d58609e9c8e661cd41357a54d926c4a2a1a", + "shasum": "" + }, + "require": { + "php": "~7.1" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^5.7", + "predis/predis": "~1.0" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ], + "time": "2017-08-25T07:02:50+00:00" + }, + { + "name": "doctrine/lexer", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ], + "time": "2014-09-09T13:34:57+00:00" + }, + { + "name": "domnikl/statsd", + "version": "2.6.0", + "source": { + "type": "git", + "url": "https://github.com/domnikl/statsd-php.git", + "reference": "ca9daa049fd9f353c0551384612bb4f17615b14a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/domnikl/statsd-php/zipball/ca9daa049fd9f353c0551384612bb4f17615b14a", + "reference": "ca9daa049fd9f353c0551384612bb4f17615b14a", + "shasum": "" + }, + "require": { + "php": ">= 5.6.0" + }, + "require-dev": { + "phpunit/phpunit": ">= 4.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Domnikl\\Statsd\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dominik Liebler", + "email": "liebler.dominik@gmail.com" + } + ], + "description": "a PHP client for statsd", + "homepage": "https://domnikl.github.com/statsd-php", + "keywords": [ + "Metrics", + "monitoring", + "statistics", + "statsd", + "udp" + ], + "time": "2017-07-30T17:40:00+00:00" + }, + { + "name": "egulias/email-validator", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "1bec00a10039b823cc94eef4eddd47dcd3b2ca04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/1bec00a10039b823cc94eef4eddd47dcd3b2ca04", + "reference": "1bec00a10039b823cc94eef4eddd47dcd3b2ca04", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^1.0.1", + "php": ">= 5.5" + }, + "require-dev": { + "dominicsayers/isemail": "dev-master", + "phpunit/phpunit": "^4.8.35", + "satooshi/php-coveralls": "^1.0.1" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "EmailValidator" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "time": "2017-11-15T23:40:40+00:00" + }, + { + "name": "ely/amqp-controller", + "version": "dev-master", + "source": { + "type": "git", + "url": "git@gitlab.ely.by:elyby/amqp-controller.git", + "reference": "d7f8cdbc66c45e477c9c7d5d509bc0c1b11fd3ec" + }, + "require": { + "php-amqplib/php-amqplib": "^2.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ely\\Amqp\\": "src/" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ely.by team", + "email": "team@ely.by" + }, + { + "name": "ErickSkrauch", + "email": "erickskrauch@ely.by" + } + ], + "homepage": "http://ely.by", + "keywords": [ + "" + ], + "time": "2016-11-15T19:40:20+00:00" + }, + { + "name": "ely/email-renderer", + "version": "dev-master", + "source": { + "type": "git", + "url": "git@gitlab.ely.by:elyby/email-renderer.git", + "reference": "8aa2e71c5b3b8e4a726c3c090b2997030ba29f73" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ely\\Email\\": "src-php/" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ely.by team", + "email": "team@ely.by" + }, + { + "name": "ErickSkrauch", + "email": "erickskrauch@ely.by" + }, + { + "name": "SleepWalker", + "email": "dev@udf.su" + } + ], + "homepage": "http://ely.by", + "keywords": [ + "" + ], + "time": "2017-10-04T17:17:10+00:00" + }, + { + "name": "ely/yii2-tempmail-validator", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/elyby/yii2-tempmail-validator.git", + "reference": "92d74e3369868ef9238d8e7e487c301a4572dcb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/elyby/yii2-tempmail-validator/zipball/92d74e3369868ef9238d8e7e487c301a4572dcb1", + "reference": "92d74e3369868ef9238d8e7e487c301a4572dcb1", + "shasum": "" + }, + "require": { + "daveearley/daves-email-validation-tool": "^0.1.5", + "php": ">=7", + "yiisoft/yii2": "*" + }, + "require-dev": { + "phpunit/phpunit": "~4.8 || ~5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ely\\Yii2\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ely.by team", + "email": "team@ely.by" + }, + { + "name": "ErickSkrauch", + "email": "erickskrauch@ely.by" + } + ], + "description": "Yii2 validator to prevent using tempmail services or any variable domains", + "homepage": "https://github.com/elyby/yii2-tempmail-validator", + "keywords": [ + "email", + "validation", + "yii2" + ], + "time": "2017-09-30T22:51:45+00:00" + }, + { + "name": "emarref/jwt", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/emarref/jwt.git", + "reference": "79f563750ff90dabd4fa677c4b4e5ec9ed52d9b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/emarref/jwt/zipball/79f563750ff90dabd4fa677c4b4e5ec9ed52d9b4", + "reference": "79f563750ff90dabd4fa677c4b4e5ec9ed52d9b4", + "shasum": "" + }, + "require": { + "php": ">=5.4" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "ext-openssl": "Enables more token encryption options" + }, + "type": "library", + "autoload": { + "psr-4": { + "Emarref\\Jwt\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Malcolm Fell", + "email": "emarref@gmail.com" + } + ], + "description": "A JWT implementation", + "time": "2016-09-05T20:33:06+00:00" + }, + { + "name": "enqueue/amqp-lib", + "version": "0.8.21", + "source": { + "type": "git", + "url": "https://github.com/php-enqueue/amqp-lib.git", + "reference": "5a0da2f2eccb2ebda4d0b2526e1753c96cf0ef75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-enqueue/amqp-lib/zipball/5a0da2f2eccb2ebda4d0b2526e1753c96cf0ef75", + "reference": "5a0da2f2eccb2ebda4d0b2526e1753c96cf0ef75", + "shasum": "" + }, + "require": { + "enqueue/amqp-tools": "^0.8.5@dev", + "php": ">=5.6", + "php-amqplib/php-amqplib": "^2.7@dev", + "queue-interop/amqp-interop": "^0.7@dev", + "queue-interop/queue-interop": "^0.6@dev" + }, + "require-dev": { + "enqueue/enqueue": "^0.8@dev", + "enqueue/null": "^0.8@dev", + "enqueue/test": "^0.8@dev", + "phpunit/phpunit": "~5.4.0", + "queue-interop/queue-spec": "^0.5.3@dev", + "symfony/config": "^2.8|^3|^4", + "symfony/dependency-injection": "^2.8|^3|^4" + }, + "suggest": { + "enqueue/enqueue": "If you'd like to use advanced features like Client abstract layer or Symfony integration features" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Enqueue\\AmqpLib\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Message Queue Amqp Transport", + "homepage": "https://enqueue.forma-pro.com/", + "keywords": [ + "AMQP", + "messaging", + "queue" + ], + "time": "2018-02-16T11:05:22+00:00" + }, + { + "name": "enqueue/amqp-tools", + "version": "0.8.14", + "source": { + "type": "git", + "url": "https://github.com/php-enqueue/amqp-tools.git", + "reference": "f375dee4d8609fca565a80df1c0f238bf0fe774f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-enqueue/amqp-tools/zipball/f375dee4d8609fca565a80df1c0f238bf0fe774f", + "reference": "f375dee4d8609fca565a80df1c0f238bf0fe774f", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "queue-interop/amqp-interop": "^0.7@dev", + "queue-interop/queue-interop": "^0.6@dev" + }, + "require-dev": { + "enqueue/null": "^0.8@dev", + "enqueue/test": "^0.8@dev", + "phpunit/phpunit": "~5.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Enqueue\\AmqpTools\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Message Queue Amqp Tools", + "homepage": "https://enqueue.forma-pro.com/", + "keywords": [ + "AMQP", + "messaging", + "queue" + ], + "time": "2018-01-10T12:00:35+00:00" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.9.3", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "95e1bae3182efc0f3422896a3236e991049dac69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/95e1bae3182efc0f3422896a3236e991049dac69", + "reference": "95e1bae3182efc0f3422896a3236e991049dac69", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "require-dev": { + "simpletest/simpletest": "^1.1" + }, + "type": "library", + "autoload": { + "psr-0": { + "HTMLPurifier": "library/" + }, + "files": [ + "library/HTMLPurifier.composer.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "time": "2017-06-03T02:28:16+00:00" + }, + { + "name": "goaop/framework", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/goaop/framework.git", + "reference": "152abbffffcba72d2d159b892deb40b0829d0f28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/goaop/framework/zipball/152abbffffcba72d2d159b892deb40b0829d0f28", + "reference": "152abbffffcba72d2d159b892deb40b0829d0f28", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.2.3", + "doctrine/cache": "^1.5", + "goaop/parser-reflection": "~1.4", + "jakubledl/dissect": "~1.0", + "php": ">=5.6.0" + }, + "require-dev": { + "adlawson/vfs": "^0.12", + "doctrine/orm": "^2.5", + "phpunit/phpunit": "^5.7", + "symfony/console": "^2.7|^3.0", + "symfony/filesystem": "^3.3", + "symfony/process": "^3.3" + }, + "suggest": { + "symfony/console": "Enables the usage of the command-line tool." + }, + "bin": [ + "bin/aspect" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Go\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lisachenko Alexander", + "homepage": "https://github.com/lisachenko" + } + ], + "description": "Framework for aspect-oriented programming in PHP.", + "homepage": "http://go.aopphp.com/", + "keywords": [ + "aop", + "aspect", + "library", + "php" + ], + "time": "2018-01-05T23:07:51+00:00" + }, + { + "name": "goaop/parser-reflection", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/goaop/parser-reflection.git", + "reference": "adfc38fee63014880932ebcc4810871b8e33edc9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/goaop/parser-reflection/zipball/adfc38fee63014880932ebcc4810871b8e33edc9", + "reference": "adfc38fee63014880932ebcc4810871b8e33edc9", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^1.2|^2.0|^3.0", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Go\\ParserReflection\\": "src" + }, + "files": [ + "src/bootstrap.php" + ], + "exclude-from-classmap": [ + "/tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alexander Lisachenko", + "email": "lisachenko.it@gmail.com" + } + ], + "description": "Provides reflection information, based on raw source", + "time": "2017-09-03T14:59:13+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0 || ^5.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2017-06-22T18:50:49+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-03-20T17:10:46+00:00" + }, + { + "name": "jakubledl/dissect", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/jakubledl/dissect.git", + "reference": "d3a391de31e45a247e95cef6cf58a91c05af67c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jakubledl/dissect/zipball/d3a391de31e45a247e95cef6cf58a91c05af67c4", + "reference": "d3a391de31e45a247e95cef6cf58a91c05af67c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/console": "~2.1" + }, + "suggest": { + "symfony/console": "for the command-line tool" + }, + "bin": [ + "bin/dissect.php", + "bin/dissect" + ], + "type": "library", + "autoload": { + "psr-0": { + "Dissect": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "unlicense" + ], + "authors": [ + { + "name": "Jakub Lédl", + "email": "jakubledl@gmail.com" + } + ], + "description": "Lexing and parsing in pure PHP", + "homepage": "https://github.com/jakubledl/dissect", + "keywords": [ + "ast", + "lexing", + "parser", + "parsing" + ], + "time": "2013-01-29T21:29:14+00:00" + }, + { + "name": "league/event", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/event.git", + "reference": "e4bfc88dbcb60c8d8a2939a71f9813e141bbe4cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/event/zipball/e4bfc88dbcb60c8d8a2939a71f9813e141bbe4cd", + "reference": "e4bfc88dbcb60c8d8a2939a71f9813e141bbe4cd", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "~1.0.1", + "phpspec/phpspec": "~2.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Event\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Event package", + "keywords": [ + "emitter", + "event", + "listener" + ], + "time": "2015-05-21T12:24:47+00:00" + }, + { + "name": "league/oauth2-server", + "version": "4.1.6", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-server.git", + "reference": "3bec591393b1cc6a3fc02c94d18262e4ffad2e4f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/3bec591393b1cc6a3fc02c94d18262e4ffad2e4f", + "reference": "3bec591393b1cc6a3fc02c94d18262e4ffad2e4f", + "shasum": "" + }, + "require": { + "league/event": "~2.1", + "php": ">=5.4.0", + "symfony/http-foundation": "~2.4|~3.0" + }, + "replace": { + "league/oauth2server": "*", + "lncd/oauth2": "*" + }, + "require-dev": { + "mockery/mockery": "0.9.*", + "phpunit/phpunit": "4.3.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\OAuth2\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + } + ], + "description": "A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.", + "homepage": "http://oauth2.thephpleague.com/", + "keywords": [ + "Authentication", + "api", + "auth", + "authorisation", + "authorization", + "oauth", + "oauth 2", + "oauth 2.0", + "oauth2", + "protect", + "resource", + "secure", + "server" + ], + "time": "2016-09-13T13:42:53+00:00" + }, + { + "name": "mito/yii2-sentry", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/hellowearemito/yii2-sentry.git", + "reference": "a754ab1bf2f7262a6637341b534a4638a787dd6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hellowearemito/yii2-sentry/zipball/a754ab1bf2f7262a6637341b534a4638a787dd6e", + "reference": "a754ab1bf2f7262a6637341b534a4638a787dd6e", + "shasum": "" + }, + "require": { + "bower-asset/raven-js": "~3.17", + "php": ">=5.6.0", + "sentry/sentry": "~1.8", + "yiisoft/yii2": "~2.0.0" + }, + "require-dev": { + "codeception/codeception": "~2.3", + "codeception/mockery-module": "^0.2.2", + "codeception/specify": " *", + "codeception/verify": " *", + "mito/yii2-coding-standards": "~2.0.0@beta", + "satooshi/php-coveralls": "~1.0", + "yiisoft/yii2-codeception": "~2.0" + }, + "type": "yii2-extension", + "autoload": { + "psr-4": { + "mito\\sentry\\": "src/", + "mito\\sentry\\tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Albert Borsos", + "email": "a.borsos@mito.hu" + } + ], + "description": "Yii 2 extension for Sentry", + "keywords": [ + "extension", + "sentry", + "yii2" + ], + "time": "2017-11-28T16:52:35+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v3.1.4", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "e57b3a09784f846411aa7ed664eedb73e3399078" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/e57b3a09784f846411aa7ed664eedb73e3399078", + "reference": "e57b3a09784f846411aa7ed664eedb73e3399078", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2018-01-25T21:31:33+00:00" + }, + { + "name": "paragonie/constant_time_encoding", + "version": "v2.2.1", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "7c74c5d08761ead7b5e89d07c854bc28eb0b2186" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/7c74c5d08761ead7b5e89d07c854bc28eb0b2186", + "reference": "7c74c5d08761ead7b5e89d07c854bc28eb0b2186", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "^6", + "vimeo/psalm": "^0.3|^1" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "time": "2018-01-23T00:54:57+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.11", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/5da4d3c796c275c55f057af5a643ae297d96b4d8", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2017-09-27T21:40:39+00:00" + }, + { + "name": "php-amqplib/php-amqplib", + "version": "v2.7.2", + "source": { + "type": "git", + "url": "https://github.com/php-amqplib/php-amqplib.git", + "reference": "dfd3694a86f1a7394d3693485259d4074a6ec79b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/dfd3694a86f1a7394d3693485259d4074a6ec79b", + "reference": "dfd3694a86f1a7394d3693485259d4074a6ec79b", + "shasum": "" + }, + "require": { + "ext-bcmath": "*", + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "replace": { + "videlalvaro/php-amqplib": "self.version" + }, + "require-dev": { + "phpdocumentor/phpdocumentor": "^2.9", + "phpunit/phpunit": "^4.8", + "scrutinizer/ocular": "^1.1", + "squizlabs/php_codesniffer": "^2.5" + }, + "suggest": { + "ext-sockets": "Use AMQPSocketConnection" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "PhpAmqpLib\\": "PhpAmqpLib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Alvaro Videla", + "role": "Original Maintainer" + }, + { + "name": "John Kelly", + "email": "johnmkelly86@gmail.com", + "role": "Maintainer" + }, + { + "name": "Raúl Araya", + "email": "nubeiro@gmail.com", + "role": "Maintainer" + } + ], + "description": "Formerly videlalvaro/php-amqplib. This library is a pure PHP implementation of the AMQP protocol. It's been tested against RabbitMQ.", + "homepage": "https://github.com/php-amqplib/php-amqplib/", + "keywords": [ + "message", + "queue", + "rabbitmq" + ], + "time": "2018-02-11T19:28:00+00:00" + }, + { + "name": "predis/predis", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/nrk/predis.git", + "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nrk/predis/zipball/f0210e38881631afeafb56ab43405a92cafd9fd1", + "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "ext-curl": "Allows access to Webdis when paired with phpiredis", + "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + }, + "type": "library", + "autoload": { + "psr-4": { + "Predis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniele Alessandri", + "email": "suppakilla@gmail.com", + "homepage": "http://clorophilla.net" + } + ], + "description": "Flexible and feature-complete Redis client for PHP and HHVM", + "homepage": "http://github.com/nrk/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ], + "time": "2016-06-16T16:22:20+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "queue-interop/amqp-interop", + "version": "0.7.2", + "source": { + "type": "git", + "url": "https://github.com/queue-interop/amqp-interop.git", + "reference": "03cfac42483d07ab45d1896a6a2e1d873a216bba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/queue-interop/amqp-interop/zipball/03cfac42483d07ab45d1896a6a2e1d873a216bba", + "reference": "03cfac42483d07ab45d1896a6a2e1d873a216bba", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "queue-interop/queue-interop": "^0.6@dev" + }, + "require-dev": { + "phpunit/phpunit": "~5.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Interop\\Amqp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "time": "2018-01-04T09:52:06+00:00" + }, + { + "name": "queue-interop/queue-interop", + "version": "0.6.1", + "source": { + "type": "git", + "url": "https://github.com/queue-interop/queue-interop.git", + "reference": "38579005c0492c0275bbae31170edf30a7e740fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/queue-interop/queue-interop/zipball/38579005c0492c0275bbae31170edf30a7e740fa", + "reference": "38579005c0492c0275bbae31170edf30a7e740fa", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Interop\\Queue\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of MQs objects. Based on Java JMS", + "homepage": "https://github.com/queue-interop/queue-interop", + "keywords": [ + "MQ", + "jms", + "message queue", + "messaging", + "queue" + ], + "time": "2017-08-10T11:24:15+00:00" + }, + { + "name": "ramsey/uuid", + "version": "3.7.3", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "44abcdad877d9a46685a3a4d221e3b2c4b87cb76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/44abcdad877d9a46685a3a4d221e3b2c4b87cb76", + "reference": "44abcdad877d9a46685a3a4d221e3b2c4b87cb76", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "^1.0|^2.0", + "php": "^5.4 || ^7.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "codeception/aspect-mock": "^1.0 | ~2.0.0", + "doctrine/annotations": "~1.2.0", + "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ^2.1", + "ircmaxell/random-lib": "^1.1", + "jakub-onderka/php-parallel-lint": "^0.9.0", + "mockery/mockery": "^0.9.9", + "moontoast/math": "^1.1", + "php-mock/php-mock-phpunit": "^0.3|^1.1", + "phpunit/phpunit": "^4.7|^5.0", + "squizlabs/php_codesniffer": "^2.3" + }, + "suggest": { + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + }, + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "homepage": "https://github.com/ramsey/uuid", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "time": "2018-01-20T00:28:24+00:00" + }, + { + "name": "roave/security-advisories", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Roave/SecurityAdvisories.git", + "reference": "94230db36bded9d164ffccabcb38c67eedd63595" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/94230db36bded9d164ffccabcb38c67eedd63595", + "reference": "94230db36bded9d164ffccabcb38c67eedd63595", + "shasum": "" + }, + "conflict": { + "adodb/adodb-php": "<5.20.6", + "amphp/artax": "<1.0.6|>=2,<2.0.6", + "aws/aws-sdk-php": ">=3,<3.2.1", + "bugsnag/bugsnag-laravel": ">=2,<2.0.2", + "cakephp/cakephp": ">=1.3,<1.3.18|>=2,<2.4.99|>=2.5,<2.5.99|>=2.6,<2.6.12|>=2.7,<2.7.6|>=3,<3.0.15|>=3.1,<3.1.4", + "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", + "cartalyst/sentry": "<=2.1.6", + "codeigniter/framework": "<=3.0.6", + "composer/composer": "<=1.0.0-alpha11", + "contao-components/mediaelement": ">=2.14.2,<2.21.1", + "contao/core": ">=2,<3.5.32", + "contao/core-bundle": ">=4,<4.4.8", + "contao/listing-bundle": ">=4,<4.4.8", + "contao/newsletter-bundle": ">=4,<4.1", + "doctrine/annotations": ">=1,<1.2.7", + "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", + "doctrine/common": ">=2,<2.4.3|>=2.5,<2.5.1", + "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2", + "doctrine/doctrine-bundle": "<1.5.2", + "doctrine/doctrine-module": "<=0.7.1", + "doctrine/mongodb-odm": ">=1,<1.0.2", + "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", + "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1", + "dompdf/dompdf": ">=0.6,<0.6.2", + "drupal/core": ">=8,<8.3.7", + "drupal/drupal": ">=8,<8.3.7", + "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.2|>=5.4,<5.4.10.1|>=2017.8,<2017.8.1.1", + "firebase/php-jwt": "<2", + "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", + "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", + "gree/jose": "<=2.2", + "gregwar/rst": "<1.0.3", + "guzzlehttp/guzzle": ">=6,<6.2.1|>=4.0.0-rc2,<4.2.4|>=5,<5.3.1", + "illuminate/auth": ">=4,<4.0.99|>=4.1,<4.1.26", + "illuminate/database": ">=4,<4.0.99|>=4.1,<4.1.29", + "joomla/session": "<1.3.1", + "laravel/framework": ">=4,<4.0.99|>=4.1,<4.1.29", + "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", + "magento/magento1ce": ">=1.5.0.1,<1.9.3.2", + "magento/magento1ee": ">=1.9,<1.14.3.2", + "magento/magento2ce": ">=2,<2.2", + "monolog/monolog": ">=1.8,<1.12", + "namshi/jose": "<2.2", + "onelogin/php-saml": "<2.10.4", + "oro/crm": ">=1.7,<1.7.4", + "oro/platform": ">=1.7,<1.7.4", + "padraic/humbug_get_contents": "<1.1.2", + "phpmailer/phpmailer": ">=5,<5.2.24", + "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3", + "phpxmlrpc/extras": "<0.6.1", + "pusher/pusher-php-server": "<2.2.1", + "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", + "shopware/shopware": "<5.3.7", + "silverstripe/cms": ">=3,<=3.0.11|>=3.1,<3.1.11", + "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", + "silverstripe/framework": ">=3,<3.3", + "silverstripe/userforms": "<3", + "simplesamlphp/saml2": "<1.10.4|>=2,<2.3.5|>=3,<3.1.1", + "simplesamlphp/simplesamlphp": "<1.15.2", + "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", + "socalnick/scn-social-auth": "<1.15.2", + "squizlabs/php_codesniffer": ">=1,<2.8.1", + "swiftmailer/swiftmailer": ">=4,<5.4.5", + "symfony/dependency-injection": ">=2,<2.0.17", + "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2", + "symfony/http-foundation": ">=2,<2.3.27|>=2.4,<2.5.11|>=2.6,<2.6.6", + "symfony/http-kernel": ">=2,<2.3.29|>=2.4,<2.5.12|>=2.6,<2.6.8", + "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/routing": ">=2,<2.0.19", + "symfony/security": ">=2,<2.0.25|>=2.1,<2.1.13|>=2.2,<2.2.9|>=2.3,<2.3.37|>=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8.23,<2.8.25|>=3.2.10,<3.2.12|>=3.3.3,<3.3.5", + "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.6|>=2.8.23,<2.8.25|>=3,<3.0.6|>=3.2.10,<3.2.12|>=3.3.3,<3.3.5", + "symfony/security-csrf": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/serializer": ">=2,<2.0.11", + "symfony/symfony": ">=2,<2.3.41|>=2.4,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/translation": ">=2,<2.0.17", + "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", + "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", + "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", + "thelia/backoffice-default-template": ">=2.1,<2.1.2", + "thelia/thelia": ">=2.1.0-beta1,<2.1.3|>=2.1,<2.1.2", + "twig/twig": "<1.20", + "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.22|>=8,<8.7.5", + "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5", + "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4", + "willdurand/js-translation-bundle": "<2.1.1", + "yiisoft/yii": ">=1.1.14,<1.1.15", + "yiisoft/yii2": "<2.0.14", + "yiisoft/yii2-bootstrap": "<2.0.4", + "yiisoft/yii2-dev": "<2.0.14", + "yiisoft/yii2-gii": "<2.0.4", + "yiisoft/yii2-jui": "<2.0.4", + "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", + "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", + "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", + "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5", + "zendframework/zend-diactoros": ">=1,<1.0.4", + "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-http": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.3,<2.3.8|>=2.4,<2.4.1", + "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", + "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", + "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2", + "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4", + "zendframework/zend-validator": ">=2.3,<2.3.6", + "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", + "zendframework/zendframework": ">=2,<2.4.11|>=2.5,<2.5.1", + "zendframework/zendframework1": "<1.12.20", + "zendframework/zendopenid": ">=2,<2.0.2", + "zendframework/zendxml": ">=1,<1.0.1", + "zetacomponents/mail": "<1.8.2", + "zf-commons/zfc-user": "<1.2.2", + "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", + "zfr/zfr-oauth2-server-module": "<0.1.2" + }, + "type": "metapackage", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "role": "maintainer" + } + ], + "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", + "time": "2018-02-19T09:31:21+00:00" + }, + { + "name": "sentry/sentry", + "version": "1.8.3", + "source": { + "type": "git", + "url": "https://github.com/getsentry/sentry-php.git", + "reference": "512661046e403dc5eb5ae192d5a6cda10e068a95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/512661046e403dc5eb5ae192d5a6cda10e068a95", + "reference": "512661046e403dc5eb5ae192d5a6cda10e068a95", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": "^5.3|^7.0" + }, + "conflict": { + "raven/raven": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^1.8.0", + "monolog/monolog": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7" + }, + "suggest": { + "ext-hash": "*", + "ext-json": "*", + "ext-mbstring": "*", + "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" + }, + "bin": [ + "bin/sentry" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9.x-dev" + } + }, + "autoload": { + "psr-0": { + "Raven_": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "David Cramer", + "email": "dcramer@gmail.com" + } + ], + "description": "A PHP client for Sentry (http://getsentry.com)", + "homepage": "http://getsentry.com", + "keywords": [ + "log", + "logging" + ], + "time": "2018-02-07T11:26:52+00:00" + }, + { + "name": "spomky-labs/otphp", + "version": "v9.0.3", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/otphp.git", + "reference": "26d19c0baff675fb64193e22b777aa9be63b8deb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/26d19c0baff675fb64193e22b777aa9be63b8deb", + "reference": "26d19c0baff675fb64193e22b777aa9be63b8deb", + "shasum": "" + }, + "require": { + "beberlei/assert": "^2.4", + "paragonie/constant_time_encoding": "^2.0", + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.0", + "satooshi/php-coveralls": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "OTPHP\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/otphp/contributors" + } + ], + "description": "A PHP library for generating one time passwords according to RFC 4226 (HOTP Algorithm) and the RFC 6238 (TOTP Algorithm) and compatible with Google Authenticator", + "homepage": "https://github.com/Spomky-Labs/otphp", + "keywords": [ + "FreeOTP", + "RFC 4226", + "RFC 6238", + "google authenticator", + "hotp", + "otp", + "totp" + ], + "time": "2017-11-23T08:44:21+00:00" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v6.0.2", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "412333372fb6c8ffb65496a2bbd7321af75733fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/412333372fb6c8ffb65496a2bbd7321af75733fc", + "reference": "412333372fb6c8ffb65496a2bbd7321af75733fc", + "shasum": "" + }, + "require": { + "egulias/email-validator": "~2.0", + "php": ">=7.0.0" + }, + "require-dev": { + "mockery/mockery": "~0.9.1", + "symfony/phpunit-bridge": "~3.3@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.0-dev" + } + }, + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "http://swiftmailer.symfony.com", + "keywords": [ + "email", + "mail", + "mailer" + ], + "time": "2017-09-30T22:39:41+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v3.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "8c39071ac9cc7e6d8dab1d556c990dc0d2cc3d30" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/8c39071ac9cc7e6d8dab1d556c990dc0d2cc3d30", + "reference": "8c39071ac9cc7e6d8dab1d556c990dc0d2cc3d30", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php70": "~1.6" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com", + "time": "2018-01-29T09:03:43+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2018-01-30T19:27:44+00:00" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "3532bfcd8f933a7816f3a0a59682fc404776600f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/3532bfcd8f933a7816f3a0a59682fc404776600f", + "reference": "3532bfcd8f933a7816f3a0a59682fc404776600f", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2018-01-30T19:27:44+00:00" + }, + { + "name": "symfony/process", + "version": "v4.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "e1712002d81de6f39f854bc5bbd9e9f4bb6345b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/e1712002d81de6f39f854bc5bbd9e9f4bb6345b4", + "reference": "e1712002d81de6f39f854bc5bbd9e9f4bb6345b4", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2018-01-29T09:06:29+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2018-01-29T19:49:41+00:00" + }, + { + "name": "yiisoft/yii2", + "version": "2.0.14", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-framework.git", + "reference": "1c9cf916b1394681c7d043e79e1522c33e5bc6c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/1c9cf916b1394681c7d043e79e1522c33e5bc6c1", + "reference": "1c9cf916b1394681c7d043e79e1522c33e5bc6c1", + "shasum": "" + }, + "require": { + "bower-asset/inputmask": "~3.2.2 | ~3.3.5", + "bower-asset/jquery": "3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", + "bower-asset/punycode": "1.3.*", + "bower-asset/yii2-pjax": "~2.0.1", + "cebe/markdown": "~1.0.0 | ~1.1.0", + "ext-ctype": "*", + "ext-mbstring": "*", + "ezyang/htmlpurifier": "~4.6", + "lib-pcre": "*", + "php": ">=5.4.0", + "yiisoft/yii2-composer": "~2.0.4" + }, + "bin": [ + "yii" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com", + "homepage": "http://www.yiiframework.com/", + "role": "Founder and project lead" + }, + { + "name": "Alexander Makarov", + "email": "sam@rmcreative.ru", + "homepage": "http://rmcreative.ru/", + "role": "Core framework development" + }, + { + "name": "Maurizio Domba", + "homepage": "http://mdomba.info/", + "role": "Core framework development" + }, + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Core framework development" + }, + { + "name": "Timur Ruziev", + "email": "resurtm@gmail.com", + "homepage": "http://resurtm.com/", + "role": "Core framework development" + }, + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com", + "role": "Core framework development" + }, + { + "name": "Dmitry Naumenko", + "email": "d.naumenko.a@gmail.com", + "role": "Core framework development" + }, + { + "name": "Boudewijn Vahrmeijer", + "email": "info@dynasource.eu", + "homepage": "http://dynasource.eu", + "role": "Core framework development" + } + ], + "description": "Yii PHP Framework Version 2", + "homepage": "http://www.yiiframework.com/", + "keywords": [ + "framework", + "yii2" + ], + "time": "2018-02-18T22:52:12+00:00" + }, + { + "name": "yiisoft/yii2-composer", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-composer.git", + "reference": "3f4923c2bde6caf3f5b88cc22fdd5770f52f8df2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/3f4923c2bde6caf3f5b88cc22fdd5770f52f8df2", + "reference": "3f4923c2bde6caf3f5b88cc22fdd5770f52f8df2", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0" + }, + "require-dev": { + "composer/composer": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "yii\\composer\\Plugin", + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\composer\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "description": "The composer plugin for Yii extension installer", + "keywords": [ + "composer", + "extension installer", + "yii2" + ], + "time": "2016-12-20T13:26:02+00:00" + }, + { + "name": "yiisoft/yii2-queue", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-queue.git", + "reference": "8c2b337f7d9ea934c2affdfc21c9fb387d0a0773" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-queue/zipball/8c2b337f7d9ea934c2affdfc21c9fb387d0a0773", + "reference": "8c2b337f7d9ea934c2affdfc21c9fb387d0a0773", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "symfony/process": "*", + "yiisoft/yii2": "~2.0.13" + }, + "require-dev": { + "enqueue/amqp-lib": "^0.8", + "jeremeamia/superclosure": "*", + "pda/pheanstalk": "*", + "php-amqplib/php-amqplib": "*", + "phpunit/phpunit": "~4.4", + "yiisoft/yii2-debug": "*", + "yiisoft/yii2-gii": "*", + "yiisoft/yii2-redis": "*" + }, + "suggest": { + "enqueue/amqp-lib": "Need for AMQP interop queue.", + "ext-gearman": "Need for Gearman queue.", + "ext-pcntl": "Need for process signals.", + "pda/pheanstalk": "Need for Beanstalk queue.", + "php-amqplib/php-amqplib": "Need for AMQP queue.", + "yiisoft/yii2-redis": "Need for Redis queue." + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\queue\\": "src", + "yii\\queue\\amqp\\": "src/drivers/amqp", + "yii\\queue\\amqp_interop\\": "src/drivers/amqp_interop", + "yii\\queue\\beanstalk\\": "src/drivers/beanstalk", + "yii\\queue\\db\\": "src/drivers/db", + "yii\\queue\\file\\": "src/drivers/file", + "yii\\queue\\gearman\\": "src/drivers/gearman", + "yii\\queue\\redis\\": "src/drivers/redis", + "yii\\queue\\sync\\": "src/drivers/sync" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Roman Zhuravlev", + "email": "zhuravljov@gmail.com" + } + ], + "description": "Yii2 Queue Extension which supported DB, Redis, RabbitMQ, Beanstalk and Gearman", + "keywords": [ + "async", + "beanstalk", + "db", + "gearman", + "gii", + "queue", + "rabbitmq", + "redis", + "yii" + ], + "time": "2017-12-26T17:16:14+00:00" + }, + { + "name": "yiisoft/yii2-redis", + "version": "2.0.7", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-redis.git", + "reference": "3891bb19f3ddc7ad744b439fe1d656ebb5b60a99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-redis/zipball/3891bb19f3ddc7ad744b439fe1d656ebb5b60a99", + "reference": "3891bb19f3ddc7ad744b439fe1d656ebb5b60a99", + "shasum": "" + }, + "require": { + "yiisoft/yii2": "~2.0.13" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\redis\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc" + } + ], + "description": "Redis Cache, Session and ActiveRecord for the Yii framework", + "keywords": [ + "active-record", + "cache", + "redis", + "session", + "yii2" + ], + "time": "2017-12-11T21:17:34+00:00" + }, + { + "name": "yiisoft/yii2-swiftmailer", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-swiftmailer.git", + "reference": "563570c9aa19ca47c1b22e3032983229378e9274" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-swiftmailer/zipball/563570c9aa19ca47c1b22e3032983229378e9274", + "reference": "563570c9aa19ca47c1b22e3032983229378e9274", + "shasum": "" + }, + "require": { + "swiftmailer/swiftmailer": "~6.0", + "yiisoft/yii2": "~2.0.4" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\swiftmailer\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com" + } + ], + "description": "The SwiftMailer integration for the Yii framework", + "keywords": [ + "email", + "mail", + "mailer", + "swift", + "swiftmailer", + "yii2" + ], + "time": "2017-08-04T10:48:17+00:00" + } + ], + "packages-dev": [ + { + "name": "behat/gherkin", + "version": "v4.4.5", + "source": { + "type": "git", + "url": "https://github.com/Behat/Gherkin.git", + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74", + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "~4.5|~5", + "symfony/phpunit-bridge": "~2.7|~3", + "symfony/yaml": "~2.3|~3" + }, + "suggest": { + "symfony/yaml": "If you want to parse features, represented in YAML files" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Gherkin": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Gherkin DSL parser for PHP 5.3", + "homepage": "http://behat.org/", + "keywords": [ + "BDD", + "Behat", + "Cucumber", + "DSL", + "gherkin", + "parser" + ], + "time": "2016-10-30T11:50:56+00:00" + }, + { + "name": "bower-asset/bootstrap", + "version": "v3.3.7", + "source": { + "type": "git", + "url": "https://github.com/twbs/bootstrap.git", + "reference": "0b9c4a4007c44201dce9a6cc1a38407005c26c86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twbs/bootstrap/zipball/0b9c4a4007c44201dce9a6cc1a38407005c26c86", + "reference": "0b9c4a4007c44201dce9a6cc1a38407005c26c86", + "shasum": null + }, + "require": { + "bower-asset/jquery": ">=1.9.1,<4.0" + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "codeception/codeception", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Codeception.git", + "reference": "43eade17a8cd68e9cde401e8585b09d11d41b12d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/43eade17a8cd68e9cde401e8585b09d11d41b12d", + "reference": "43eade17a8cd68e9cde401e8585b09d11d41b12d", + "shasum": "" + }, + "require": { + "behat/gherkin": "~4.4.0", + "codeception/stub": "^1.0", + "ext-json": "*", + "ext-mbstring": "*", + "facebook/webdriver": ">=1.1.3 <2.0", + "guzzlehttp/guzzle": ">=4.1.4 <7.0", + "guzzlehttp/psr7": "~1.0", + "php": ">=5.4.0 <8.0", + "phpunit/php-code-coverage": ">=2.2.4 <6.0", + "phpunit/phpunit": ">=4.8.28 <5.0.0 || >=5.6.3 <7.0", + "sebastian/comparator": ">1.1 <3.0", + "sebastian/diff": ">=1.4 <3.0", + "symfony/browser-kit": ">=2.7 <5.0", + "symfony/console": ">=2.7 <5.0", + "symfony/css-selector": ">=2.7 <5.0", + "symfony/dom-crawler": ">=2.7 <5.0", + "symfony/event-dispatcher": ">=2.7 <5.0", + "symfony/finder": ">=2.7 <5.0", + "symfony/yaml": ">=2.7 <5.0" + }, + "require-dev": { + "codeception/specify": "~0.3", + "facebook/graph-sdk": "~5.3", + "flow/jsonpath": "~0.2", + "monolog/monolog": "~1.8", + "pda/pheanstalk": "~3.0", + "php-amqplib/php-amqplib": "~2.4", + "predis/predis": "^1.0", + "squizlabs/php_codesniffer": "~2.0", + "symfony/process": ">=2.7 <5.0", + "vlucas/phpdotenv": "^2.4.0" + }, + "suggest": { + "aws/aws-sdk-php": "For using AWS Auth in REST module and Queue module", + "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests", + "codeception/specify": "BDD-style code blocks", + "codeception/verify": "BDD-style assertions", + "flow/jsonpath": "For using JSONPath in REST module", + "league/factory-muffin": "For DataFactory module", + "league/factory-muffin-faker": "For Faker support in DataFactory module", + "phpseclib/phpseclib": "for SFTP option in FTP Module", + "stecman/symfony-console-completion": "For BASH autocompletion", + "symfony/phpunit-bridge": "For phpunit-bridge support" + }, + "bin": [ + "codecept" + ], + "type": "library", + "extra": { + "branch-alias": [] + }, + "autoload": { + "psr-4": { + "Codeception\\": "src\\Codeception", + "Codeception\\Extension\\": "ext" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@mail.ua", + "homepage": "http://codegyre.com" + } + ], + "description": "BDD-style testing framework", + "homepage": "http://codeception.com/", + "keywords": [ + "BDD", + "TDD", + "acceptance testing", + "functional testing", + "unit testing" + ], + "time": "2018-01-27T22:47:33+00:00" + }, + { + "name": "codeception/specify", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Specify.git", + "reference": "1b1fe23f887aac647d0f7f5be3867b240690449d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Specify/zipball/1b1fe23f887aac647d0f7f5be3867b240690449d", + "reference": "1b1fe23f887aac647d0f7f5be3867b240690449d", + "shasum": "" + }, + "require": { + "myclabs/deep-copy": "~1.1", + "php": ">=7.0.0", + "phpunit/phpunit": "~6.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Codeception\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@codeception.com" + } + ], + "description": "BDD code blocks for PHPUnit and Codeception", + "time": "2017-11-20T23:40:31+00:00" + }, + { + "name": "codeception/stub", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Stub.git", + "reference": "95fb7a36b81890dd2e5163e7ab31310df6f1bb99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Stub/zipball/95fb7a36b81890dd2e5163e7ab31310df6f1bb99", + "reference": "95fb7a36b81890dd2e5163e7ab31310df6f1bb99", + "shasum": "" + }, + "require": { + "phpunit/phpunit-mock-objects": ">2.3 <7.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4.8 <8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Codeception\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", + "time": "2018-02-18T13:56:56+00:00" + }, + { + "name": "codeception/verify", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Verify.git", + "reference": "f45b39025b3f5cfd9a9d8fb992432885ff5380c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Verify/zipball/f45b39025b3f5cfd9a9d8fb992432885ff5380c1", + "reference": "f45b39025b3f5cfd9a9d8fb992432885ff5380c1", + "shasum": "" + }, + "require": { + "php": ">= 7.0", + "phpunit/phpunit": "> 6.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Codeception/function.php" + ], + "psr-4": { + "Codeception\\": "src\\Codeception" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@codeception.com" + } + ], + "description": "BDD assertion library for PHPUnit", + "time": "2017-11-12T01:51:59+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "^6.2.3", + "squizlabs/php_codesniffer": "^3.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2017-07-22T11:58:36+00:00" + }, + { + "name": "facebook/webdriver", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/facebook/php-webdriver.git", + "reference": "86b5ca2f67173c9d34340845dd690149c886a605" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/86b5ca2f67173c9d34340845dd690149c886a605", + "reference": "86b5ca2f67173c9d34340845dd690149c886a605", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-zip": "*", + "php": "^5.6 || ~7.0", + "symfony/process": "^2.8 || ^3.1 || ^4.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "guzzle/guzzle": "^3.4.1", + "php-coveralls/php-coveralls": "^1.0.2", + "php-mock/php-mock-phpunit": "^1.1", + "phpunit/phpunit": "^5.7", + "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", + "squizlabs/php_codesniffer": "^2.6", + "symfony/var-dumper": "^3.3 || ^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-community": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "Facebook\\WebDriver\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "A PHP client for Selenium WebDriver", + "homepage": "https://github.com/facebook/php-webdriver", + "keywords": [ + "facebook", + "php", + "selenium", + "webdriver" + ], + "time": "2017-11-15T11:08:09+00:00" + }, + { + "name": "flow/jsonpath", + "version": "0.3.4", + "source": { + "type": "git", + "url": "https://github.com/FlowCommunications/JSONPath.git", + "reference": "00aa9c361e4d0a210dd95f3c917a1e0dde3a957f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FlowCommunications/JSONPath/zipball/00aa9c361e4d0a210dd95f3c917a1e0dde3a957f", + "reference": "00aa9c361e4d0a210dd95f3c917a1e0dde3a957f", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "peekmo/jsonpath": "dev-master", + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Flow\\JSONPath": "src/", + "Flow\\JSONPath\\Test": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stephen Frank", + "email": "stephen@flowsa.com" + } + ], + "description": "JSONPath implementation for parsing, searching and flattening arrays", + "time": "2016-09-06T17:43:18+00:00" + }, + { + "name": "fzaninotto/faker", + "version": "v1.7.1", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", + "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "^4.0 || ^5.0", + "squizlabs/php_codesniffer": "^1.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2017-08-15T16:48:10+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/776503d3a8e85d4f9a1148614f95b7a608b046ad", + "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "1.3.3", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "time": "2016-01-20T08:20:44+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "1bac8c362b12f522fdd1f1fa3556284c91affa38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1bac8c362b12f522fdd1f1fa3556284c91affa38", + "reference": "1bac8c362b12f522fdd1f1fa3556284c91affa38", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "~2.0", + "lib-pcre": ">=7.0", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "~5.7|~6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", + "homepage": "http://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "time": "2017-10-06T16:20:43+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-10-19T19:58:43+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" + }, + { + "name": "php-mock/php-mock", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock.git", + "reference": "bfa2d17d64dbf129073a7ba2051a96ce52749570" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock/zipball/bfa2d17d64dbf129073a7ba2051a96ce52749570", + "reference": "bfa2d17d64dbf129073a7ba2051a96ce52749570", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpunit/php-text-template": "^1" + }, + "replace": { + "malkusch/php-mock": "*" + }, + "require-dev": { + "phpunit/phpunit": "^4|^5" + }, + "suggest": { + "php-mock/php-mock-mockery": "Allows using PHPMockery for Mockery integration", + "php-mock/php-mock-phpunit": "Allows integration into PHPUnit testcase with the trait PHPMock." + }, + "type": "library", + "autoload": { + "psr-4": { + "phpmock\\": [ + "classes/", + "tests/unit/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "PHP-Mock can mock built-in PHP functions (e.g. time()). PHP-Mock relies on PHP's namespace fallback policy. No further extension is needed.", + "homepage": "https://github.com/php-mock/php-mock", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "stub", + "test", + "test double" + ], + "time": "2015-11-11T22:37:09+00:00" + }, + { + "name": "php-mock/php-mock-integration", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock-integration.git", + "reference": "e83fb65dd20cd3cf250d554cbd4682b96b684f4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/e83fb65dd20cd3cf250d554cbd4682b96b684f4b", + "reference": "e83fb65dd20cd3cf250d554cbd4682b96b684f4b", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "php-mock/php-mock": "^1", + "phpunit/php-text-template": "^1" + }, + "require-dev": { + "phpunit/phpunit": "^4|^5" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpmock\\integration\\": "classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "Integration package for PHP-Mock", + "homepage": "https://github.com/php-mock/php-mock-integration", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "stub", + "test", + "test double" + ], + "time": "2015-10-26T21:21:42+00:00" + }, + { + "name": "php-mock/php-mock-mockery", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock-mockery.git", + "reference": "f3d67a36558c6d1fb912ed8ba786fffaf94e0755" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock-mockery/zipball/f3d67a36558c6d1fb912ed8ba786fffaf94e0755", + "reference": "f3d67a36558c6d1fb912ed8ba786fffaf94e0755", + "shasum": "" + }, + "require": { + "mockery/mockery": "^1", + "php": ">=5.6", + "php-mock/php-mock-integration": "^1" + }, + "require-dev": { + "phpunit/phpunit": "^4|^5" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpmock\\mockery\\": "classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "Mock built-in PHP functions (e.g. time()) with Mockery. This package relies on PHP's namespace fallback policy. No further extension is needed.", + "homepage": "https://github.com/php-mock/php-mock-mockery", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "mockery", + "stub", + "test", + "test double" + ], + "time": "2017-10-13T09:34:01+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "~1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2017-11-30T07:14:17+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-07-14T14:27:02+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.7.5", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/dfd6be44111a7c41c2e884a336cc4f461b3b2401", + "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2018-02-19T10:16:54+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/661f34d0bd3f1a7225ef491a70a020ad23a057a1", + "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.0", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-xdebug": "^2.5.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-12-06T09:29:45+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-11-27T05:48:46+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "6.5.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "3330ef26ade05359d006041316ed0fa9e8e3cefe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3330ef26ade05359d006041316ed0fa9e8e3cefe", + "reference": "3330ef26ade05359d006041316ed0fa9e8e3cefe", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.3", + "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^5.0.5", + "sebastian/comparator": "^2.1", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "^1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2018-02-01T05:57:37+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "5.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/33fd41a76e746b8fa96d00b49a23dadfa8334cdf", + "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.5", + "php": "^7.0", + "phpunit/php-text-template": "^1.2.1", + "sebastian/exporter": "^3.1" + }, + "conflict": { + "phpunit/phpunit": "<6.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2018-01-06T05:45:45+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/diff": "^2.0 || ^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-02-01T13:46:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-08-03T08:09:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2017-07-01T08:51:00+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2017-04-03T13:19:02+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v4.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "fee0fcd501304b1c3190f6293f650cceb738a353" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/fee0fcd501304b1c3190f6293f650cceb738a353", + "reference": "fee0fcd501304b1c3190f6293f650cceb738a353", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/dom-crawler": "~3.4|~4.0" + }, + "require-dev": { + "symfony/css-selector": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0" + }, + "suggest": { + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony BrowserKit Component", + "homepage": "https://symfony.com", + "time": "2018-01-03T07:38:00+00:00" + }, + { + "name": "symfony/console", + "version": "v4.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "36d5b41e7d4e1ccf0370f6babe966c08ef0a1488" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/36d5b41e7d4e1ccf0370f6babe966c08ef0a1488", + "reference": "36d5b41e7d4e1ccf0370f6babe966c08ef0a1488", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2018-01-29T09:06:29+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v4.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "f97600434e3141ef3cbb9ea42cf500fba88022b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/f97600434e3141ef3cbb9ea42cf500fba88022b7", + "reference": "f97600434e3141ef3cbb9ea42cf500fba88022b7", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2018-01-03T07:38:00+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v4.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "39b785e1cf28e9f21bb601a5d62c4992a8e8a290" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/39b785e1cf28e9f21bb601a5d62c4992a8e8a290", + "reference": "39b785e1cf28e9f21bb601a5d62c4992a8e8a290", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "~3.4|~4.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DomCrawler Component", + "homepage": "https://symfony.com", + "time": "2018-01-03T07:38:00+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "74d33aac36208c4d6757807d9f598f0133a3a4eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/74d33aac36208c4d6757807d9f598f0133a3a4eb", + "reference": "74d33aac36208c4d6757807d9f598f0133a3a4eb", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/stopwatch": "~3.4|~4.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2018-01-03T07:38:00+00:00" + }, + { + "name": "symfony/finder", + "version": "v4.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "8b08180f2b7ccb41062366b9ad91fbc4f1af8601" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/8b08180f2b7ccb41062366b9ad91fbc4f1af8601", + "reference": "8b08180f2b7ccb41062366b9ad91fbc4f1af8601", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2018-01-03T07:38:00+00:00" + }, + { + "name": "symfony/yaml", + "version": "v4.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "ffc60bda1d4a00ec0b32eeabf39dc017bf480028" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/ffc60bda1d4a00ec0b32eeabf39dc017bf480028", + "reference": "ffc60bda1d4a00ec0b32eeabf39dc017bf480028", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2018-01-21T19:06:11+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2017-04-07T12:08:54+00:00" + }, + { + "name": "yiisoft/yii2-bootstrap", + "version": "2.0.8", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-bootstrap.git", + "reference": "3f49c47924bb9fa5363c3fc7b073d954168cf438" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-bootstrap/zipball/3f49c47924bb9fa5363c3fc7b073d954168cf438", + "reference": "3f49c47924bb9fa5363c3fc7b073d954168cf438", + "shasum": "" + }, + "require": { + "bower-asset/bootstrap": "3.3.* | 3.2.* | 3.1.*", + "yiisoft/yii2": "~2.0.6" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\bootstrap\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com" + }, + { + "name": "Alexander Makarov", + "email": "sam@rmcreative.ru", + "homepage": "http://rmcreative.ru/" + }, + { + "name": "Antonio Ramirez", + "email": "amigo.cobos@gmail.com" + }, + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com", + "homepage": "http://www.yiiframework.com/" + } + ], + "description": "The Twitter Bootstrap extension for the Yii framework", + "keywords": [ + "bootstrap", + "yii2" + ], + "time": "2018-02-16T10:41:52+00:00" + }, + { + "name": "yiisoft/yii2-debug", + "version": "2.0.13", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-debug.git", + "reference": "b37f414959c2fafefb332020b42037cd17c1cb7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-debug/zipball/b37f414959c2fafefb332020b42037cd17c1cb7f", + "reference": "b37f414959c2fafefb332020b42037cd17c1cb7f", + "shasum": "" + }, + "require": { + "yiisoft/yii2": "~2.0.13", + "yiisoft/yii2-bootstrap": "~2.0.0" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\debug\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "description": "The debugger extension for the Yii framework", + "keywords": [ + "debug", + "debugger", + "yii2" + ], + "time": "2017-12-05T07:36:23+00:00" + }, + { + "name": "yiisoft/yii2-faker", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-faker.git", + "reference": "b88ca69ee226a3610b2c26c026c3203d7ac50f6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-faker/zipball/b88ca69ee226a3610b2c26c026c3203d7ac50f6c", + "reference": "b88ca69ee226a3610b2c26c026c3203d7ac50f6c", + "shasum": "" + }, + "require": { + "fzaninotto/faker": "*", + "yiisoft/yii2": "*" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\faker\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Mark Jebri", + "email": "mark.github@yandex.ru" + } + ], + "description": "Fixture generator. The Faker integration for the Yii framework.", + "keywords": [ + "Fixture", + "faker", + "yii2" + ], + "time": "2015-03-01T06:22:44+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "roave/security-advisories": 20, + "ely/amqp-controller": 20, + "ely/email-renderer": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.1" + }, + "platform-dev": [] +} diff --git a/console/controllers/CleanupController.php b/console/controllers/CleanupController.php index e2276e8..fc72371 100644 --- a/console/controllers/CleanupController.php +++ b/console/controllers/CleanupController.php @@ -4,12 +4,15 @@ namespace console\controllers; use common\models\AccountSession; use common\models\EmailActivation; use common\models\MinecraftAccessKey; +use common\models\OauthClient; +use common\tasks\ClearOauthSessions; use Yii; use yii\console\Controller; +use yii\console\ExitCode; class CleanupController extends Controller { - public function actionEmailKeys() { + public function actionEmailKeys(): int { $query = EmailActivation::find(); foreach ($this->getEmailActivationsDurationsMap() as $typeId => $expiration) { $query->orWhere([ @@ -24,10 +27,10 @@ class CleanupController extends Controller { $email->delete(); } - return self::EXIT_CODE_NORMAL; + return ExitCode::OK; } - public function actionMinecraftSessions() { + public function actionMinecraftSessions(): int { $expiredMinecraftSessionsQuery = MinecraftAccessKey::find() ->andWhere(['<', 'updated_at', time() - 1209600]); // 2 weeks @@ -36,7 +39,7 @@ class CleanupController extends Controller { $minecraftSession->delete(); } - return self::EXIT_CODE_NORMAL; + return ExitCode::OK; } /** @@ -47,7 +50,7 @@ class CleanupController extends Controller { * У модели AccountSession нет внешних связей, так что целевые записи * могут быть удалены без использования циклов. */ - public function actionWebSessions() { + public function actionWebSessions(): int { AccountSession::deleteAll([ 'OR', ['<', 'last_refreshed_at', time() - 7776000], // 90 days @@ -58,7 +61,24 @@ class CleanupController extends Controller { ], ]); - return self::EXIT_CODE_NORMAL; + return ExitCode::OK; + } + + public function actionOauthClients(): int { + /** @var OauthClient[] $clients */ + $clients = OauthClient::find() + ->onlyDeleted() + ->all(); + foreach ($clients as $client) { + if ($client->getSessions()->exists()) { + Yii::$app->queue->push(ClearOauthSessions::createFromOauthClient($client)); + continue; + } + + $client->delete(); + } + + return ExitCode::OK; } private function getEmailActivationsDurationsMap(): array { diff --git a/console/controllers/RbacController.php b/console/controllers/RbacController.php index 7dbc07b..0c615da 100644 --- a/console/controllers/RbacController.php +++ b/console/controllers/RbacController.php @@ -4,6 +4,7 @@ namespace console\controllers; use common\rbac\Permissions as P; use common\rbac\Roles as R; use common\rbac\rules\AccountOwner; +use common\rbac\rules\OauthClientOwner; use InvalidArgumentException; use Yii; use yii\base\ErrorException; @@ -30,6 +31,9 @@ class RbacController extends Controller { $permChangeAccountEmail = $this->createPermission(P::CHANGE_ACCOUNT_EMAIL); $permManageTwoFactorAuth = $this->createPermission(P::MANAGE_TWO_FACTOR_AUTH); $permBlockAccount = $this->createPermission(P::BLOCK_ACCOUNT); + $permCreateOauthClients = $this->createPermission(P::CREATE_OAUTH_CLIENTS); + $permViewOauthClients = $this->createPermission(P::VIEW_OAUTH_CLIENTS); + $permManageOauthClients = $this->createPermission(P::MANAGE_OAUTH_CLIENTS); $permCompleteOauthFlow = $this->createPermission(P::COMPLETE_OAUTH_FLOW, AccountOwner::class); $permObtainAccountEmail = $this->createPermission(P::OBTAIN_ACCOUNT_EMAIL); @@ -44,6 +48,8 @@ class RbacController extends Controller { $permChangeOwnAccountEmail = $this->createPermission(P::CHANGE_OWN_ACCOUNT_EMAIL, AccountOwner::class); $permManageOwnTwoFactorAuth = $this->createPermission(P::MANAGE_OWN_TWO_FACTOR_AUTH, AccountOwner::class); $permMinecraftServerSession = $this->createPermission(P::MINECRAFT_SERVER_SESSION); + $permViewOwnOauthClients = $this->createPermission(P::VIEW_OWN_OAUTH_CLIENTS, OauthClientOwner::class); + $permManageOwnOauthClients = $this->createPermission(P::MANAGE_OWN_OAUTH_CLIENTS, OauthClientOwner::class); $permEscapeIdentityVerification = $this->createPermission(P::ESCAPE_IDENTITY_VERIFICATION); @@ -56,6 +62,8 @@ class RbacController extends Controller { $authManager->addChild($permChangeOwnAccountPassword, $permChangeAccountPassword); $authManager->addChild($permChangeOwnAccountEmail, $permChangeAccountEmail); $authManager->addChild($permManageOwnTwoFactorAuth, $permManageTwoFactorAuth); + $authManager->addChild($permViewOwnOauthClients, $permViewOauthClients); + $authManager->addChild($permManageOwnOauthClients, $permManageOauthClients); $authManager->addChild($permObtainExtendedAccountInfo, $permObtainAccountInfo); $authManager->addChild($permObtainExtendedAccountInfo, $permObtainAccountEmail); @@ -68,6 +76,9 @@ class RbacController extends Controller { $authManager->addChild($roleAccountsWebUser, $permChangeOwnAccountEmail); $authManager->addChild($roleAccountsWebUser, $permManageOwnTwoFactorAuth); $authManager->addChild($roleAccountsWebUser, $permCompleteOauthFlow); + $authManager->addChild($roleAccountsWebUser, $permCreateOauthClients); + $authManager->addChild($roleAccountsWebUser, $permViewOwnOauthClients); + $authManager->addChild($roleAccountsWebUser, $permManageOwnOauthClients); } private function createRole(string $name): Role { diff --git a/console/migrations/m180224_132027_extend_oauth_clients_attributes.php b/console/migrations/m180224_132027_extend_oauth_clients_attributes.php new file mode 100644 index 0000000..65b774c --- /dev/null +++ b/console/migrations/m180224_132027_extend_oauth_clients_attributes.php @@ -0,0 +1,29 @@ +addColumn('{{%oauth_clients}}', 'type', $this->string()->notNull()->after('secret')); + $this->addColumn('{{%oauth_clients}}', 'website_url', $this->string()->null()->after('redirect_uri')); + $this->addColumn('{{%oauth_clients}}', 'minecraft_server_ip', $this->string()->null()->after('website_url')); + $this->addColumn('{{%oauth_clients}}', 'is_deleted', $this->boolean()->notNull()->defaultValue(false)->after('is_trusted')); + $this->update('{{%oauth_clients}}', [ + 'type' => 'application', + ]); + $this->addColumn('{{%oauth_sessions}}', 'created_at', $this->integer()->unsigned()->notNull()); + $this->update('{{%oauth_sessions}}', [ + 'created_at' => time(), + ]); + } + + public function safeDown() { + $this->dropColumn('{{%oauth_clients}}', 'type'); + $this->dropColumn('{{%oauth_clients}}', 'website_url'); + $this->dropColumn('{{%oauth_clients}}', 'minecraft_server_ip'); + $this->dropColumn('{{%oauth_clients}}', 'is_deleted'); + $this->dropColumn('{{%oauth_sessions}}', 'created_at'); + } + +} diff --git a/docker/cron/cleanup b/docker/cron/cleanup index 9c7e9c0..64077aa 100644 --- a/docker/cron/cleanup +++ b/docker/cron/cleanup @@ -2,3 +2,4 @@ 0 0 * * * root /usr/local/bin/php /var/www/html/yii cleanup/email-keys >/dev/null 2>&1 0 1 * * * root /usr/local/bin/php /var/www/html/yii cleanup/minecraft-sessions >/dev/null 2>&1 0 2 * * * root /usr/local/bin/php /var/www/html/yii cleanup/web-sessions >/dev/null 2>&1 +0 3 * * * root /usr/local/bin/php /var/www/html/yii cleanup/oauth-clients >/dev/null 2>&1 diff --git a/tests/codeception/api/_pages/OauthRoute.php b/tests/codeception/api/_pages/OauthRoute.php index c700211..11b5e18 100644 --- a/tests/codeception/api/_pages/OauthRoute.php +++ b/tests/codeception/api/_pages/OauthRoute.php @@ -3,16 +3,40 @@ namespace tests\codeception\api\_pages; class OauthRoute extends BasePage { - public function validate($queryParams) { + public function validate(array $queryParams): void { $this->getActor()->sendGET('/oauth2/v1/validate', $queryParams); } - public function complete($queryParams = [], $postParams = []) { + public function complete(array $queryParams = [], array $postParams = []): void { $this->getActor()->sendPOST('/oauth2/v1/complete?' . http_build_query($queryParams), $postParams); } - public function issueToken($postParams = []) { + public function issueToken(array $postParams = []): void { $this->getActor()->sendPOST('/oauth2/v1/token', $postParams); } + public function createClient(string $type, array $postParams): void { + $this->getActor()->sendPOST('/v1/oauth2/' . $type, $postParams); + } + + public function updateClient(string $clientId, array $params): void { + $this->getActor()->sendPUT('/v1/oauth2/' . $clientId, $params); + } + + public function deleteClient(string $clientId): void { + $this->getActor()->sendDELETE('/v1/oauth2/' . $clientId); + } + + public function resetClient(string $clientId, bool $regenerateSecret = false): void { + $this->getActor()->sendPOST("/v1/oauth2/$clientId/reset" . ($regenerateSecret ? '?regenerateSecret' : '')); + } + + public function getClient(string $clientId): void { + $this->getActor()->sendGET("/v1/oauth2/$clientId"); + } + + public function getPerAccount(int $accountId): void { + $this->getActor()->sendGET("/v1/accounts/$accountId/oauth2/clients"); + } + } diff --git a/tests/codeception/api/codeception.yml b/tests/codeception/api/codeception.yml index 92cfc34..406850d 100644 --- a/tests/codeception/api/codeception.yml +++ b/tests/codeception/api/codeception.yml @@ -22,7 +22,6 @@ coverage: - ../../../api/* exclude: - ../../../api/config/* - - ../../../api/mails/* - ../../../api/web/* - ../../../api/runtime/* c3url: 'http://localhost/api/web/index.php' diff --git a/tests/codeception/api/functional/oauth/CreateClientCest.php b/tests/codeception/api/functional/oauth/CreateClientCest.php new file mode 100644 index 0000000..4f6cc99 --- /dev/null +++ b/tests/codeception/api/functional/oauth/CreateClientCest.php @@ -0,0 +1,92 @@ +route = new OauthRoute($I); + } + + public function testCreateApplicationWithWrongParams(FunctionalTester $I) { + $I->amAuthenticated('admin'); + + $this->route->createClient('application', []); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'name' => 'error.name_required', + 'redirectUri' => 'error.redirectUri_required', + ], + ]); + + $this->route->createClient('application', [ + 'name' => 'my test oauth client', + 'redirectUri' => 'localhost', + ]); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'redirectUri' => 'error.redirectUri_invalid', + ], + ]); + } + + public function testCreateApplication(FunctionalTester $I) { + $I->amAuthenticated('admin'); + $this->route->createClient('application', [ + 'name' => 'My admin application', + 'description' => 'Application description.', + 'redirectUri' => 'http://some-site.com/oauth/ely', + 'websiteUrl' => 'http://some-site.com', + ]); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'success' => true, + 'data' => [ + 'clientId' => 'my-admin-application', + 'name' => 'My admin application', + 'description' => 'Application description.', + 'websiteUrl' => 'http://some-site.com', + 'countUsers' => 0, + 'redirectUri' => 'http://some-site.com/oauth/ely', + ], + ]); + $I->canSeeResponseJsonMatchesJsonPath('$.data.clientSecret'); + $I->canSeeResponseJsonMatchesJsonPath('$.data.createdAt'); + } + + public function testCreateMinecraftServer(FunctionalTester $I) { + $I->amAuthenticated('admin'); + $this->route->createClient('minecraft-server', [ + 'name' => 'My amazing server', + 'websiteUrl' => 'http://some-site.com', + 'minecraftServerIp' => 'hypixel.com:25565', + ]); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'success' => true, + 'data' => [ + 'clientId' => 'my-amazing-server', + 'name' => 'My amazing server', + 'websiteUrl' => 'http://some-site.com', + 'countUsers' => 0, + 'minecraftServerIp' => 'hypixel.com:25565', + ], + ]); + $I->canSeeResponseJsonMatchesJsonPath('$.data.clientSecret'); + $I->canSeeResponseJsonMatchesJsonPath('$.data.createdAt'); + } + +} diff --git a/tests/codeception/api/functional/oauth/DeleteClientCest.php b/tests/codeception/api/functional/oauth/DeleteClientCest.php new file mode 100644 index 0000000..7a9ba67 --- /dev/null +++ b/tests/codeception/api/functional/oauth/DeleteClientCest.php @@ -0,0 +1,28 @@ +route = new OauthRoute($I); + } + + public function testDelete(FunctionalTester $I) { + $I->amAuthenticated('TwoOauthClients'); + $this->route->deleteClient('first-test-oauth-client'); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'success' => true, + ]); + } + +} diff --git a/tests/codeception/api/functional/oauth/GetClientsCest.php b/tests/codeception/api/functional/oauth/GetClientsCest.php new file mode 100644 index 0000000..300d969 --- /dev/null +++ b/tests/codeception/api/functional/oauth/GetClientsCest.php @@ -0,0 +1,89 @@ +route = new OauthRoute($I); + } + + public function testGet(FunctionalTester $I) { + $I->amAuthenticated('admin'); + $this->route->getClient('admin-oauth-client'); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'clientId' => 'admin-oauth-client', + 'clientSecret' => 'FKyO71iCIlv4YM2IHlLbhsvYoIJScUzTZt1kEK7DQLXXYISLDvURVXK32Q58sHWS', + 'type' => 'application', + 'name' => 'Admin\'s oauth client', + 'description' => 'Personal oauth client', + 'redirectUri' => 'http://some-site.com/oauth/ely', + 'websiteUrl' => '', + 'createdAt' => 1519254133, + ]); + } + + public function testGetNotOwn(FunctionalTester $I) { + $I->amAuthenticated('admin'); + $this->route->getClient('another-test-oauth-client'); + $I->canSeeResponseCodeIs(403); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'name' => 'Forbidden', + 'status' => 403, + 'message' => 'You are not allowed to perform this action.', + ]); + } + + public function testGetAllPerAccountList(FunctionalTester $I) { + $I->amAuthenticated('TwoOauthClients'); + $this->route->getPerAccount(14); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + [ + 'clientId' => 'first-test-oauth-client', + 'clientSecret' => 'Zt1kEK7DQLXXYISLDvURVXK32Q58sHWSFKyO71iCIlv4YM2IHlLbhsvYoIJScUzT', + 'type' => 'application', + 'name' => 'First test oauth client', + 'description' => 'Some description to the first oauth client', + 'redirectUri' => 'http://some-site-1.com/oauth/ely', + 'websiteUrl' => '', + 'countUsers' => 0, + 'createdAt' => 1519487434, + ], + [ + 'clientId' => 'another-test-oauth-client', + 'clientSecret' => 'URVXK32Q58sHWSFKyO71iCIlv4YM2Zt1kEK7DQLXXYISLDvIHlLbhsvYoIJScUzT', + 'type' => 'minecraft-server', + 'name' => 'Another test oauth client', + 'websiteUrl' => '', + 'minecraftServerIp' => '136.243.88.97:25565', + 'countUsers' => 0, + 'createdAt' => 1519487472, + ], + ]); + } + + public function testGetAllPerNotOwnAccount(FunctionalTester $I) { + $I->amAuthenticated('TwoOauthClients'); + $this->route->getPerAccount(1); + $I->canSeeResponseCodeIs(403); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'name' => 'Forbidden', + 'status' => 403, + 'message' => 'You are not allowed to perform this action.', + ]); + } + +} diff --git a/tests/codeception/api/functional/oauth/ResetClientCest.php b/tests/codeception/api/functional/oauth/ResetClientCest.php new file mode 100644 index 0000000..a5e7cea --- /dev/null +++ b/tests/codeception/api/functional/oauth/ResetClientCest.php @@ -0,0 +1,60 @@ +route = new OauthRoute($I); + } + + public function testReset(FunctionalTester $I) { + $I->amAuthenticated('TwoOauthClients'); + $this->route->resetClient('first-test-oauth-client'); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'success' => true, + 'data' => [ + 'clientId' => 'first-test-oauth-client', + 'clientSecret' => 'Zt1kEK7DQLXXYISLDvURVXK32Q58sHWSFKyO71iCIlv4YM2IHlLbhsvYoIJScUzT', + 'name' => 'First test oauth client', + 'description' => 'Some description to the first oauth client', + 'redirectUri' => 'http://some-site-1.com/oauth/ely', + 'websiteUrl' => '', + 'countUsers' => 0, + 'createdAt' => 1519487434, + ], + ]); + } + + public function testResetWithSecretChanging(FunctionalTester $I) { + $I->amAuthenticated('TwoOauthClients'); + $this->route->resetClient('first-test-oauth-client', true); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'success' => true, + 'data' => [ + 'clientId' => 'first-test-oauth-client', + 'name' => 'First test oauth client', + 'description' => 'Some description to the first oauth client', + 'redirectUri' => 'http://some-site-1.com/oauth/ely', + 'websiteUrl' => '', + 'countUsers' => 0, + 'createdAt' => 1519487434, + ], + ]); + $I->canSeeResponseJsonMatchesJsonPath('$.data.clientSecret'); + $secret = $I->grabDataFromResponseByJsonPath('$.data.clientSecret')[0]; + $I->assertNotEquals('Zt1kEK7DQLXXYISLDvURVXK32Q58sHWSFKyO71iCIlv4YM2IHlLbhsvYoIJScUzT', $secret); + } + +} diff --git a/tests/codeception/api/functional/oauth/UpdateClientCest.php b/tests/codeception/api/functional/oauth/UpdateClientCest.php new file mode 100644 index 0000000..0dc72f8 --- /dev/null +++ b/tests/codeception/api/functional/oauth/UpdateClientCest.php @@ -0,0 +1,65 @@ +route = new OauthRoute($I); + } + + public function testUpdateApplication(FunctionalTester $I) { + $I->amAuthenticated('TwoOauthClients'); + $this->route->updateClient('first-test-oauth-client', [ + 'name' => 'Updated name', + 'description' => 'Updated description.', + 'redirectUri' => 'http://new-site.com/oauth/ely', + 'websiteUrl' => 'http://new-site.com', + ]); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'success' => true, + 'data' => [ + 'clientId' => 'first-test-oauth-client', + 'clientSecret' => 'Zt1kEK7DQLXXYISLDvURVXK32Q58sHWSFKyO71iCIlv4YM2IHlLbhsvYoIJScUzT', + 'name' => 'Updated name', + 'description' => 'Updated description.', + 'redirectUri' => 'http://new-site.com/oauth/ely', + 'websiteUrl' => 'http://new-site.com', + 'createdAt' => 1519487434, + 'countUsers' => 0, + ], + ]); + } + + public function testUpdateMinecraftServer(FunctionalTester $I) { + $I->amAuthenticated('TwoOauthClients'); + $this->route->updateClient('another-test-oauth-client', [ + 'name' => 'Updated server name', + 'websiteUrl' => 'http://new-site.com', + 'minecraftServerIp' => 'hypixel.com:25565', + ]); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'success' => true, + 'data' => [ + 'clientId' => 'another-test-oauth-client', + 'clientSecret' => 'URVXK32Q58sHWSFKyO71iCIlv4YM2Zt1kEK7DQLXXYISLDvIHlLbhsvYoIJScUzT', + 'name' => 'Updated server name', + 'websiteUrl' => 'http://new-site.com', + 'minecraftServerIp' => 'hypixel.com:25565', + 'createdAt' => 1519487472, + ], + ]); + } + +} diff --git a/tests/codeception/api/unit/modules/oauth/models/ApplicationTypeTest.php b/tests/codeception/api/unit/modules/oauth/models/ApplicationTypeTest.php new file mode 100644 index 0000000..1eed2c4 --- /dev/null +++ b/tests/codeception/api/unit/modules/oauth/models/ApplicationTypeTest.php @@ -0,0 +1,27 @@ +name = 'Application name'; + $model->websiteUrl = 'http://example.com'; + $model->redirectUri = 'http://example.com/oauth/ely'; + $model->description = 'Application description.'; + + $client = new OauthClient(); + + $model->applyToClient($client); + + $this->assertSame('Application name', $client->name); + $this->assertSame('Application description.', $client->description); + $this->assertSame('http://example.com/oauth/ely', $client->redirect_uri); + $this->assertSame('http://example.com', $client->website_url); + } + +} diff --git a/tests/codeception/api/unit/modules/oauth/models/BaseOauthClientTypeTest.php b/tests/codeception/api/unit/modules/oauth/models/BaseOauthClientTypeTest.php new file mode 100644 index 0000000..b119012 --- /dev/null +++ b/tests/codeception/api/unit/modules/oauth/models/BaseOauthClientTypeTest.php @@ -0,0 +1,24 @@ +makePartial(); + $form->name = 'Application name'; + $form->websiteUrl = 'http://example.com'; + + $form->applyToClient($client); + $this->assertSame('Application name', $client->name); + $this->assertSame('http://example.com', $client->website_url); + } + +} diff --git a/tests/codeception/api/unit/modules/oauth/models/MinecraftServerTypeTest.php b/tests/codeception/api/unit/modules/oauth/models/MinecraftServerTypeTest.php new file mode 100644 index 0000000..8354462 --- /dev/null +++ b/tests/codeception/api/unit/modules/oauth/models/MinecraftServerTypeTest.php @@ -0,0 +1,25 @@ +name = 'Server name'; + $model->websiteUrl = 'http://example.com'; + $model->minecraftServerIp = 'localhost:12345'; + + $client = new OauthClient(); + + $model->applyToClient($client); + + $this->assertSame('Server name', $client->name); + $this->assertSame('http://example.com', $client->website_url); + $this->assertSame('localhost:12345', $client->minecraft_server_ip); + } + +} diff --git a/tests/codeception/api/unit/modules/oauth/models/OauthClientFormFactoryTest.php b/tests/codeception/api/unit/modules/oauth/models/OauthClientFormFactoryTest.php new file mode 100644 index 0000000..642c788 --- /dev/null +++ b/tests/codeception/api/unit/modules/oauth/models/OauthClientFormFactoryTest.php @@ -0,0 +1,49 @@ +type = OauthClient::TYPE_APPLICATION; + $client->name = 'Application name'; + $client->description = 'Application description.'; + $client->website_url = 'http://example.com'; + $client->redirect_uri = 'http://example.com/oauth/ely'; + /** @var ApplicationType $requestForm */ + $requestForm = OauthClientFormFactory::create($client); + $this->assertInstanceOf(ApplicationType::class, $requestForm); + $this->assertSame('Application name', $requestForm->name); + $this->assertSame('Application description.', $requestForm->description); + $this->assertSame('http://example.com', $requestForm->websiteUrl); + $this->assertSame('http://example.com/oauth/ely', $requestForm->redirectUri); + + $client = new OauthClient(); + $client->type = OauthClient::TYPE_MINECRAFT_SERVER; + $client->name = 'Server name'; + $client->website_url = 'http://example.com'; + $client->minecraft_server_ip = 'localhost:12345'; + /** @var MinecraftServerType $requestForm */ + $requestForm = OauthClientFormFactory::create($client); + $this->assertInstanceOf(MinecraftServerType::class, $requestForm); + $this->assertSame('Server name', $requestForm->name); + $this->assertSame('http://example.com', $requestForm->websiteUrl); + $this->assertSame('localhost:12345', $requestForm->minecraftServerIp); + } + + /** + * @expectedException \api\modules\oauth\exceptions\UnsupportedOauthClientType + */ + public function testCreateUnknownType() { + $client = new OauthClient(); + $client->type = 'unknown-type'; + OauthClientFormFactory::create($client); + } + +} diff --git a/tests/codeception/api/unit/modules/oauth/models/OauthClientFormTest.php b/tests/codeception/api/unit/modules/oauth/models/OauthClientFormTest.php new file mode 100644 index 0000000..f9fc446 --- /dev/null +++ b/tests/codeception/api/unit/modules/oauth/models/OauthClientFormTest.php @@ -0,0 +1,140 @@ +shouldReceive('save')->andReturn(true); + $client->account_id = 1; + $client->type = OauthClient::TYPE_APPLICATION; + $client->name = 'Test application'; + + /** @var OauthClientForm|\Mockery\MockInterface $form */ + $form = mock(OauthClientForm::class . '[isClientExists]', [$client]); + $form->shouldAllowMockingProtectedMethods(); + $form->shouldReceive('isClientExists') + ->times(3) + ->andReturnValues([true, true, false]); + + /** @var OauthClientTypeForm|\Mockery\MockInterface $requestType */ + $requestType = mock(OauthClientTypeForm::class); + $requestType->shouldReceive('validate')->once()->andReturn(true); + $requestType->shouldReceive('applyToClient')->once()->withArgs([$client]); + + $this->assertTrue($form->save($requestType)); + $this->assertSame('test-application2', $client->id); + $this->assertNotNull($client->secret); + $this->assertSame(64, mb_strlen($client->secret)); + } + + public function testSaveUpdateExistsModel() { + /** @var OauthClient|\Mockery\MockInterface $client */ + $client = mock(OauthClient::class . '[save]'); + $client->shouldReceive('save')->andReturn(true); + $client->setIsNewRecord(false); + $client->id = 'application-id'; + $client->secret = 'application_secret'; + $client->account_id = 1; + $client->type = OauthClient::TYPE_APPLICATION; + $client->name = 'Application name'; + $client->description = 'Application description'; + $client->redirect_uri = 'http://example.com/oauth/ely'; + $client->website_url = 'http://example.com'; + + /** @var OauthClientForm|\Mockery\MockInterface $form */ + $form = mock(OauthClientForm::class . '[isClientExists]', [$client]); + $form->shouldAllowMockingProtectedMethods(); + $form->shouldReceive('isClientExists')->andReturn(false); + + $request = new class implements OauthClientTypeForm { + + public function load($data): bool { + return true; + } + + public function validate(): bool { + return true; + } + + public function getValidationErrors(): array { + return []; + } + + public function applyToClient(OauthClient $client): void { + $client->name = 'New name'; + $client->description = 'New description.'; + } + + }; + + $this->assertTrue($form->save($request)); + $this->assertSame('application-id', $client->id); + $this->assertSame('application_secret', $client->secret); + $this->assertSame('New name', $client->name); + $this->assertSame('New description.', $client->description); + $this->assertSame('http://example.com/oauth/ely', $client->redirect_uri); + $this->assertSame('http://example.com', $client->website_url); + } + + public function testDelete() { + /** @var OauthClient|\Mockery\MockInterface $client */ + $client = mock(OauthClient::class . '[save]'); + $client->id = 'mocked-id'; + $client->type = OauthClient::TYPE_APPLICATION; + $client->shouldReceive('save')->andReturn(true); + + $form = new OauthClientForm($client); + $this->assertTrue($form->delete()); + $this->assertTrue($form->getClient()->is_deleted); + /** @var ClearOauthSessions $job */ + $job = $this->tester->grabLastQueuedJob(); + $this->assertInstanceOf(ClearOauthSessions::class, $job); + $this->assertSame('mocked-id', $job->clientId); + $this->assertNull($job->notSince); + } + + public function testReset() { + /** @var OauthClient|\Mockery\MockInterface $client */ + $client = mock(OauthClient::class . '[save]'); + $client->id = 'mocked-id'; + $client->secret = 'initial_secret'; + $client->type = OauthClient::TYPE_APPLICATION; + $client->shouldReceive('save')->andReturn(true); + + $form = new OauthClientForm($client); + $this->assertTrue($form->reset()); + $this->assertSame('initial_secret', $form->getClient()->secret); + /** @var ClearOauthSessions $job */ + $job = $this->tester->grabLastQueuedJob(); + $this->assertInstanceOf(ClearOauthSessions::class, $job); + $this->assertSame('mocked-id', $job->clientId); + $this->assertEquals(time(), $job->notSince, '', 2); + } + + public function testResetWithSecret() { + /** @var OauthClient|\Mockery\MockInterface $client */ + $client = mock(OauthClient::class . '[save]'); + $client->id = 'mocked-id'; + $client->secret = 'initial_secret'; + $client->type = OauthClient::TYPE_APPLICATION; + $client->shouldReceive('save')->andReturn(true); + + $form = new OauthClientForm($client); + $this->assertTrue($form->reset(true)); + $this->assertNotSame('initial_secret', $form->getClient()->secret); + /** @var ClearOauthSessions $job */ + $job = $this->tester->grabLastQueuedJob(); + $this->assertInstanceOf(ClearOauthSessions::class, $job); + $this->assertSame('mocked-id', $job->clientId); + $this->assertEquals(time(), $job->notSince, '', 2); + } + +} diff --git a/tests/codeception/common/fixtures/data/accounts.php b/tests/codeception/common/fixtures/data/accounts.php index 382db97..4b8bfed 100644 --- a/tests/codeception/common/fixtures/data/accounts.php +++ b/tests/codeception/common/fixtures/data/accounts.php @@ -186,4 +186,20 @@ return [ 'updated_at' => 1485124685, 'password_changed_at' => 1485124685, ], + 'account-with-two-oauth-clients' => [ + 'id' => 14, + 'uuid' => '1b946267-b1a9-4409-ae83-94f84a329883', + 'username' => 'TwoOauthClients', + 'email' => 'oauth2-two@gmail.com', + 'password_hash' => '$2y$13$2rYkap5T6jG8z/mMK8a3Ou6aZxJcmAaTha6FEuujvHEmybSHRzW5e', # password_0 + 'password_hash_strategy' => \common\models\Account::PASS_HASH_STRATEGY_YII2, + 'lang' => 'ru', + 'status' => \common\models\Account::STATUS_ACTIVE, + 'rules_agreement_version' => \common\LATEST_RULES_VERSION, + 'otp_secret' => null, + 'is_otp_enabled' => false, + 'created_at' => 1519487320, + 'updated_at' => 1519487320, + 'password_changed_at' => 1519487320, + ], ]; diff --git a/tests/codeception/common/fixtures/data/oauth-clients.php b/tests/codeception/common/fixtures/data/oauth-clients.php index e8c2dfc..df4da2c 100644 --- a/tests/codeception/common/fixtures/data/oauth-clients.php +++ b/tests/codeception/common/fixtures/data/oauth-clients.php @@ -3,51 +3,141 @@ return [ 'ely' => [ 'id' => 'ely', 'secret' => 'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM', + 'type' => 'application', 'name' => 'Ely.by', 'description' => 'Всем знакомое елуби', 'redirect_uri' => 'http://ely.by', + 'website_url' => '', + 'minecraft_server_ip' => '', 'account_id' => null, 'is_trusted' => 0, + 'is_deleted' => 0, 'created_at' => 1455309271, ], 'tlauncher' => [ 'id' => 'tlauncher', 'secret' => 'HsX-xXzdGiz3mcsqeEvrKHF47sqiaX94', + 'type' => 'application', 'name' => 'TLauncher', 'description' => 'Лучший альтернативный лаунчер для Minecraft с большим количеством версий и их модификаций, а также возмоностью входа как с лицензионным аккаунтом, так и без него.', 'redirect_uri' => '', + 'website_url' => '', + 'minecraft_server_ip' => '', 'account_id' => null, 'is_trusted' => 0, + 'is_deleted' => 0, 'created_at' => 1455318468, ], 'test1' => [ 'id' => 'test1', 'secret' => 'eEvrKHF47sqiaX94HsX-xXzdGiz3mcsq', + 'type' => 'application', 'name' => 'Test1', 'description' => 'Some description', 'redirect_uri' => 'http://test1.net', + 'website_url' => '', + 'minecraft_server_ip' => '', 'account_id' => null, 'is_trusted' => 0, + 'is_deleted' => 0, 'created_at' => 1479937982, ], 'trustedClient' => [ 'id' => 'trusted-client', 'secret' => 'tXBbyvMcyaOgHMOAXBpN2EC7uFoJAaL9', + 'type' => 'application', 'name' => 'Trusted client', 'description' => 'Это клиент, которому мы доверяем', 'redirect_uri' => null, + 'website_url' => '', + 'minecraft_server_ip' => '', 'account_id' => null, 'is_trusted' => 1, + 'is_deleted' => 0, 'created_at' => 1482922663, ], 'defaultClient' => [ 'id' => 'default-client', 'secret' => 'AzWRy7ZjS1yRQUk2vRBDic8fprOKDB1W', + 'type' => 'application', 'name' => 'Default client', 'description' => 'Это обычный клиент, каких может быть много', 'redirect_uri' => null, + 'website_url' => '', + 'minecraft_server_ip' => '', 'account_id' => null, 'is_trusted' => 0, + 'is_deleted' => 0, 'created_at' => 1482922711, ], + 'admin_oauth_client' => [ + 'id' => 'admin-oauth-client', + 'secret' => 'FKyO71iCIlv4YM2IHlLbhsvYoIJScUzTZt1kEK7DQLXXYISLDvURVXK32Q58sHWS', + 'type' => 'application', + 'name' => 'Admin\'s oauth client', + 'description' => 'Personal oauth client', + 'redirect_uri' => 'http://some-site.com/oauth/ely', + 'website_url' => '', + 'minecraft_server_ip' => '', + 'account_id' => 1, + 'is_trusted' => 0, + 'is_deleted' => 0, + 'created_at' => 1519254133, + ], + 'first_test_oauth_client' => [ + 'id' => 'first-test-oauth-client', + 'secret' => 'Zt1kEK7DQLXXYISLDvURVXK32Q58sHWSFKyO71iCIlv4YM2IHlLbhsvYoIJScUzT', + 'type' => 'application', + 'name' => 'First test oauth client', + 'description' => 'Some description to the first oauth client', + 'redirect_uri' => 'http://some-site-1.com/oauth/ely', + 'website_url' => '', + 'minecraft_server_ip' => '', + 'account_id' => 14, + 'is_trusted' => 0, + 'is_deleted' => 0, + 'created_at' => 1519487434, + ], + 'another_test_oauth_client' => [ + 'id' => 'another-test-oauth-client', + 'secret' => 'URVXK32Q58sHWSFKyO71iCIlv4YM2Zt1kEK7DQLXXYISLDvIHlLbhsvYoIJScUzT', + 'type' => 'minecraft-server', + 'name' => 'Another test oauth client', + 'description' => null, + 'redirect_uri' => null, + 'website_url' => '', + 'minecraft_server_ip' => '136.243.88.97:25565', + 'account_id' => 14, + 'is_trusted' => 0, + 'is_deleted' => 0, + 'created_at' => 1519487472, + ], + 'deleted_oauth_client' => [ + 'id' => 'deleted-oauth-client', + 'secret' => 'YISLDvIHlLbhsvYoIJScUzTURVXK32Q58sHWSFKyO71iCIlv4YM2Zt1kEK7DQLXX', + 'type' => 'application', + 'name' => 'I was deleted :(', + 'description' => null, + 'redirect_uri' => 'http://not-exists-site.com/oauth/ely', + 'website_url' => '', + 'minecraft_server_ip' => null, + 'account_id' => 1, + 'is_trusted' => 0, + 'is_deleted' => 1, + 'created_at' => 1519504563, + ], + 'deleted_oauth_client_with_sessions' => [ + 'id' => 'deleted-oauth-client-with-sessions', + 'secret' => 'EK7DQLXXYISLDvIHlLbhsvYoIJScUzTURVXK32Q58sHWSFKyO71iCIlv4YM2Zt1k', + 'type' => 'application', + 'name' => 'I still have some sessions ^_^', + 'description' => null, + 'redirect_uri' => 'http://not-exists-site.com/oauth/ely', + 'website_url' => '', + 'minecraft_server_ip' => null, + 'account_id' => 1, + 'is_trusted' => 0, + 'is_deleted' => 1, + 'created_at' => 1519507190, + ], ]; diff --git a/tests/codeception/common/fixtures/data/oauth-sessions.php b/tests/codeception/common/fixtures/data/oauth-sessions.php index 69e0536..678833b 100644 --- a/tests/codeception/common/fixtures/data/oauth-sessions.php +++ b/tests/codeception/common/fixtures/data/oauth-sessions.php @@ -6,6 +6,7 @@ return [ 'owner_id' => 1, 'client_id' => 'test1', 'client_redirect_uri' => 'http://test1.net/oauth', + 'created_at' => 1479944472, ], 'banned-account-session' => [ 'id' => 2, @@ -13,5 +14,22 @@ return [ 'owner_id' => 10, 'client_id' => 'test1', 'client_redirect_uri' => 'http://test1.net/oauth', + 'created_at' => 1481421663, + ], + 'deleted-client-session' => [ + 'id' => 3, + 'owner_type' => 'user', + 'owner_id' => 1, + 'client_id' => 'deleted-oauth-client-with-sessions', + 'client_redirect_uri' => 'http://not-exists-site.com/oauth/ely', + 'created_at' => 1519510065, + ], + 'actual-deleted-client-session' => [ + 'id' => 4, + 'owner_type' => 'user', + 'owner_id' => 2, + 'client_id' => 'deleted-oauth-client-with-sessions', + 'client_redirect_uri' => 'http://not-exists-site.com/oauth/ely', + 'created_at' => 1519511568, ], ]; diff --git a/tests/codeception/common/unit/models/OauthClientQueryTest.php b/tests/codeception/common/unit/models/OauthClientQueryTest.php new file mode 100644 index 0000000..e4680e6 --- /dev/null +++ b/tests/codeception/common/unit/models/OauthClientQueryTest.php @@ -0,0 +1,42 @@ + OauthClientFixture::class, + ]; + } + + public function testDefaultHideDeletedEntries() { + /** @var OauthClient[] $clients */ + $clients = OauthClient::find()->all(); + $this->assertEmpty(array_filter($clients, function(OauthClient $client) { + return (bool)$client->is_deleted === true; + })); + $this->assertNull(OauthClient::findOne('deleted-oauth-client')); + } + + public function testAllowFindDeletedEntries() { + /** @var OauthClient[] $clients */ + $clients = OauthClient::find()->includeDeleted()->all(); + $this->assertNotEmpty(array_filter($clients, function(OauthClient $client) { + return (bool)$client->is_deleted === true; + })); + $client = OauthClient::find() + ->includeDeleted() + ->andWhere(['id' => 'deleted-oauth-client']) + ->one(); + $this->assertInstanceOf(OauthClient::class, $client); + $deletedClients = OauthClient::find()->onlyDeleted()->all(); + $this->assertEmpty(array_filter($deletedClients, function(OauthClient $client) { + return (bool)$client->is_deleted === false; + })); + } + +} diff --git a/tests/codeception/common/unit/rbac/rules/AccountOwnerTest.php b/tests/codeception/common/unit/rbac/rules/AccountOwnerTest.php index 0113ea6..ed387f3 100644 --- a/tests/codeception/common/unit/rbac/rules/AccountOwnerTest.php +++ b/tests/codeception/common/unit/rbac/rules/AccountOwnerTest.php @@ -40,6 +40,7 @@ class AccountOwnerTest extends TestCase { Yii::$app->set('user', $component); + $this->assertFalse($rule->execute('token', $item, [])); $this->assertFalse($rule->execute('token', $item, ['accountId' => 2])); $this->assertFalse($rule->execute('token', $item, ['accountId' => '2'])); $this->assertTrue($rule->execute('token', $item, ['accountId' => 1])); @@ -53,11 +54,4 @@ class AccountOwnerTest extends TestCase { $this->assertFalse($rule->execute('token', $item, ['accountId' => 1, 'optionalRules' => true])); } - /** - * @expectedException \yii\base\InvalidParamException - */ - public function testExecuteWithException() { - (new AccountOwner())->execute('', new Item(), []); - } - } diff --git a/tests/codeception/common/unit/rbac/rules/OauthClientOwnerTest.php b/tests/codeception/common/unit/rbac/rules/OauthClientOwnerTest.php new file mode 100644 index 0000000..1919377 --- /dev/null +++ b/tests/codeception/common/unit/rbac/rules/OauthClientOwnerTest.php @@ -0,0 +1,53 @@ + OauthClientFixture::class, + ]; + } + + public function testExecute() { + $rule = new OauthClientOwner(); + $item = new Item(); + + $account = new Account(); + $account->id = 1; + $account->status = Account::STATUS_ACTIVE; + $account->rules_agreement_version = LATEST_RULES_VERSION; + + /** @var IdentityInterface|\Mockery\MockInterface $identity */ + $identity = mock(IdentityInterface::class); + $identity->shouldReceive('getAccount')->andReturn($account); + + /** @var Component|\Mockery\MockInterface $component */ + $component = mock(Component::class . '[findIdentityByAccessToken]', [['secret' => 'secret']]); + $component->shouldDeferMissing(); + $component->shouldReceive('findIdentityByAccessToken')->withArgs(['token'])->andReturn($identity); + + Yii::$app->set('user', $component); + + $this->assertFalse($rule->execute('token', $item, [])); + $this->assertTrue($rule->execute('token', $item, ['clientId' => 'admin-oauth-client'])); + $this->assertFalse($rule->execute('token', $item, ['clientId' => 'not-exists-client'])); + $account->id = 2; + $this->assertFalse($rule->execute('token', $item, ['clientId' => 'admin-oauth-client'])); + $item->name = P::VIEW_OWN_OAUTH_CLIENTS; + $this->assertTrue($rule->execute('token', $item, ['accountId' => 2])); + $this->assertFalse($rule->execute('token', $item, ['accountId' => 1])); + } + +} diff --git a/tests/codeception/common/unit/tasks/ClearOauthSessionsTest.php b/tests/codeception/common/unit/tasks/ClearOauthSessionsTest.php new file mode 100644 index 0000000..a054990 --- /dev/null +++ b/tests/codeception/common/unit/tasks/ClearOauthSessionsTest.php @@ -0,0 +1,55 @@ + fixtures\OauthClientFixture::class, + 'oauthSessions' => fixtures\OauthSessionFixture::class, + ]; + } + + public function testCreateFromClient() { + $client = new OauthClient(); + $client->id = 'mocked-id'; + + $result = ClearOauthSessions::createFromOauthClient($client); + $this->assertInstanceOf(ClearOauthSessions::class, $result); + $this->assertSame('mocked-id', $result->clientId); + $this->assertNull($result->notSince); + + $result = ClearOauthSessions::createFromOauthClient($client, time()); + $this->assertInstanceOf(ClearOauthSessions::class, $result); + $this->assertSame('mocked-id', $result->clientId); + $this->assertEquals(time(), $result->notSince, '', 1); + } + + public function testExecute() { + $task = new ClearOauthSessions(); + $task->clientId = 'deleted-oauth-client-with-sessions'; + $task->notSince = 1519510065; + $task->execute(mock(Queue::class)); + + $this->assertFalse(OauthSession::find()->andWhere(['id' => 3])->exists()); + $this->assertTrue(OauthSession::find()->andWhere(['id' => 4])->exists()); + + $task = new ClearOauthSessions(); + $task->clientId = 'deleted-oauth-client-with-sessions'; + $task->execute(mock(Queue::class)); + + $this->assertFalse(OauthSession::find()->andWhere(['id' => 4])->exists()); + + $task = new ClearOauthSessions(); + $task->clientId = 'some-not-exists-client-id'; + $task->execute(mock(Queue::class)); + } + +} diff --git a/tests/codeception/common/unit/validators/MinecraftServerAddressValidatorTest.php b/tests/codeception/common/unit/validators/MinecraftServerAddressValidatorTest.php new file mode 100644 index 0000000..3483554 --- /dev/null +++ b/tests/codeception/common/unit/validators/MinecraftServerAddressValidatorTest.php @@ -0,0 +1,33 @@ +validate($address, $errors); + $this->assertEquals($shouldBeValid, $errors === null); + } + + public function domainNames() { + return [ + ['localhost', true ], + ['localhost:25565', true ], + ['mc.hypixel.net', true ], + ['mc.hypixel.net:25565', true ], + ['136.243.88.97', true ], + ['136.243.88.97:25565', true ], + ['http://ely.by', false], + ['http://ely.by:80', false], + ['ely.by/abcd', false], + ['ely.by?abcd', false], + ]; + } + +} diff --git a/tests/codeception/console/unit.suite.yml b/tests/codeception/console/unit.suite.yml index baa5a80..a675ae0 100644 --- a/tests/codeception/console/unit.suite.yml +++ b/tests/codeception/console/unit.suite.yml @@ -4,6 +4,7 @@ modules: - Yii2: part: [orm, email, fixtures] - tests\codeception\common\_support\Mockery + - tests\codeception\common\_support\queue\CodeceptionQueueHelper config: Yii2: configFile: '../config/console/unit.php' diff --git a/tests/codeception/console/unit/controllers/CleanupControllerTest.php b/tests/codeception/console/unit/controllers/CleanupControllerTest.php index b957c05..f87d40f 100644 --- a/tests/codeception/console/unit/controllers/CleanupControllerTest.php +++ b/tests/codeception/console/unit/controllers/CleanupControllerTest.php @@ -4,10 +4,10 @@ namespace codeception\console\unit\controllers; use common\models\AccountSession; use common\models\EmailActivation; use common\models\MinecraftAccessKey; +use common\models\OauthClient; +use common\tasks\ClearOauthSessions; use console\controllers\CleanupController; -use tests\codeception\common\fixtures\AccountSessionFixture; -use tests\codeception\common\fixtures\EmailActivationFixture; -use tests\codeception\common\fixtures\MinecraftAccessKeyFixture; +use tests\codeception\common\fixtures; use tests\codeception\console\unit\TestCase; use Yii; @@ -15,9 +15,11 @@ class CleanupControllerTest extends TestCase { public function _fixtures() { return [ - 'emailActivations' => EmailActivationFixture::class, - 'minecraftSessions' => MinecraftAccessKeyFixture::class, - 'accountsSessions' => AccountSessionFixture::class, + 'emailActivations' => fixtures\EmailActivationFixture::class, + 'minecraftSessions' => fixtures\MinecraftAccessKeyFixture::class, + 'accountsSessions' => fixtures\AccountSessionFixture::class, + 'oauthClients' => fixtures\OauthClientFixture::class, + 'oauthSessions' => fixtures\OauthSessionFixture::class, ]; } @@ -56,4 +58,22 @@ class CleanupControllerTest extends TestCase { $this->assertEquals($totalSessionsCount - 2, AccountSession::find()->count()); } + public function testActionOauthClients() { + /** @var OauthClient $deletedClient */ + $totalClientsCount = OauthClient::find()->includeDeleted()->count(); + + $controller = new CleanupController('cleanup', Yii::$app); + $this->assertEquals(0, $controller->actionOauthClients()); + + $this->assertNull(OauthClient::find()->includeDeleted()->andWhere(['id' => 'deleted-oauth-client'])->one()); + $this->assertNotNull(OauthClient::find()->includeDeleted()->andWhere(['id' => 'deleted-oauth-client-with-sessions'])->one()); + $this->assertEquals($totalClientsCount - 1, OauthClient::find()->includeDeleted()->count()); + + /** @var ClearOauthSessions $job */ + $job = $this->tester->grabLastQueuedJob(); + $this->assertInstanceOf(ClearOauthSessions::class, $job); + $this->assertSame('deleted-oauth-client-with-sessions', $job->clientId); + $this->assertNull($job->notSince); + } + }