mirror of
https://github.com/elyby/accounts.git
synced 2024-11-30 02:32:26 +05:30
Rework identity provider for the legacy OAuth2 tokens [skip ci]
This commit is contained in:
parent
c722c46ad5
commit
cf62c686b1
@ -11,9 +11,6 @@ use League\OAuth2\Server\AuthorizationServer;
|
|||||||
use League\OAuth2\Server\Grant;
|
use League\OAuth2\Server\Grant;
|
||||||
use yii\base\Component as BaseComponent;
|
use yii\base\Component as BaseComponent;
|
||||||
|
|
||||||
/**
|
|
||||||
* @property AuthorizationServer $authServer
|
|
||||||
*/
|
|
||||||
class Component extends BaseComponent {
|
class Component extends BaseComponent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,7 +36,6 @@ class Component extends BaseComponent {
|
|||||||
new EmptyKey(),
|
new EmptyKey(),
|
||||||
'123' // TODO: extract to the variable
|
'123' // TODO: extract to the variable
|
||||||
);
|
);
|
||||||
/** @noinspection PhpUnhandledExceptionInspection */
|
|
||||||
$authCodeGrant = new AuthCodeGrant($authCodesRepo, $refreshTokensRepo, new DateInterval('PT10M'));
|
$authCodeGrant = new AuthCodeGrant($authCodesRepo, $refreshTokensRepo, new DateInterval('PT10M'));
|
||||||
$authCodeGrant->disableRequireCodeChallengeForPublicClients();
|
$authCodeGrant->disableRequireCodeChallengeForPublicClients();
|
||||||
$authServer->enableGrantType($authCodeGrant, $accessTokenTTL);
|
$authServer->enableGrantType($authCodeGrant, $accessTokenTTL);
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace api\components\OAuth2\Repositories;
|
|
||||||
|
|
||||||
use api\components\OAuth2\Entities\AccessTokenEntity;
|
|
||||||
use common\components\Redis\Key;
|
|
||||||
use common\components\Redis\Set;
|
|
||||||
use League\OAuth2\Server\Entity\AccessTokenEntity as OriginalAccessTokenEntity;
|
|
||||||
use League\OAuth2\Server\Entity\ScopeEntity;
|
|
||||||
use League\OAuth2\Server\Storage\AbstractStorage;
|
|
||||||
use League\OAuth2\Server\Storage\AccessTokenInterface;
|
|
||||||
use yii\helpers\Json;
|
|
||||||
|
|
||||||
class AccessTokenStorage extends AbstractStorage implements AccessTokenInterface {
|
|
||||||
|
|
||||||
public $dataTable = 'oauth_access_tokens';
|
|
||||||
|
|
||||||
public function get($token) {
|
|
||||||
$result = Json::decode((new Key($this->dataTable, $token))->getValue());
|
|
||||||
if ($result === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$token = new AccessTokenEntity($this->server);
|
|
||||||
$token->setId($result['id']);
|
|
||||||
$token->setExpireTime($result['expire_time']);
|
|
||||||
$token->setSessionId($result['session_id']);
|
|
||||||
|
|
||||||
return $token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getScopes(OriginalAccessTokenEntity $token) {
|
|
||||||
$scopes = $this->scopes($token->getId());
|
|
||||||
$entities = [];
|
|
||||||
foreach ($scopes as $scope) {
|
|
||||||
if ($this->server->getScopeStorage()->get($scope) !== null) {
|
|
||||||
$entities[] = (new ScopeEntity($this->server))->hydrate(['id' => $scope]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $entities;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function create($token, $expireTime, $sessionId) {
|
|
||||||
$payload = Json::encode([
|
|
||||||
'id' => $token,
|
|
||||||
'expire_time' => $expireTime,
|
|
||||||
'session_id' => $sessionId,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->key($token)->setValue($payload)->expireAt($expireTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function associateScope(OriginalAccessTokenEntity $token, ScopeEntity $scope) {
|
|
||||||
$this->scopes($token->getId())->add($scope->getId())->expireAt($token->getExpireTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(OriginalAccessTokenEntity $token) {
|
|
||||||
$this->key($token->getId())->delete();
|
|
||||||
$this->scopes($token->getId())->delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function key(string $token): Key {
|
|
||||||
return new Key($this->dataTable, $token);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function scopes(string $token): Set {
|
|
||||||
return new Set($this->dataTable, $token, 'scopes');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -8,19 +8,24 @@ use yii\web\UnauthorizedHttpException;
|
|||||||
class IdentityFactory {
|
class IdentityFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws UnauthorizedHttpException
|
* @param string $token
|
||||||
|
* @param string $type
|
||||||
|
*
|
||||||
* @return IdentityInterface
|
* @return IdentityInterface
|
||||||
|
* @throws UnauthorizedHttpException
|
||||||
*/
|
*/
|
||||||
public static function findIdentityByAccessToken($token, $type = null): IdentityInterface {
|
public static function findIdentityByAccessToken($token, $type = null): IdentityInterface {
|
||||||
if (empty($token)) {
|
if (!empty($token)) {
|
||||||
throw new UnauthorizedHttpException('Incorrect token');
|
if (mb_strlen($token) === 40) {
|
||||||
|
return LegacyOAuth2Identity::findIdentityByAccessToken($token, $type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (substr_count($token, '.') === 2) {
|
if (substr_count($token, '.') === 2) {
|
||||||
return JwtIdentity::findIdentityByAccessToken($token, $type);
|
return JwtIdentity::findIdentityByAccessToken($token, $type);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return OAuth2Identity::findIdentityByAccessToken($token, $type);
|
throw new UnauthorizedHttpException('Incorrect token');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
119
api/components/User/LegacyOAuth2Identity.php
Normal file
119
api/components/User/LegacyOAuth2Identity.php
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace api\components\User;
|
||||||
|
|
||||||
|
use common\models\Account;
|
||||||
|
use common\models\OauthSession;
|
||||||
|
use Exception;
|
||||||
|
use Yii;
|
||||||
|
use yii\base\NotSupportedException;
|
||||||
|
use yii\web\UnauthorizedHttpException;
|
||||||
|
|
||||||
|
class LegacyOAuth2Identity implements IdentityInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $accessToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sessionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private $scopes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var OauthSession|null
|
||||||
|
*/
|
||||||
|
private $session = false;
|
||||||
|
|
||||||
|
private function __construct(string $accessToken, string $sessionId, array $scopes) {
|
||||||
|
$this->accessToken = $accessToken;
|
||||||
|
$this->sessionId = $sessionId;
|
||||||
|
$this->scopes = $scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
* @throws UnauthorizedHttpException
|
||||||
|
* @return IdentityInterface
|
||||||
|
*/
|
||||||
|
public static function findIdentityByAccessToken($token, $type = null): IdentityInterface {
|
||||||
|
$tokenParams = self::findRecordOnLegacyStorage($token);
|
||||||
|
if ($tokenParams === null) {
|
||||||
|
throw new UnauthorizedHttpException('Incorrect token');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($tokenParams['expire_time'] < time()) {
|
||||||
|
throw new UnauthorizedHttpException('Token expired');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new static($token, $tokenParams['session_id'], $tokenParams['scopes']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAccount(): ?Account {
|
||||||
|
$session = $this->getSession();
|
||||||
|
if ($session === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $session->account;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getAssignedPermissions(): array {
|
||||||
|
return $this->scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): string {
|
||||||
|
return $this->accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
public function getAuthKey() {
|
||||||
|
throw new NotSupportedException('This method used for cookie auth, except we using Bearer auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateAuthKey($authKey) {
|
||||||
|
throw new NotSupportedException('This method used for cookie auth, except we using Bearer auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function findIdentity($id) {
|
||||||
|
throw new NotSupportedException('This method used for cookie auth, except we using Bearer auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
|
||||||
|
private static function findRecordOnLegacyStorage(string $accessToken): ?array {
|
||||||
|
$record = Yii::$app->redis->get("oauth:access:tokens:{$accessToken}");
|
||||||
|
if ($record === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$data = json_decode($record, true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['scopes'] = (array)Yii::$app->redis->smembers("oauth:access:tokens:{$accessToken}:scopes");
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getSession(): ?OauthSession {
|
||||||
|
if ($this->session === false) {
|
||||||
|
$this->session = OauthSession::findOne(['id' => $this->sessionId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->session;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,78 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace api\components\User;
|
|
||||||
|
|
||||||
use api\components\OAuth2\Entities\AccessTokenEntity;
|
|
||||||
use common\models\Account;
|
|
||||||
use common\models\OauthSession;
|
|
||||||
use Yii;
|
|
||||||
use yii\base\NotSupportedException;
|
|
||||||
use yii\web\UnauthorizedHttpException;
|
|
||||||
|
|
||||||
class OAuth2Identity implements IdentityInterface {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var AccessTokenEntity
|
|
||||||
*/
|
|
||||||
private $_accessToken;
|
|
||||||
|
|
||||||
private function __construct(AccessTokenEntity $accessToken) {
|
|
||||||
$this->_accessToken = $accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
* @throws UnauthorizedHttpException
|
|
||||||
* @return IdentityInterface
|
|
||||||
*/
|
|
||||||
public static function findIdentityByAccessToken($token, $type = null): IdentityInterface {
|
|
||||||
/** @var AccessTokenEntity|null $model */
|
|
||||||
// TODO: rework
|
|
||||||
$model = Yii::$app->oauth->getAccessTokenStorage()->get($token);
|
|
||||||
if ($model === null) {
|
|
||||||
throw new UnauthorizedHttpException('Incorrect token');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($model->isExpired()) {
|
|
||||||
throw new UnauthorizedHttpException('Token expired');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new static($model);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAccount(): ?Account {
|
|
||||||
return $this->getSession()->account;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
public function getAssignedPermissions(): array {
|
|
||||||
return array_keys($this->_accessToken->getScopes());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): string {
|
|
||||||
return $this->_accessToken->getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
// @codeCoverageIgnoreStart
|
|
||||||
public function getAuthKey() {
|
|
||||||
throw new NotSupportedException('This method used for cookie auth, except we using Bearer auth');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function validateAuthKey($authKey) {
|
|
||||||
throw new NotSupportedException('This method used for cookie auth, except we using Bearer auth');
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function findIdentity($id) {
|
|
||||||
throw new NotSupportedException('This method used for cookie auth, except we using Bearer auth');
|
|
||||||
}
|
|
||||||
|
|
||||||
// @codeCoverageIgnoreEnd
|
|
||||||
|
|
||||||
private function getSession(): OauthSession {
|
|
||||||
return OauthSession::findOne(['id' => $this->_accessToken->getSessionId()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -5,7 +5,7 @@ namespace codeception\api\unit\components\User;
|
|||||||
|
|
||||||
use api\components\User\Component;
|
use api\components\User\Component;
|
||||||
use api\components\User\JwtIdentity;
|
use api\components\User\JwtIdentity;
|
||||||
use api\components\User\OAuth2Identity;
|
use api\components\User\LegacyOAuth2Identity;
|
||||||
use api\tests\unit\TestCase;
|
use api\tests\unit\TestCase;
|
||||||
use common\models\Account;
|
use common\models\Account;
|
||||||
use common\models\AccountSession;
|
use common\models\AccountSession;
|
||||||
@ -41,7 +41,7 @@ class ComponentTest extends TestCase {
|
|||||||
$this->assertNull($component->getActiveSession());
|
$this->assertNull($component->getActiveSession());
|
||||||
|
|
||||||
// Identity is a Oauth2Identity
|
// Identity is a Oauth2Identity
|
||||||
$component->setIdentity(mock(OAuth2Identity::class));
|
$component->setIdentity(mock(LegacyOAuth2Identity::class));
|
||||||
$this->assertNull($component->getActiveSession());
|
$this->assertNull($component->getActiveSession());
|
||||||
|
|
||||||
// Identity is correct, but have no jti claim
|
// Identity is correct, but have no jti claim
|
||||||
|
@ -7,7 +7,7 @@ use api\components\OAuth2\Component;
|
|||||||
use api\components\OAuth2\Entities\AccessTokenEntity;
|
use api\components\OAuth2\Entities\AccessTokenEntity;
|
||||||
use api\components\User\IdentityFactory;
|
use api\components\User\IdentityFactory;
|
||||||
use api\components\User\JwtIdentity;
|
use api\components\User\JwtIdentity;
|
||||||
use api\components\User\OAuth2Identity;
|
use api\components\User\LegacyOAuth2Identity;
|
||||||
use api\tests\unit\TestCase;
|
use api\tests\unit\TestCase;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use League\OAuth2\Server\AbstractServer;
|
use League\OAuth2\Server\AbstractServer;
|
||||||
@ -37,7 +37,7 @@ class IdentityFactoryTest extends TestCase {
|
|||||||
Yii::$app->set('oauth', $component);
|
Yii::$app->set('oauth', $component);
|
||||||
|
|
||||||
$identity = IdentityFactory::findIdentityByAccessToken('mock-token');
|
$identity = IdentityFactory::findIdentityByAccessToken('mock-token');
|
||||||
$this->assertInstanceOf(OAuth2Identity::class, $identity);
|
$this->assertInstanceOf(LegacyOAuth2Identity::class, $identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFindIdentityByAccessTokenWithEmptyValue() {
|
public function testFindIdentityByAccessTokenWithEmptyValue() {
|
||||||
|
@ -5,14 +5,12 @@ namespace api\tests\unit\components\User;
|
|||||||
|
|
||||||
use api\components\OAuth2\Component;
|
use api\components\OAuth2\Component;
|
||||||
use api\components\OAuth2\Entities\AccessTokenEntity;
|
use api\components\OAuth2\Entities\AccessTokenEntity;
|
||||||
use api\components\User\OAuth2Identity;
|
use api\components\User\LegacyOAuth2Identity;
|
||||||
use api\tests\unit\TestCase;
|
use api\tests\unit\TestCase;
|
||||||
use League\OAuth2\Server\AbstractServer;
|
|
||||||
use League\OAuth2\Server\Storage\AccessTokenInterface;
|
|
||||||
use Yii;
|
use Yii;
|
||||||
use yii\web\UnauthorizedHttpException;
|
use yii\web\UnauthorizedHttpException;
|
||||||
|
|
||||||
class OAuth2IdentityTest extends TestCase {
|
class LegacyOAuth2IdentityTest extends TestCase {
|
||||||
|
|
||||||
public function testFindIdentityByAccessToken() {
|
public function testFindIdentityByAccessToken() {
|
||||||
$accessToken = new AccessTokenEntity(mock(AbstractServer::class));
|
$accessToken = new AccessTokenEntity(mock(AbstractServer::class));
|
||||||
@ -20,7 +18,7 @@ class OAuth2IdentityTest extends TestCase {
|
|||||||
$accessToken->setId('mock-token');
|
$accessToken->setId('mock-token');
|
||||||
$this->mockFoundedAccessToken($accessToken);
|
$this->mockFoundedAccessToken($accessToken);
|
||||||
|
|
||||||
$identity = OAuth2Identity::findIdentityByAccessToken('mock-token');
|
$identity = LegacyOAuth2Identity::findIdentityByAccessToken('mock-token');
|
||||||
$this->assertSame('mock-token', $identity->getId());
|
$this->assertSame('mock-token', $identity->getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +26,7 @@ class OAuth2IdentityTest extends TestCase {
|
|||||||
$this->expectException(UnauthorizedHttpException::class);
|
$this->expectException(UnauthorizedHttpException::class);
|
||||||
$this->expectExceptionMessage('Incorrect token');
|
$this->expectExceptionMessage('Incorrect token');
|
||||||
|
|
||||||
OAuth2Identity::findIdentityByAccessToken('not exists token');
|
LegacyOAuth2Identity::findIdentityByAccessToken('not exists token');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFindIdentityByAccessTokenWithExpiredToken() {
|
public function testFindIdentityByAccessTokenWithExpiredToken() {
|
||||||
@ -39,7 +37,7 @@ class OAuth2IdentityTest extends TestCase {
|
|||||||
$accessToken->setExpireTime(time() - 3600);
|
$accessToken->setExpireTime(time() - 3600);
|
||||||
$this->mockFoundedAccessToken($accessToken);
|
$this->mockFoundedAccessToken($accessToken);
|
||||||
|
|
||||||
OAuth2Identity::findIdentityByAccessToken('mock-token');
|
LegacyOAuth2Identity::findIdentityByAccessToken('mock-token');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function mockFoundedAccessToken(AccessTokenEntity $accessToken) {
|
private function mockFoundedAccessToken(AccessTokenEntity $accessToken) {
|
@ -1,63 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace common\components\Redis;
|
|
||||||
|
|
||||||
use InvalidArgumentException;
|
|
||||||
use Yii;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
class Key {
|
|
||||||
|
|
||||||
private $key;
|
|
||||||
|
|
||||||
public function __construct(...$key) {
|
|
||||||
if (empty($key)) {
|
|
||||||
throw new InvalidArgumentException('You must specify at least one key.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->key = $this->buildKey($key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getKey(): string {
|
|
||||||
return $this->key;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getValue() {
|
|
||||||
return Yii::$app->redis->get($this->key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setValue($value): self {
|
|
||||||
Yii::$app->redis->set($this->key, $value);
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(): self {
|
|
||||||
Yii::$app->redis->del($this->getKey());
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function exists(): bool {
|
|
||||||
return (bool)Yii::$app->redis->exists($this->key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function expire(int $ttl): self {
|
|
||||||
Yii::$app->redis->expire($this->key, $ttl);
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function expireAt(int $unixTimestamp): self {
|
|
||||||
Yii::$app->redis->expireat($this->key, $unixTimestamp);
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function buildKey(array $parts): string {
|
|
||||||
$keyParts = [];
|
|
||||||
foreach ($parts as $part) {
|
|
||||||
$keyParts[] = str_replace('_', ':', $part);
|
|
||||||
}
|
|
||||||
|
|
||||||
return implode(':', $keyParts);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace common\components\Redis;
|
|
||||||
|
|
||||||
use ArrayIterator;
|
|
||||||
use IteratorAggregate;
|
|
||||||
use Yii;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
class Set extends Key implements IteratorAggregate {
|
|
||||||
|
|
||||||
public function add($value): self {
|
|
||||||
Yii::$app->redis->sadd($this->getKey(), $value);
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function remove($value): self {
|
|
||||||
Yii::$app->redis->srem($this->getKey(), $value);
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function members(): array {
|
|
||||||
return Yii::$app->redis->smembers($this->getKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getValue(): array {
|
|
||||||
return $this->members();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function exists(string $value = null): bool {
|
|
||||||
if ($value === null) {
|
|
||||||
return parent::exists();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (bool)Yii::$app->redis->sismember($this->getKey(), $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function diff(array $sets): array {
|
|
||||||
return Yii::$app->redis->sdiff([$this->getKey(), implode(' ', $sets)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
public function getIterator() {
|
|
||||||
return new ArrayIterator($this->members());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user