diff --git a/com_oauthserver/administrator/src/Event/Scope/ScopeResolveEvent.php b/com_oauthserver/administrator/src/Event/Scope/ScopeResolveEvent.php new file mode 100644 index 0000000..50ae660 --- /dev/null +++ b/com_oauthserver/administrator/src/Event/Scope/ScopeResolveEvent.php @@ -0,0 +1,87 @@ + $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 + ) + ); + } +} \ No newline at end of file diff --git a/com_oauthserver/administrator/src/Model/AuthCodeModel.php b/com_oauthserver/administrator/src/Model/AuthCodeModel.php new file mode 100644 index 0000000..950e3a2 --- /dev/null +++ b/com_oauthserver/administrator/src/Model/AuthCodeModel.php @@ -0,0 +1,64 @@ +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()); + } + } +} \ No newline at end of file diff --git a/com_oauthserver/administrator/src/Model/GetItemByIdentifierTrait.php b/com_oauthserver/administrator/src/Model/GetItemByIdentifierTrait.php index 1576af8..fcddff1 100644 --- a/com_oauthserver/administrator/src/Model/GetItemByIdentifierTrait.php +++ b/com_oauthserver/administrator/src/Model/GetItemByIdentifierTrait.php @@ -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]); } diff --git a/com_oauthserver/administrator/src/Model/RefreshTokenModel.php b/com_oauthserver/administrator/src/Model/RefreshTokenModel.php new file mode 100644 index 0000000..ae66ae7 --- /dev/null +++ b/com_oauthserver/administrator/src/Model/RefreshTokenModel.php @@ -0,0 +1,38 @@ +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; + } +} \ No newline at end of file diff --git a/com_oauthserver/administrator/src/Model/ScopeModel.php b/com_oauthserver/administrator/src/Model/ScopeModel.php new file mode 100644 index 0000000..d5bac94 --- /dev/null +++ b/com_oauthserver/administrator/src/Model/ScopeModel.php @@ -0,0 +1,26 @@ +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 "
";
+
+        var_dump();
+
         die();
     }
 }
\ No newline at end of file
diff --git a/com_oauthserver/site/src/Entity/AuthCode.php b/com_oauthserver/site/src/Entity/AuthCode.php
new file mode 100644
index 0000000..2aafdb4
--- /dev/null
+++ b/com_oauthserver/site/src/Entity/AuthCode.php
@@ -0,0 +1,26 @@
+ $this->getIdentifier(),
+            'expiry' => $this->getExpiryDateTime(),
+            'user_id' => $this->getUserIdentifier(),
+            'scopes' => $this->getScopes(),
+            'client_identifier' => $this->getClient()->getIdentifier()
+        ];
+    }
+}
\ No newline at end of file
diff --git a/com_oauthserver/site/src/Entity/RefreshToken.php b/com_oauthserver/site/src/Entity/RefreshToken.php
new file mode 100644
index 0000000..1d23dc3
--- /dev/null
+++ b/com_oauthserver/site/src/Entity/RefreshToken.php
@@ -0,0 +1,22 @@
+ $this->getIdentifier(),
+            'expiry' => $this->getExpiryDateTime(),
+            'access_token_identifier' => $this->getAccessToken()->getIdentifier()
+        ];
+    }
+}
\ No newline at end of file
diff --git a/com_oauthserver/site/src/Entity/Scope.php b/com_oauthserver/site/src/Entity/Scope.php
new file mode 100644
index 0000000..88d5c97
--- /dev/null
+++ b/com_oauthserver/site/src/Entity/Scope.php
@@ -0,0 +1,48 @@
+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;
+    }
+
+
+}
\ No newline at end of file
diff --git a/com_oauthserver/site/src/Entity/User.php b/com_oauthserver/site/src/Entity/User.php
new file mode 100644
index 0000000..b185fa4
--- /dev/null
+++ b/com_oauthserver/site/src/Entity/User.php
@@ -0,0 +1,16 @@
+setIdentifier($user->id);
+    }
+}
\ No newline at end of file
diff --git a/com_oauthserver/site/src/Repository/AuthCodeRepository.php b/com_oauthserver/site/src/Repository/AuthCodeRepository.php
index 92aeba0..f92fe88 100644
--- a/com_oauthserver/site/src/Repository/AuthCodeRepository.php
+++ b/com_oauthserver/site/src/Repository/AuthCodeRepository.php
@@ -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;
     }
 }
\ No newline at end of file
diff --git a/com_oauthserver/site/src/Repository/RefreshTokenRepository.php b/com_oauthserver/site/src/Repository/RefreshTokenRepository.php
index 31ec9e3..704f612 100644
--- a/com_oauthserver/site/src/Repository/RefreshTokenRepository.php
+++ b/com_oauthserver/site/src/Repository/RefreshTokenRepository.php
@@ -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;
     }
 }
\ No newline at end of file
diff --git a/com_oauthserver/site/src/Repository/ScopeRepository.php b/com_oauthserver/site/src/Repository/ScopeRepository.php
index a606365..2bb7df0 100644
--- a/com_oauthserver/site/src/Repository/ScopeRepository.php
+++ b/com_oauthserver/site/src/Repository/ScopeRepository.php
@@ -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;
     }
 }
\ No newline at end of file
diff --git a/lib_oauthserver/composer.json b/lib_oauthserver/composer.json
index 134816f..465ef23 100644
--- a/lib_oauthserver/composer.json
+++ b/lib_oauthserver/composer.json
@@ -16,6 +16,7 @@
   "minimum-stability": "stable",
   "require": {
     "php": "^8.0",
-    "league/oauth2-server": "^8.5"
+    "league/oauth2-server": "^8.5",
+    "ext-openssl": "*"
   }
 }