mirror of
				https://github.com/webmasterskaya/joomla-oauth-server.git
				synced 2025-11-04 10:43:22 +03:00 
			
		
		
		
	Первый тест - Success
This commit is contained in:
		
							parent
							
								
									1efe232439
								
							
						
					
					
						commit
						2636bebd60
					
				@ -0,0 +1,87 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Webmasterskaya\Component\OauthServer\Administrator\Event\Scope;
 | 
			
		||||
 | 
			
		||||
use Joomla\CMS\Event\AbstractEvent;
 | 
			
		||||
use Webmasterskaya\Component\OauthServer\Site\Entity\Scope;
 | 
			
		||||
 | 
			
		||||
class ScopeResolveEvent extends AbstractEvent
 | 
			
		||||
{
 | 
			
		||||
    private bool $is_constructed;
 | 
			
		||||
 | 
			
		||||
    public function __construct(array $scopes, string $grant, object $client, ?int $user_id)
 | 
			
		||||
    {
 | 
			
		||||
        $arguments = [
 | 
			
		||||
            'scopes' => $scopes,
 | 
			
		||||
            'grant' => $grant,
 | 
			
		||||
            'client' => $client,
 | 
			
		||||
            'user_id' => $user_id
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        $this->is_constructed = false;
 | 
			
		||||
 | 
			
		||||
        parent::__construct('onOauthServerScopeResolve', $arguments);
 | 
			
		||||
 | 
			
		||||
        $this->is_constructed = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function onSetScopes(array $scopes): array
 | 
			
		||||
    {
 | 
			
		||||
        foreach ($scopes as &$scope) {
 | 
			
		||||
            if (!($scope instanceof Scope)) {
 | 
			
		||||
                throw new \InvalidArgumentException(sprintf('Argument "scopes" must be array of "%s" in class "%s". "%s" given.',
 | 
			
		||||
                    Scope::class,
 | 
			
		||||
                    get_class($this),
 | 
			
		||||
                    get_debug_type($scope)
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $scopes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function onSetGrant(string $grant)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->is_constructed) {
 | 
			
		||||
            $grant = $this->getArgument('grant');
 | 
			
		||||
        }
 | 
			
		||||
        return $grant;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function onSetClient(object $client)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->is_constructed) {
 | 
			
		||||
            $client = $this->getArgument('client');
 | 
			
		||||
        }
 | 
			
		||||
        return $client;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function onSetUser_id(?int $user_id)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->is_constructed) {
 | 
			
		||||
            $user_id = $this->getArgument('user_id');
 | 
			
		||||
        }
 | 
			
		||||
        return $user_id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function removeArgument($name)
 | 
			
		||||
    {
 | 
			
		||||
        throw new \BadMethodCallException(
 | 
			
		||||
            sprintf(
 | 
			
		||||
                'Cannot remove the argument %s of the event %s.',
 | 
			
		||||
                $name,
 | 
			
		||||
                $this->name
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function clearArguments()
 | 
			
		||||
    {
 | 
			
		||||
        throw new \BadMethodCallException(
 | 
			
		||||
            sprintf(
 | 
			
		||||
                'Cannot clear arguments of the event %s.',
 | 
			
		||||
                $this->name
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								com_oauthserver/administrator/src/Model/AuthCodeModel.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								com_oauthserver/administrator/src/Model/AuthCodeModel.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Webmasterskaya\Component\OauthServer\Administrator\Model;
 | 
			
		||||
 | 
			
		||||
use Joomla\CMS\Factory;
 | 
			
		||||
use Joomla\CMS\Form\Form;
 | 
			
		||||
use Joomla\CMS\Form\FormFactoryInterface;
 | 
			
		||||
use Joomla\CMS\MVC\Factory\LegacyFactory;
 | 
			
		||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
 | 
			
		||||
use Joomla\CMS\MVC\Model\AdminModel;
 | 
			
		||||
 | 
			
		||||
class AuthCodeModel extends AdminModel implements RevokedModelInterface
 | 
			
		||||
{
 | 
			
		||||
    use GetItemByIdentifierTrait;
 | 
			
		||||
    use RevokedModelTrait;
 | 
			
		||||
 | 
			
		||||
    public function __construct($config = [], MVCFactoryInterface $factory = null, FormFactoryInterface $formFactory = null)
 | 
			
		||||
    {
 | 
			
		||||
        $this->name = 'AuthCode';
 | 
			
		||||
        parent::__construct($config, $factory, $formFactory);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getTable($name = 'AuthCode', $prefix = 'Administrator', $options = [])
 | 
			
		||||
    {
 | 
			
		||||
        return parent::getTable($name, $prefix, $options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getForm($data = [], $loadData = true): Form|bool
 | 
			
		||||
    {
 | 
			
		||||
        $form = $this->loadForm('com_oauthserver.auth_code', 'auth_code', ['control' => 'jform', 'load_data' => $loadData]);
 | 
			
		||||
 | 
			
		||||
        if (empty($form)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $form;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function loadFormData(): mixed
 | 
			
		||||
    {
 | 
			
		||||
        // Check the session for previously entered form data.
 | 
			
		||||
        $data = Factory::getApplication()->getUserState('com_oauthserver.edit.auth_code.data', []);
 | 
			
		||||
 | 
			
		||||
        if (empty($data)) {
 | 
			
		||||
            $data = $this->getItem();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->preprocessData('com_oauthserver.auth_code', $data);
 | 
			
		||||
 | 
			
		||||
        return $data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param \Webmasterskaya\Component\OauthServer\Administrator\Table\AuthCodeTable $table
 | 
			
		||||
     * @return void
 | 
			
		||||
     * @since version
 | 
			
		||||
     */
 | 
			
		||||
    protected function prepareTable($table)
 | 
			
		||||
    {
 | 
			
		||||
        if ($table->expiry instanceof \DateTime || $table->expiry instanceof \DateTimeImmutable) {
 | 
			
		||||
            $table->expiry = $table->expiry->format($table->getDbo()->getDateFormat());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -4,7 +4,6 @@ namespace Webmasterskaya\Component\OauthServer\Administrator\Model;
 | 
			
		||||
 | 
			
		||||
use Joomla\CMS\Language\Text;
 | 
			
		||||
use Joomla\CMS\Object\CMSObject;
 | 
			
		||||
use Joomla\Registry\Registry;
 | 
			
		||||
use Joomla\Utilities\ArrayHelper;
 | 
			
		||||
 | 
			
		||||
trait GetItemByIdentifierTrait
 | 
			
		||||
@ -37,7 +36,7 @@ trait GetItemByIdentifierTrait
 | 
			
		||||
        $all_properties = $table->getProperties(false);
 | 
			
		||||
 | 
			
		||||
        if (!empty($all_properties['_jsonEncode'])) {
 | 
			
		||||
            foreach ($all_properties['$_jsonEncode'] as $prop) {
 | 
			
		||||
            foreach ($all_properties['_jsonEncode'] as $prop) {
 | 
			
		||||
                if (array_key_exists($prop, $properties) && is_string($properties[$prop])) {
 | 
			
		||||
                    $properties[$prop] = json_decode($properties[$prop]);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,38 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Webmasterskaya\Component\OauthServer\Administrator\Model;
 | 
			
		||||
 | 
			
		||||
use Joomla\CMS\Factory;
 | 
			
		||||
use Joomla\CMS\Form\Form;
 | 
			
		||||
use Joomla\CMS\MVC\Model\AdminModel;
 | 
			
		||||
 | 
			
		||||
class RefreshTokenModel extends AdminModel implements RevokedModelInterface
 | 
			
		||||
{
 | 
			
		||||
    use GetItemByIdentifierTrait;
 | 
			
		||||
    use RevokedModelTrait;
 | 
			
		||||
 | 
			
		||||
    public function getForm($data = [], $loadData = true): Form|bool
 | 
			
		||||
    {
 | 
			
		||||
        $form = $this->loadForm('com_oauthserver.refresh_token', 'refresh_token', ['control' => 'jform', 'load_data' => $loadData]);
 | 
			
		||||
 | 
			
		||||
        if (empty($form)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $form;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function loadFormData(): mixed
 | 
			
		||||
    {
 | 
			
		||||
        // Check the session for previously entered form data.
 | 
			
		||||
        $data = Factory::getApplication()->getUserState('com_oauthserver.edit.refresh_token.data', []);
 | 
			
		||||
 | 
			
		||||
        if (empty($data)) {
 | 
			
		||||
            $data = $this->getItem();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->preprocessData('com_oauthserver.refresh_token', $data);
 | 
			
		||||
 | 
			
		||||
        return $data;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								com_oauthserver/administrator/src/Model/ScopeModel.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								com_oauthserver/administrator/src/Model/ScopeModel.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Webmasterskaya\Component\OauthServer\Administrator\Model;
 | 
			
		||||
 | 
			
		||||
use Joomla\CMS\Component\ComponentHelper;
 | 
			
		||||
use Joomla\CMS\MVC\Model\BaseModel;
 | 
			
		||||
use Joomla\CMS\MVC\Model\ItemModelInterface;
 | 
			
		||||
 | 
			
		||||
class ScopeModel extends BaseModel implements ItemModelInterface
 | 
			
		||||
{
 | 
			
		||||
    private static array $_storage;
 | 
			
		||||
 | 
			
		||||
    private const PREDEFINED_SCOPES = ['userinfo', 'email'];
 | 
			
		||||
 | 
			
		||||
    public function getItem($pk = null)
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: Implement getItem() method.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function fillStorage(): void
 | 
			
		||||
    {
 | 
			
		||||
        $config = ComponentHelper::getParams('com_oauthserver');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								com_oauthserver/administrator/src/Table/AuthCodeTable.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								com_oauthserver/administrator/src/Table/AuthCodeTable.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Webmasterskaya\Component\OauthServer\Administrator\Table;
 | 
			
		||||
 | 
			
		||||
use Joomla\CMS\Table\Table;
 | 
			
		||||
use Joomla\Database\DatabaseDriver;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @property int $id
 | 
			
		||||
 * @property string $identifier
 | 
			
		||||
 * @property \DateTimeImmutable|\DateTime|string $expiry
 | 
			
		||||
 * @property int|null $user_id
 | 
			
		||||
 * @property string|array|null $scopes
 | 
			
		||||
 * @property int $revoked
 | 
			
		||||
 * @property int $client_id
 | 
			
		||||
 *
 | 
			
		||||
 * @since version
 | 
			
		||||
 */
 | 
			
		||||
class AuthCodeTable extends Table implements RevokedTableInterface
 | 
			
		||||
{
 | 
			
		||||
    use RevokedTableTrait;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicates that columns fully support the NULL value in the database
 | 
			
		||||
     *
 | 
			
		||||
     * @var    boolean
 | 
			
		||||
     * @since  version
 | 
			
		||||
     */
 | 
			
		||||
    protected $_supportNullValue = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An array of key names to be json encoded in the bind function
 | 
			
		||||
     *
 | 
			
		||||
     * @var    array
 | 
			
		||||
     * @since  version
 | 
			
		||||
     */
 | 
			
		||||
    protected $_jsonEncode = ['scopes'];
 | 
			
		||||
 | 
			
		||||
    public function __construct(DatabaseDriver $db)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct('#__webmasterskaya_oauthserver_authorization_codes', 'id', $db);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,36 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Webmasterskaya\Component\OauthServer\Administrator\Table;
 | 
			
		||||
 | 
			
		||||
use DateTimeImmutable;
 | 
			
		||||
use Joomla\CMS\Table\Table;
 | 
			
		||||
use Joomla\Database\DatabaseDriver;
 | 
			
		||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
 | 
			
		||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @property int $id
 | 
			
		||||
 * @property string $identifier
 | 
			
		||||
 * @property \DateTimeImmutable|\DateTime|string $expiry
 | 
			
		||||
 * @property int $revoked
 | 
			
		||||
 * @property int|null $access_token_id
 | 
			
		||||
 *
 | 
			
		||||
 * @since version
 | 
			
		||||
 */
 | 
			
		||||
class RefreshTokenTable extends Table implements RevokedTableInterface
 | 
			
		||||
{
 | 
			
		||||
    use RevokedTableTrait;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicates that columns fully support the NULL value in the database
 | 
			
		||||
     *
 | 
			
		||||
     * @var    boolean
 | 
			
		||||
     * @since  version
 | 
			
		||||
     */
 | 
			
		||||
    protected $_supportNullValue = true;
 | 
			
		||||
 | 
			
		||||
    public function __construct(DatabaseDriver $db)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct('#__webmasterskaya_oauthserver_refresh_tokens', 'id', $db);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -4,5 +4,5 @@ namespace Webmasterskaya\Component\OauthServer\Administrator\Table;
 | 
			
		||||
 | 
			
		||||
interface RevokedTableInterface
 | 
			
		||||
{
 | 
			
		||||
    public function revoke($pks = null, $userId = 0): bool;
 | 
			
		||||
    public function revoke($pks = null): bool;
 | 
			
		||||
}
 | 
			
		||||
@ -45,11 +45,8 @@ trait RevokedTableTrait
 | 
			
		||||
 | 
			
		||||
    abstract public function appendPrimaryKeys($query, $pk = null);
 | 
			
		||||
 | 
			
		||||
    public function revoke($pks = null, $userId = 0): bool
 | 
			
		||||
    public function revoke($pks = null): bool
 | 
			
		||||
    {
 | 
			
		||||
        // Sanitize input
 | 
			
		||||
        $userId = (int)$userId;
 | 
			
		||||
 | 
			
		||||
        // Pre-processing by observers
 | 
			
		||||
        $event = AbstractEvent::create(
 | 
			
		||||
            'onTableBeforeRevoke',
 | 
			
		||||
 | 
			
		||||
@ -3,12 +3,21 @@
 | 
			
		||||
namespace Webmasterskaya\Component\OauthServer\Site\Controller;
 | 
			
		||||
 | 
			
		||||
use Joomla\CMS\Application\CMSApplication;
 | 
			
		||||
use Webmasterskaya\Component\OauthServer\Site\Entity\User as UserEntity;
 | 
			
		||||
use Joomla\CMS\MVC\Controller\BaseController;
 | 
			
		||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
 | 
			
		||||
use Joomla\CMS\Router\Route;
 | 
			
		||||
use Joomla\Input\Input;
 | 
			
		||||
use Joomla\CMS\Uri\Uri;
 | 
			
		||||
use Laminas\Diactoros\ResponseFactory;
 | 
			
		||||
use Laminas\Diactoros\ServerRequestFactory;
 | 
			
		||||
use League\OAuth2\Server\AuthorizationServer;
 | 
			
		||||
use League\OAuth2\Server\Grant\AuthCodeGrant;
 | 
			
		||||
use Webmasterskaya\Component\OauthServer\Site\Repository\AccessTokenRepository;
 | 
			
		||||
use Webmasterskaya\Component\OauthServer\Site\Repository\AuthCodeRepository;
 | 
			
		||||
use Webmasterskaya\Component\OauthServer\Site\Repository\ClientRepository;
 | 
			
		||||
use Webmasterskaya\Component\OauthServer\Site\Repository\RefreshTokenRepository;
 | 
			
		||||
use Webmasterskaya\Component\OauthServer\Site\Repository\ScopeRepository;
 | 
			
		||||
 | 
			
		||||
class LoginController extends BaseController
 | 
			
		||||
{
 | 
			
		||||
@ -17,20 +26,104 @@ class LoginController extends BaseController
 | 
			
		||||
        parent::__construct($config, $factory, $app, $input);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function authorize()
 | 
			
		||||
    /**
 | 
			
		||||
     * @return void
 | 
			
		||||
     * @throws \Exception
 | 
			
		||||
     * @since version
 | 
			
		||||
     */
 | 
			
		||||
    public function authorize(): void
 | 
			
		||||
    {
 | 
			
		||||
        $app = $this->app;
 | 
			
		||||
        $user = $app->getIdentity();
 | 
			
		||||
        $uri = Uri::getInstance();
 | 
			
		||||
 | 
			
		||||
        if (!$user->id) {
 | 
			
		||||
            $return = http_build_query(['return' => base64_encode(Uri::getInstance()->toString(['scheme', 'user', 'pass', 'host', 'port', 'path']))]);
 | 
			
		||||
            $this->app->setUserState('oauthserver.login.authorize.request', Uri::getInstance()->getQuery(true));
 | 
			
		||||
            $return = http_build_query(['return' => base64_encode($uri->toString(['scheme', 'user', 'pass', 'host', 'port', 'path']))]);
 | 
			
		||||
            $this->app->setUserState('oauthserver.login.authorize.request', $uri->getQuery(true));
 | 
			
		||||
            $this->app->enqueueMessage('Необходимо авторизоваться!');
 | 
			
		||||
            $this->app->redirect(Route::_('index.php?option=com_users&view=login&' . $return));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $clientRepository = new ClientRepository($this->factory);
 | 
			
		||||
        var_dump($this->app->getUserState('oauthserver.login.authorize.request'));
 | 
			
		||||
        $state_request = $this->app->getUserState('oauthserver.login.authorize.request');
 | 
			
		||||
        if (!empty($state_request) && empty($uri->getQuery(true))) {
 | 
			
		||||
            foreach ($state_request as $k => $v) {
 | 
			
		||||
                $uri->setVar($k, $v);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $this->app->setUserState('oauthserver.login.authorize.request', []);
 | 
			
		||||
 | 
			
		||||
        /** @var \Webmasterskaya\Component\OauthServer\Administrator\Model\ClientModel $clientModel */
 | 
			
		||||
        $clientModel = $this->factory->createModel('Client', 'Administrator', ['request_ignore' => true]);
 | 
			
		||||
        $clientRepository = new ClientRepository($clientModel);
 | 
			
		||||
 | 
			
		||||
        /** @var \Webmasterskaya\Component\OauthServer\Administrator\Model\AccessTokenModel $accessTokenModel */
 | 
			
		||||
        $accessTokenModel = $this->factory->createModel('AccessToken', 'Administrator', ['request_ignore' => true]);
 | 
			
		||||
        $accessTokenRepository = new AccessTokenRepository($accessTokenModel, $clientModel);
 | 
			
		||||
 | 
			
		||||
        $scopeRepository = new ScopeRepository($clientModel);
 | 
			
		||||
        $scopeRepository->setDispatcher($this->getDispatcher());
 | 
			
		||||
 | 
			
		||||
        /** @var \Webmasterskaya\Component\OauthServer\Administrator\Model\AuthCodeModel $authCodeModel */
 | 
			
		||||
        $authCodeModel = $this->factory->createModel('AuthCode', 'Administrator', ['request_ignore' => true]);
 | 
			
		||||
        $authCodeRepository = new AuthCodeRepository($authCodeModel, $clientModel);
 | 
			
		||||
 | 
			
		||||
        /** @var \Webmasterskaya\Component\OauthServer\Administrator\Model\RefreshTokenModel $refreshTokenModel */
 | 
			
		||||
        $refreshTokenModel = $this->factory->createModel('RefreshToken', 'Administrator', ['request_ignore' => true]);
 | 
			
		||||
        $refreshTokenRepository = new RefreshTokenRepository($refreshTokenModel, $accessTokenModel);
 | 
			
		||||
 | 
			
		||||
        $key = openssl_pkey_new([
 | 
			
		||||
            "digest_alg" => "sha512",
 | 
			
		||||
            "private_key_bits" => 4096,
 | 
			
		||||
            "private_key_type" => OPENSSL_KEYTYPE_RSA,
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $ppk = '';
 | 
			
		||||
        openssl_pkey_export($key, $ppk);
 | 
			
		||||
 | 
			
		||||
        // Extract the public key from $res to $pubKey
 | 
			
		||||
//        $pub = openssl_pkey_get_details($key);
 | 
			
		||||
//        $pub = $pub["key"];
 | 
			
		||||
 | 
			
		||||
//        var_dump($this->app->getUserState('oauthserver.login.authorize.request'));
 | 
			
		||||
 | 
			
		||||
        $server = new AuthorizationServer(
 | 
			
		||||
            $clientRepository,
 | 
			
		||||
            $accessTokenRepository,
 | 
			
		||||
            $scopeRepository,
 | 
			
		||||
            $ppk,
 | 
			
		||||
            $this->app->get('secret')
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $grant = new AuthCodeGrant(
 | 
			
		||||
            $authCodeRepository,
 | 
			
		||||
            $refreshTokenRepository,
 | 
			
		||||
            new \DateInterval('PT10M') // authorization codes will expire after 10 minutes
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $grant->setRefreshTokenTTL(new \DateInterval('P1M')); // refresh tokens will expire after 1 month
 | 
			
		||||
 | 
			
		||||
        $server->enableGrantType(
 | 
			
		||||
            $grant,
 | 
			
		||||
            new \DateInterval('PT1H') // access tokens will expire after 1 hour
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $serverRequest = ServerRequestFactory::fromGlobals();
 | 
			
		||||
        $serverResponse = $this->app->getResponse();
 | 
			
		||||
 | 
			
		||||
//        var_dump($serverRequest->getQueryParams()); die();
 | 
			
		||||
 | 
			
		||||
        $authRequest = $server->validateAuthorizationRequest($serverRequest);
 | 
			
		||||
        $authRequest->setUser(new UserEntity($user));
 | 
			
		||||
        $authRequest->setAuthorizationApproved(true);
 | 
			
		||||
 | 
			
		||||
        $this->app->setResponse($server->completeAuthorizationRequest($authRequest, $serverResponse));
 | 
			
		||||
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
        echo "<pre>";
 | 
			
		||||
 | 
			
		||||
        var_dump();
 | 
			
		||||
 | 
			
		||||
        die();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								com_oauthserver/site/src/Entity/AuthCode.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								com_oauthserver/site/src/Entity/AuthCode.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Webmasterskaya\Component\OauthServer\Site\Entity;
 | 
			
		||||
 | 
			
		||||
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
 | 
			
		||||
use League\OAuth2\Server\Entities\Traits\AuthCodeTrait;
 | 
			
		||||
use League\OAuth2\Server\Entities\Traits\EntityTrait;
 | 
			
		||||
use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;
 | 
			
		||||
 | 
			
		||||
class AuthCode implements AuthCodeEntityInterface
 | 
			
		||||
{
 | 
			
		||||
    use AuthCodeTrait;
 | 
			
		||||
    use EntityTrait;
 | 
			
		||||
    use TokenEntityTrait;
 | 
			
		||||
 | 
			
		||||
    public function getData(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            'identifier' => $this->getIdentifier(),
 | 
			
		||||
            'expiry' => $this->getExpiryDateTime(),
 | 
			
		||||
            'user_id' => $this->getUserIdentifier(),
 | 
			
		||||
            'scopes' => $this->getScopes(),
 | 
			
		||||
            'client_identifier' => $this->getClient()->getIdentifier()
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								com_oauthserver/site/src/Entity/RefreshToken.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								com_oauthserver/site/src/Entity/RefreshToken.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Webmasterskaya\Component\OauthServer\Site\Entity;
 | 
			
		||||
 | 
			
		||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
 | 
			
		||||
use League\OAuth2\Server\Entities\Traits\EntityTrait;
 | 
			
		||||
use League\OAuth2\Server\Entities\Traits\RefreshTokenTrait;
 | 
			
		||||
 | 
			
		||||
class RefreshToken implements RefreshTokenEntityInterface
 | 
			
		||||
{
 | 
			
		||||
    use EntityTrait;
 | 
			
		||||
    use RefreshTokenTrait;
 | 
			
		||||
 | 
			
		||||
    public function getData(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            'identifier' => $this->getIdentifier(),
 | 
			
		||||
            'expiry' => $this->getExpiryDateTime(),
 | 
			
		||||
            'access_token_identifier' => $this->getAccessToken()->getIdentifier()
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								com_oauthserver/site/src/Entity/Scope.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								com_oauthserver/site/src/Entity/Scope.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Webmasterskaya\Component\OauthServer\Site\Entity;
 | 
			
		||||
 | 
			
		||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
 | 
			
		||||
use League\OAuth2\Server\Entities\Traits\EntityTrait;
 | 
			
		||||
 | 
			
		||||
final class Scope implements ScopeEntityInterface
 | 
			
		||||
{
 | 
			
		||||
    use EntityTrait;
 | 
			
		||||
 | 
			
		||||
    protected ?string $description;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return mixed
 | 
			
		||||
     * @since version
 | 
			
		||||
     */
 | 
			
		||||
    public function jsonSerialize(): mixed
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getIdentifier();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return string|null
 | 
			
		||||
     * @since version
 | 
			
		||||
     */
 | 
			
		||||
    public function getDescription(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->description ?? null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param string $description
 | 
			
		||||
     * @since version
 | 
			
		||||
     */
 | 
			
		||||
    public function setDescription(string $description): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->description = $description;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function __toString(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->identifier;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								com_oauthserver/site/src/Entity/User.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								com_oauthserver/site/src/Entity/User.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Webmasterskaya\Component\OauthServer\Site\Entity;
 | 
			
		||||
 | 
			
		||||
use League\OAuth2\Server\Entities\Traits\EntityTrait;
 | 
			
		||||
use League\OAuth2\Server\Entities\UserEntityInterface;
 | 
			
		||||
 | 
			
		||||
class User implements UserEntityInterface
 | 
			
		||||
{
 | 
			
		||||
    use EntityTrait;
 | 
			
		||||
 | 
			
		||||
    public function __construct(\Joomla\CMS\User\User $user)
 | 
			
		||||
    {
 | 
			
		||||
        $this->setIdentifier($user->id);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -3,28 +3,75 @@
 | 
			
		||||
namespace Webmasterskaya\Component\OauthServer\Site\Repository;
 | 
			
		||||
 | 
			
		||||
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
 | 
			
		||||
use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;
 | 
			
		||||
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
 | 
			
		||||
use Wamania\Snowball\NotFoundException;
 | 
			
		||||
use Webmasterskaya\Component\OauthServer\Administrator\Model\AuthCodeModel;
 | 
			
		||||
use Webmasterskaya\Component\OauthServer\Administrator\Model\ClientModel;
 | 
			
		||||
use Webmasterskaya\Component\OauthServer\Site\Entity\AuthCode;
 | 
			
		||||
 | 
			
		||||
class AuthCodeRepository implements AuthCodeRepositoryInterface
 | 
			
		||||
{
 | 
			
		||||
    private AuthCodeModel $authCodeModel;
 | 
			
		||||
 | 
			
		||||
    public function getNewAuthCode()
 | 
			
		||||
    private ClientModel $clientModel;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param \Webmasterskaya\Component\OauthServer\Administrator\Model\AuthCodeModel $authCodeModel
 | 
			
		||||
     * @param \Webmasterskaya\Component\OauthServer\Administrator\Model\ClientModel $clientModel
 | 
			
		||||
     * @since version
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct(AuthCodeModel $authCodeModel, ClientModel $clientModel)
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: Implement getNewAuthCode() method.
 | 
			
		||||
        $this->authCodeModel = $authCodeModel;
 | 
			
		||||
        $this->clientModel = $clientModel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getNewAuthCode(): AuthCode
 | 
			
		||||
    {
 | 
			
		||||
        return new AuthCode();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity)
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: Implement persistNewAuthCode() method.
 | 
			
		||||
        $found = false;
 | 
			
		||||
        try {
 | 
			
		||||
            $authCode = $this->authCodeModel->getItemByIdentifier($authCodeEntity->getIdentifier());
 | 
			
		||||
 | 
			
		||||
            if ($authCode->id > 0) {
 | 
			
		||||
                $found = true;
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($found) {
 | 
			
		||||
            throw UniqueTokenIdentifierConstraintViolationException::create();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $data = $authCodeEntity->getData();
 | 
			
		||||
 | 
			
		||||
        $client = $this->clientModel->getItemByIdentifier($authCodeEntity->getClient()->getIdentifier());
 | 
			
		||||
 | 
			
		||||
        $data['client_id'] = $client->id;
 | 
			
		||||
        unset($data['client_identifier']);
 | 
			
		||||
 | 
			
		||||
        $this->authCodeModel->save($data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function revokeAuthCode($codeId)
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: Implement revokeAuthCode() method.
 | 
			
		||||
        $this->authCodeModel->revoke($codeId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function isAuthCodeRevoked($codeId)
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: Implement isAuthCodeRevoked() method.
 | 
			
		||||
        $authCode = $this->authCodeModel->getItemByIdentifier($codeId);
 | 
			
		||||
 | 
			
		||||
        if (empty($authCode->id)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return !!$authCode->revoked;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -3,28 +3,67 @@
 | 
			
		||||
namespace Webmasterskaya\Component\OauthServer\Site\Repository;
 | 
			
		||||
 | 
			
		||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
 | 
			
		||||
use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;
 | 
			
		||||
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
 | 
			
		||||
use Webmasterskaya\Component\OauthServer\Administrator\Model\AccessTokenModel;
 | 
			
		||||
use Webmasterskaya\Component\OauthServer\Administrator\Model\RefreshTokenModel;
 | 
			
		||||
use Webmasterskaya\Component\OauthServer\Site\Entity\RefreshToken;
 | 
			
		||||
 | 
			
		||||
class RefreshTokenRepository implements RefreshTokenRepositoryInterface
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public function getNewRefreshToken()
 | 
			
		||||
    private RefreshTokenModel $refreshTokenModel;
 | 
			
		||||
 | 
			
		||||
    private AccessTokenModel $accessTokenModel;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param \Webmasterskaya\Component\OauthServer\Administrator\Model\RefreshTokenModel $refreshTokenModel
 | 
			
		||||
     * @param \Webmasterskaya\Component\OauthServer\Administrator\Model\AccessTokenModel $accessTokenModel
 | 
			
		||||
     * @since version
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct(RefreshTokenModel $refreshTokenModel, AccessTokenModel $accessTokenModel)
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: Implement getNewRefreshToken() method.
 | 
			
		||||
        $this->refreshTokenModel = $refreshTokenModel;
 | 
			
		||||
        $this->accessTokenModel = $accessTokenModel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function getNewRefreshToken(): RefreshToken
 | 
			
		||||
    {
 | 
			
		||||
        return new RefreshToken();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity)
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: Implement persistNewRefreshToken() method.
 | 
			
		||||
        $refreshToken = $this->refreshTokenModel->getItemByIdentifier($refreshTokenEntity->getIdentifier());
 | 
			
		||||
 | 
			
		||||
        if ($refreshToken->id > 0) {
 | 
			
		||||
            throw UniqueTokenIdentifierConstraintViolationException::create();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $data = $refreshTokenEntity->getData();
 | 
			
		||||
 | 
			
		||||
        $accessToken = $this->accessTokenModel->getItemByIdentifier($refreshTokenEntity->getAccessToken());
 | 
			
		||||
 | 
			
		||||
        unset($data['access_token_identifier']);
 | 
			
		||||
        $data['access_token_id'] = $accessToken->id;
 | 
			
		||||
 | 
			
		||||
        $this->refreshTokenModel->save($data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function revokeRefreshToken($tokenId)
 | 
			
		||||
    public function revokeRefreshToken($tokenId): void
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: Implement revokeRefreshToken() method.
 | 
			
		||||
        $this->refreshTokenModel->revoke($tokenId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function isRefreshTokenRevoked($tokenId)
 | 
			
		||||
    public function isRefreshTokenRevoked($tokenId): bool
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: Implement isRefreshTokenRevoked() method.
 | 
			
		||||
        $refreshToken = $this->refreshTokenModel->getItemByIdentifier($tokenId);
 | 
			
		||||
 | 
			
		||||
        if (empty($refreshToken->id)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return !!$refreshToken->revoked;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -2,19 +2,107 @@
 | 
			
		||||
 | 
			
		||||
namespace Webmasterskaya\Component\OauthServer\Site\Repository;
 | 
			
		||||
 | 
			
		||||
use Joomla\CMS\Plugin\PluginHelper;
 | 
			
		||||
use Joomla\Event\DispatcherAwareInterface;
 | 
			
		||||
use Joomla\Event\DispatcherAwareTrait;
 | 
			
		||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
 | 
			
		||||
use League\OAuth2\Server\Exception\OAuthServerException;
 | 
			
		||||
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
 | 
			
		||||
use Webmasterskaya\Component\OauthServer\Administrator\Event\Scope\ScopeResolveEvent;
 | 
			
		||||
use Webmasterskaya\Component\OauthServer\Administrator\Model\ClientModel;
 | 
			
		||||
use Webmasterskaya\Component\OauthServer\Site\Entity\Scope;
 | 
			
		||||
 | 
			
		||||
class ScopeRepository implements ScopeRepositoryInterface
 | 
			
		||||
class ScopeRepository implements ScopeRepositoryInterface, DispatcherAwareInterface
 | 
			
		||||
{
 | 
			
		||||
    use DispatcherAwareTrait;
 | 
			
		||||
 | 
			
		||||
    public function getScopeEntityByIdentifier($identifier)
 | 
			
		||||
    private ClientModel $clientModel;
 | 
			
		||||
 | 
			
		||||
    public function __construct(ClientModel $clientModel)
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: Implement getScopeEntityByIdentifier() method.
 | 
			
		||||
        $this->clientModel = $clientModel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getScopeEntityByIdentifier($identifier): ?Scope
 | 
			
		||||
    {
 | 
			
		||||
        $defined = ['userinfo', 'email'];
 | 
			
		||||
 | 
			
		||||
        if (!in_array($identifier, $defined)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $scope = new Scope();
 | 
			
		||||
        $scope->setIdentifier($identifier);
 | 
			
		||||
 | 
			
		||||
        return $scope;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param Scope[] $scopes
 | 
			
		||||
     * @param $grantType
 | 
			
		||||
     * @param \League\OAuth2\Server\Entities\ClientEntityInterface $clientEntity
 | 
			
		||||
     * @param null $userIdentifier
 | 
			
		||||
     * @return mixed
 | 
			
		||||
     * @throws \League\OAuth2\Server\Exception\OAuthServerException
 | 
			
		||||
     * @since version
 | 
			
		||||
     */
 | 
			
		||||
    public function finalizeScopes(array $scopes, $grantType, ClientEntityInterface $clientEntity, $userIdentifier = null)
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: Implement finalizeScopes() method.
 | 
			
		||||
        $client = $this->clientModel->getItemByIdentifier($clientEntity->getIdentifier());
 | 
			
		||||
 | 
			
		||||
        $scopes = $this->setupScopes($client, array_values($scopes));
 | 
			
		||||
 | 
			
		||||
        PluginHelper::importPlugin('oauthserver');
 | 
			
		||||
 | 
			
		||||
        $event = new ScopeResolveEvent(
 | 
			
		||||
            $scopes,
 | 
			
		||||
            $grantType,
 | 
			
		||||
            $client,
 | 
			
		||||
            $userIdentifier
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return $this->getDispatcher()
 | 
			
		||||
            ->dispatch($event->getName(), $event)
 | 
			
		||||
            ->getArgument('scopes', []);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param object $client
 | 
			
		||||
     * @param array $requestedScopes
 | 
			
		||||
     * @return array
 | 
			
		||||
     * @throws \League\OAuth2\Server\Exception\OAuthServerException
 | 
			
		||||
     * @since version
 | 
			
		||||
     */
 | 
			
		||||
    private function setupScopes(object $client, array $requestedScopes): array
 | 
			
		||||
    {
 | 
			
		||||
        $clientScopes = $client->scopes;
 | 
			
		||||
 | 
			
		||||
        if (empty($clientScopes)) {
 | 
			
		||||
            return $requestedScopes;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $clientScopes = array_map(function ($item) {
 | 
			
		||||
            $scope = new Scope();
 | 
			
		||||
            $scope->setIdentifier((string)$item);
 | 
			
		||||
            return $scope;
 | 
			
		||||
        }, $clientScopes);
 | 
			
		||||
 | 
			
		||||
        if (empty($requestedScopes)) {
 | 
			
		||||
            return $clientScopes;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $finalizedScopes = [];
 | 
			
		||||
        $clientScopesAsStrings = array_map('strval', $clientScopes);
 | 
			
		||||
 | 
			
		||||
        foreach ($requestedScopes as $requestedScope) {
 | 
			
		||||
            $requestedScopeAsString = (string)$requestedScope;
 | 
			
		||||
            if (!\in_array($requestedScopeAsString, $clientScopesAsStrings, true)) {
 | 
			
		||||
                throw OAuthServerException::invalidScope($requestedScopeAsString);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $finalizedScopes[] = $requestedScope;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $finalizedScopes;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -16,6 +16,7 @@
 | 
			
		||||
  "minimum-stability": "stable",
 | 
			
		||||
  "require": {
 | 
			
		||||
    "php": "^8.0",
 | 
			
		||||
    "league/oauth2-server": "^8.5"
 | 
			
		||||
    "league/oauth2-server": "^8.5",
 | 
			
		||||
    "ext-openssl": "*"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user