mirror of
https://github.com/webmasterskaya/joomla-oauth-server.git
synced 2025-04-18 20:33:04 +03:00
404 lines
16 KiB
PHP
404 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* @package Joomla.Site
|
|
* @subpackage com_oauthserver
|
|
*
|
|
* @copyright (c) 2024. Webmasterskaya. <https://webmasterskaya.xyz>
|
|
* @license MIT; see LICENSE.txt
|
|
**/
|
|
|
|
namespace Webmasterskaya\Component\OauthServer\Site\Controller;
|
|
|
|
use Joomla\CMS\Application\CMSApplication;
|
|
use Joomla\CMS\Application\SiteApplication;
|
|
use Joomla\CMS\Component\ComponentHelper;
|
|
use Joomla\CMS\Factory;
|
|
use Joomla\CMS\Log\Log;
|
|
use Joomla\CMS\MVC\Controller\BaseController;
|
|
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
|
use Joomla\CMS\Router\Route;
|
|
use Joomla\CMS\Uri\Uri;
|
|
use Joomla\CMS\User\UserFactoryInterface;
|
|
use Joomla\Input\Input;
|
|
use Laminas\Diactoros\ServerRequest;
|
|
use Laminas\Diactoros\ServerRequestFactory;
|
|
use League\OAuth2\Server\AuthorizationServer;
|
|
use League\OAuth2\Server\CryptKey;
|
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
|
use League\OAuth2\Server\Grant\AuthCodeGrant;
|
|
use League\OAuth2\Server\Grant\ClientCredentialsGrant;
|
|
use League\OAuth2\Server\Grant\ImplicitGrant;
|
|
use League\OAuth2\Server\Grant\RefreshTokenGrant;
|
|
use League\OAuth2\Server\RequestEvent as LeagueRequestEvent;
|
|
use League\OAuth2\Server\ResourceServer;
|
|
use Webmasterskaya\Component\OauthServer\Administrator\Event\RequestAccessTokenEvent;
|
|
use Webmasterskaya\Component\OauthServer\Administrator\Event\RequestEvent;
|
|
use Webmasterskaya\Component\OauthServer\Administrator\Event\RequestRefreshTokenEvent;
|
|
use Webmasterskaya\Component\OauthServer\Administrator\Event\ResolveTokenRequestEvent;
|
|
use Webmasterskaya\Component\OauthServer\Administrator\Model\AccessTokenModel;
|
|
use Webmasterskaya\Component\OauthServer\Administrator\Model\AuthCodeModel;
|
|
use Webmasterskaya\Component\OauthServer\Administrator\Model\ClientModel;
|
|
use Webmasterskaya\Component\OauthServer\Administrator\Model\RefreshTokenModel;
|
|
use Webmasterskaya\Component\OauthServer\Site\Entity\User;
|
|
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;
|
|
|
|
\defined('_JEXEC') or die;
|
|
|
|
class LoginController extends BaseController
|
|
{
|
|
private AuthorizationServer $authorizationServer;
|
|
|
|
/**
|
|
* @throws \Exception
|
|
* @since version
|
|
*/
|
|
public function __construct($config = [], MVCFactoryInterface $factory = null, ?CMSApplication $app = null, ?Input $input = null)
|
|
{
|
|
parent::__construct($config, $factory, $app, $input);
|
|
|
|
$this->setupAuthorizationServer();
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
* @throws \Exception
|
|
* @since version
|
|
*/
|
|
private function setupAuthorizationServer()
|
|
{
|
|
if (isset($authorizationServer))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Init our repositories
|
|
/**
|
|
* @var ClientModel $clientModel
|
|
* @var AccessTokenModel $accessTokenModel
|
|
* @var AuthCodeModel $authCodeModel
|
|
* @var RefreshTokenModel $refreshTokenModel
|
|
*/
|
|
$clientModel = $this->factory->createModel('Client', 'Administrator', ['request_ignore' => true]);
|
|
$clientRepository = new ClientRepository($clientModel);
|
|
|
|
$accessTokenModel = $this->factory->createModel('AccessToken', 'Administrator', ['request_ignore' => true]);
|
|
$accessTokenRepository = new AccessTokenRepository($accessTokenModel, $clientModel);
|
|
|
|
$scopeRepository = new ScopeRepository($clientModel);
|
|
$scopeRepository->setDispatcher($this->getDispatcher());
|
|
|
|
$authCodeModel = $this->factory->createModel('AuthCode', 'Administrator', ['request_ignore' => true]);
|
|
$authCodeRepository = new AuthCodeRepository($authCodeModel, $clientModel);
|
|
|
|
$refreshTokenModel = $this->factory->createModel('RefreshToken', 'Administrator', ['request_ignore' => true]);
|
|
$refreshTokenRepository = new RefreshTokenRepository($refreshTokenModel, $accessTokenModel);
|
|
|
|
$params = ComponentHelper::getParams('com_oauthserver');
|
|
|
|
if ($params->get('key_method_paste'))
|
|
{
|
|
$private_key = $params->get('private_key_raw');
|
|
}
|
|
else
|
|
{
|
|
$private_key = $params->get('private_key_path');
|
|
}
|
|
|
|
if (!!($private_key_passphrase = $params->get('private_key_passphrase')))
|
|
{
|
|
$private_key = new CryptKey($private_key, $private_key_passphrase);
|
|
}
|
|
|
|
$encryption_key = $this->app->get('secret');
|
|
|
|
$server = new AuthorizationServer(
|
|
$clientRepository,
|
|
$accessTokenRepository,
|
|
$scopeRepository,
|
|
$private_key,
|
|
$encryption_key
|
|
);
|
|
|
|
$access_token_ttl = $params->get('access_token_ttl', 'PT1H');
|
|
|
|
if (!!$params->get('enable_auth_code_grant', true))
|
|
{
|
|
$grant = new AuthCodeGrant(
|
|
$authCodeRepository,
|
|
$refreshTokenRepository,
|
|
new \DateInterval($params->get('auth_code_ttl', 'PT10M')) // authorization codes will expire after 10 minutes
|
|
);
|
|
|
|
$grant->setRefreshTokenTTL(new \DateInterval($params->get('refresh_token_ttl', 'P1M')));
|
|
|
|
$server->enableGrantType(
|
|
$grant,
|
|
new \DateInterval($access_token_ttl)
|
|
);
|
|
}
|
|
|
|
if (!!$params->get('enable_refresh_token_grant', false))
|
|
{
|
|
$grant = new RefreshTokenGrant($refreshTokenRepository);
|
|
|
|
$grant->setRefreshTokenTTL(new \DateInterval($params->get('refresh_token_ttl', 'P1M')));
|
|
|
|
$server->enableGrantType(
|
|
$grant,
|
|
new \DateInterval($access_token_ttl)
|
|
);
|
|
}
|
|
|
|
if (!!$params->get('enable_client_credentials_grant', false))
|
|
{
|
|
$server->enableGrantType(
|
|
new ClientCredentialsGrant(),
|
|
new \DateInterval($access_token_ttl)
|
|
);
|
|
}
|
|
|
|
if (!!$params->get('enable_implicit_grant', false))
|
|
{
|
|
$server->enableGrantType(
|
|
new ImplicitGrant(new \DateInterval($access_token_ttl)),
|
|
new \DateInterval($access_token_ttl)
|
|
);
|
|
}
|
|
|
|
$server->getEmitter()
|
|
->addListener(
|
|
LeagueRequestEvent::USER_AUTHENTICATION_FAILED,
|
|
function (LeagueRequestEvent $event) {
|
|
$name = 'onOauthUserAuthenticationFailed';
|
|
$this->getDispatcher()->dispatch($name, new RequestEvent(
|
|
$name,
|
|
['request' => $event->getRequest()]
|
|
));
|
|
}
|
|
)->addListener(
|
|
LeagueRequestEvent::CLIENT_AUTHENTICATION_FAILED,
|
|
function (LeagueRequestEvent $event) {
|
|
$name = 'onOauthClientAuthenticationFailed';
|
|
$this->getDispatcher()->dispatch($name, new RequestEvent(
|
|
$name,
|
|
['request' => $event->getRequest()]
|
|
));
|
|
}
|
|
)->addListener(
|
|
LeagueRequestEvent::REFRESH_TOKEN_CLIENT_FAILED,
|
|
function (LeagueRequestEvent $event) {
|
|
$name = 'onOauthRefreshTokenClientFailed';
|
|
$this->getDispatcher()->dispatch($name, new RequestEvent(
|
|
$name,
|
|
['request' => $event->getRequest()]
|
|
));
|
|
}
|
|
)->addListener(
|
|
LeagueRequestEvent::REFRESH_TOKEN_ISSUED,
|
|
function (LeagueRequestEvent $event) {
|
|
/** @var \League\OAuth2\Server\RequestRefreshTokenEvent $event */
|
|
$name = 'onOauthRefreshTokenIssued';
|
|
$this->getDispatcher()->dispatch($name, new RequestRefreshTokenEvent(
|
|
$name,
|
|
[
|
|
'request' => $event->getRequest(),
|
|
'refreshToken' => $event->getRefreshToken()
|
|
]
|
|
));
|
|
}
|
|
)->addListener(
|
|
LeagueRequestEvent::ACCESS_TOKEN_ISSUED,
|
|
function (LeagueRequestEvent $event) {
|
|
/** @var \League\OAuth2\Server\RequestAccessTokenEvent $event */
|
|
$name = 'onOauthAccessTokenIssued';
|
|
$this->getDispatcher()->dispatch($name, new RequestAccessTokenEvent(
|
|
$name,
|
|
[
|
|
'request' => $event->getRequest(),
|
|
'accessToken' => $event->getAccessToken()
|
|
]
|
|
));
|
|
}
|
|
);
|
|
|
|
$this->authorizationServer = $server;
|
|
}
|
|
|
|
/**
|
|
* @return LoginController
|
|
* @throws OAuthServerException
|
|
* @since version
|
|
*/
|
|
public function authorize(): static
|
|
{
|
|
$app = $this->app;
|
|
$input = $app->getInput();
|
|
$user = $app->getIdentity();
|
|
$uri = Uri::getInstance();
|
|
$state_prefix = 'oauthserver.login.authorize.request';
|
|
|
|
// Create PSR-7 Request object and store all query params in user state, to use it after user login is it required.
|
|
$serverRequest = (new ServerRequest([], [], $app->getUserState("$state_prefix.uri", (string) $uri)))
|
|
->withQueryParams([
|
|
'response_type' => $app->getUserStateFromRequest("$state_prefix.response_type", 'response_type'),
|
|
'client_id' => $app->getUserStateFromRequest("$state_prefix.client_id", 'client_id', $input->server->get('PHP_AUTH_USER')),
|
|
'redirect_uri' => $app->getUserStateFromRequest("$state_prefix.redirect_uri", 'redirect_uri'),
|
|
'scope' => $app->getUserStateFromRequest("$state_prefix.scope", 'scope'),
|
|
'code_challenge' => $app->getUserStateFromRequest("$state_prefix.code_challenge", 'code_challenge'),
|
|
'code_challenge_method' => $app->getUserStateFromRequest("$state_prefix.code_challenge_method", 'code_challenge_method', 'plain'),
|
|
'state' => $app->getUserStateFromRequest("$state_prefix.state", 'state', ''),
|
|
]);
|
|
|
|
if (!$user->id)
|
|
{
|
|
if ($app->getUserState("$state_prefix.uri") === null)
|
|
{
|
|
$app->setUserState("$state_prefix.uri", (string) $uri);
|
|
}
|
|
|
|
// Build the cleared current uri and encode to pass it to the login form as a callback uri.
|
|
$return = http_build_query(['return' => base64_encode($uri->toString(['scheme', 'user', 'pass', 'host', 'port', 'path']))]);
|
|
$redirect = Route::_('index.php?option=com_users&view=login&' . $return);
|
|
|
|
// The current page is not tied to any menu item, so the main page item id will be added to the route. It needs to be removed.
|
|
$redirect = preg_replace('/((&|&)itemid=\d+)/i', '', $redirect);
|
|
|
|
$app->enqueueMessage('Необходимо авторизоваться!');
|
|
$app->redirect($redirect);
|
|
|
|
return $this;
|
|
}
|
|
|
|
// Clean user state after login checks
|
|
$app->setUserState($state_prefix, null);
|
|
|
|
try
|
|
{
|
|
$server = $this->authorizationServer;
|
|
|
|
// Validate the HTTP request and return an AuthorizationRequest object.
|
|
$authRequest = $server->validateAuthorizationRequest($serverRequest);
|
|
|
|
// The auth request object can be serialized and saved into a user's session.
|
|
// You will probably want to redirect the user at this point to a login endpoint.
|
|
|
|
// Once the user has logged in set the user on the AuthorizationRequest
|
|
$authRequest->setUser(new User($user)); // an instance of UserEntityInterface
|
|
|
|
// At this point you should redirect the user to an authorization page.
|
|
// This form will ask the user to approve the client and the scopes requested.
|
|
|
|
// Once the user has approved or denied the client update the status
|
|
// (true = approved, false = denied)
|
|
$authRequest->setAuthorizationApproved(true);
|
|
|
|
$app->setResponse($server->completeAuthorizationRequest($authRequest, $app->getResponse()));
|
|
}
|
|
catch (OAuthServerException $e)
|
|
{
|
|
$this->handleOAuthServerException($e);
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return LoginController
|
|
* @throws \Exception
|
|
* @since version
|
|
*/
|
|
public function token(): static
|
|
{
|
|
$server = $this->authorizationServer;
|
|
$serverRequest = ServerRequestFactory::fromGlobals();
|
|
|
|
try
|
|
{
|
|
$response = $this->app->getResponse();
|
|
$response = $server->respondToAccessTokenRequest($serverRequest, $response);
|
|
$event = new ResolveTokenRequestEvent('onResolveTokenRequest', ['response' => $response]);
|
|
|
|
$this->getDispatcher()->dispatch($event->getName(), $event);
|
|
$this->app->setResponse($event->getArgument('response'));
|
|
|
|
echo $response->getBody();
|
|
}
|
|
catch (OAuthServerException $e)
|
|
{
|
|
$this->handleOAuthServerException($e);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @throws OAuthServerException
|
|
* @throws \Exception
|
|
* @since version
|
|
*/
|
|
public function profile(): LoginController
|
|
{
|
|
/**
|
|
* @var ClientModel $clientModel
|
|
* @var AccessTokenModel $accessTokenModel
|
|
*/
|
|
$clientModel = $this->factory->createModel('Client', 'Administrator', ['request_ignore' => true]);
|
|
$accessTokenModel = $this->factory->createModel('AccessToken', 'Administrator', ['request_ignore' => true]);
|
|
$accessTokenRepository = new AccessTokenRepository($accessTokenModel, $clientModel);
|
|
$params = ComponentHelper::getParams('com_oauthserver');
|
|
|
|
if ($params->get('key_method_paste'))
|
|
{
|
|
$public_key = $params->get('public_key_raw');
|
|
}
|
|
else
|
|
{
|
|
$public_key = $params->get('public_key_path');
|
|
}
|
|
|
|
$server = new ResourceServer($accessTokenRepository, $public_key);
|
|
$request = ServerRequestFactory::fromGlobals();
|
|
$request = $server->validateAuthenticatedRequest($request);
|
|
/** @var \Joomla\CMS\User\User $user */
|
|
$user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($request->getAttribute('oauth_user_id'));
|
|
|
|
$this->app->loadIdentity($user);
|
|
|
|
$data = [
|
|
'full_name' => $user->name,
|
|
'email' => $user->email,
|
|
'login' => $user->username,
|
|
'id' => $user->id,
|
|
];
|
|
|
|
echo json_encode($data);
|
|
|
|
return $this;
|
|
}
|
|
|
|
protected function handleOAuthServerException(OAuthServerException $exception)
|
|
{
|
|
/** @var SiteApplication $app */
|
|
$app = $this->app;
|
|
|
|
$app->setResponse($exception->generateHttpResponse($app->getResponse()));
|
|
|
|
$message = $exception->getMessage();
|
|
|
|
if (($hint = $exception->getHint()) !== null)
|
|
{
|
|
$message .= ' ' . $hint;
|
|
}
|
|
|
|
Log::add($message, Log::ERROR, 'com_oauthserver');
|
|
|
|
throw new \RuntimeException($message, $exception->getHttpStatusCode(), $exception);
|
|
}
|
|
}
|