Compare commits
No commits in common. "ecd1da9cb96d838b3da70fb1051ce9bf51ca524d" and "ec4d3806ce539f7f3111f19a1bc9aa1e8e2406f8" have entirely different histories.
ecd1da9cb9
...
ec4d3806ce
@ -21,4 +21,3 @@ RECAPTCHA_SITEKEY=your_recaptcha_sitekey
|
|||||||
RECAPTCHA_SECRET=your_recaptcha_secret
|
RECAPTCHA_SECRET=your_recaptcha_secret
|
||||||
JWT_RSA_PRIVATE_KEY=jwt-rsa256-private.pem
|
JWT_RSA_PRIVATE_KEY=jwt-rsa256-private.pem
|
||||||
JWT_RSA_PUBLIC_KEY=jwt-rsa256-public.pem
|
JWT_RSA_PUBLIC_KEY=jwt-rsa256-public.pem
|
||||||
JWT_KEY_KID=1
|
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
CREATE TABLE `oauth_sessions` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`client_id` varchar(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
|
||||||
`scope` varchar(255) NOT NULL DEFAULT '',
|
|
||||||
`nonce` varchar(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
|
||||||
`user_id` int(10) unsigned DEFAULT NULL,
|
|
||||||
`code` varchar(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
|
||||||
`code_challenge` varchar(128) NULL,
|
|
||||||
`code_challenge_method` enum('plain', 'S256') NULL,
|
|
||||||
`token_claimed` tinyint(1) NOT NULL DEFAULT 0,
|
|
||||||
`created` timestamp NOT NULL DEFAULT current_timestamp(),
|
|
||||||
`expires` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `code` (`code`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
|
|
||||||
ALTER TABLE `oauth_tokens`
|
|
||||||
ADD `session_id` int(10) unsigned NULL,
|
|
||||||
ADD CONSTRAINT `oauth_tokens_session_id` FOREIGN KEY (`session_id`) REFERENCES `oauth_sessions` (`id`),
|
|
||||||
DROP INDEX `code`,
|
|
||||||
DROP INDEX `access_token`,
|
|
||||||
DROP `scope`,
|
|
||||||
DROP `nonce`,
|
|
||||||
DROP `user_id`,
|
|
||||||
DROP `code`,
|
|
||||||
DROP `access_token`;
|
|
@ -2,23 +2,6 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
"@fortawesome/fontawesome-free@^6.4.0":
|
|
||||||
version "6.7.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.1.tgz#160a48730d533ec77578ed0141661a8f0150a71d"
|
|
||||||
integrity sha512-ALIk/MOh5gYe1TG/ieS5mVUsk7VUIJTJKPMK9rFFqOgfp0Q3d5QiBXbcOMwUvs37fyZVCz46YjOE6IFeOAXCHA==
|
|
||||||
|
|
||||||
"@orchidjs/sifter@^1.1.0":
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@orchidjs/sifter/-/sifter-1.1.0.tgz#b36154ad0cda4898305d1ac44f318b41048a0438"
|
|
||||||
integrity sha512-mYwHCfr736cIWWdhhSZvDbf90AKt2xyrJspKFC3qyIJG1LtrJeJunYEqCGG4Aq2ijENbc4WkOjszcvNaIAS/pQ==
|
|
||||||
dependencies:
|
|
||||||
"@orchidjs/unicode-variants" "^1.1.2"
|
|
||||||
|
|
||||||
"@orchidjs/unicode-variants@^1.1.2":
|
|
||||||
version "1.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@orchidjs/unicode-variants/-/unicode-variants-1.1.2.tgz#1fd71791a67fdd1591ebe0dcaadd3964537a824e"
|
|
||||||
integrity sha512-5DobW1CHgnBROOEpFlEXytED5OosEWESFvg/VYmH0143oXcijYTprRYJTs+55HzGM4IqxiLFSuqEzu9mPNwVsA==
|
|
||||||
|
|
||||||
leaflet.markercluster@^1.4.1:
|
leaflet.markercluster@^1.4.1:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/leaflet.markercluster/-/leaflet.markercluster-1.4.1.tgz#b53f2c4f2ca7306ddab1dbb6f1861d5e8aa6c5e5"
|
resolved "https://registry.yarnpkg.com/leaflet.markercluster/-/leaflet.markercluster-1.4.1.tgz#b53f2c4f2ca7306ddab1dbb6f1861d5e8aa6c5e5"
|
||||||
@ -28,11 +11,3 @@ leaflet@^1.6.0:
|
|||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.6.0.tgz#aecbb044b949ec29469eeb31c77a88e2f448f308"
|
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.6.0.tgz#aecbb044b949ec29469eeb31c77a88e2f448f308"
|
||||||
integrity sha512-CPkhyqWUKZKFJ6K8umN5/D2wrJ2+/8UIpXppY7QDnUZW5bZL5+SEI2J7GBpwh4LIupOKqbNSQXgqmrEJopHVNQ==
|
integrity sha512-CPkhyqWUKZKFJ6K8umN5/D2wrJ2+/8UIpXppY7QDnUZW5bZL5+SEI2J7GBpwh4LIupOKqbNSQXgqmrEJopHVNQ==
|
||||||
|
|
||||||
tom-select@^2.2.2:
|
|
||||||
version "2.4.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/tom-select/-/tom-select-2.4.1.tgz#6a0b6df8af3df7b09b22dd965eb75ce4d1c547bc"
|
|
||||||
integrity sha512-adI8H8+wk8RRzHYLQ3bXSk2Q+FAq/kzAATrcWlJ2fbIrEzb0VkwaXzKHTAlBwSJrhqbPJvhV/0eypFkED/nAug==
|
|
||||||
dependencies:
|
|
||||||
"@orchidjs/sifter" "^1.1.0"
|
|
||||||
"@orchidjs/unicode-variants" "^1.1.2"
|
|
||||||
|
@ -5,8 +5,6 @@ use SokoWeb\Database\Query\Modify;
|
|||||||
use SokoWeb\Database\Query\Select;
|
use SokoWeb\Database\Query\Select;
|
||||||
use SokoWeb\Interfaces\Database\IResultSet;
|
use SokoWeb\Interfaces\Database\IResultSet;
|
||||||
use RVR\Repository\UserPasswordResetterRepository;
|
use RVR\Repository\UserPasswordResetterRepository;
|
||||||
use RVR\Repository\OAuthTokenRepository;
|
|
||||||
use RVR\Repository\OAuthSessionRepository;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
@ -15,10 +13,6 @@ class MaintainDatabaseCommand extends Command
|
|||||||
{
|
{
|
||||||
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
||||||
|
|
||||||
private OAuthTokenRepository $oauthTokenRepository;
|
|
||||||
|
|
||||||
private OAuthSessionRepository $oauthSessionRepository;
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
@ -37,8 +31,6 @@ class MaintainDatabaseCommand extends Command
|
|||||||
try {
|
try {
|
||||||
$this->deleteExpiredPasswordResetters();
|
$this->deleteExpiredPasswordResetters();
|
||||||
$this->deleteExpiredSessions();
|
$this->deleteExpiredSessions();
|
||||||
$this->deleteExpiredOauthTokens();
|
|
||||||
$this->deleteExpiredOauthSessions();
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$output->writeln('<error>Maintenance failed!</error>');
|
$output->writeln('<error>Maintenance failed!</error>');
|
||||||
$output->writeln('');
|
$output->writeln('');
|
||||||
@ -67,7 +59,7 @@ class MaintainDatabaseCommand extends Command
|
|||||||
//TODO: model may be used for sessions too
|
//TODO: model may be used for sessions too
|
||||||
$select = new Select(\Container::$dbConnection, 'sessions');
|
$select = new Select(\Container::$dbConnection, 'sessions');
|
||||||
$select->columns(['id']);
|
$select->columns(['id']);
|
||||||
$select->where('updated', '<', (new DateTime('-1 days'))->format('Y-m-d H:i:s'));
|
$select->where('updated', '<', (new DateTime('-7 days'))->format('Y-m-d H:i:s'));
|
||||||
|
|
||||||
$result = $select->execute();
|
$result = $select->execute();
|
||||||
|
|
||||||
@ -77,21 +69,4 @@ class MaintainDatabaseCommand extends Command
|
|||||||
$modify->delete();
|
$modify->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deleteExpiredOauthTokens(): void
|
|
||||||
{
|
|
||||||
foreach ($this->oauthTokenRepository->getAllExpired() as $oauthToken) {
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($oauthToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function deleteExpiredOauthSessions(): void
|
|
||||||
{
|
|
||||||
foreach ($this->oauthSessionRepository->getAllExpired() as $oauthSession) {
|
|
||||||
if ($this->oauthTokenRepository->countAllBySession($oauthSession) > 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($oauthSession);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
79
src/Controller/OAuthAuthController.php
Normal file
79
src/Controller/OAuthAuthController.php
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?php namespace RVR\Controller;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use RVR\PersistentData\Model\OAuthToken;
|
||||||
|
use RVR\PersistentData\Model\User;
|
||||||
|
use RVR\Repository\OAuthClientRepository;
|
||||||
|
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
||||||
|
use SokoWeb\Interfaces\Response\IRedirect;
|
||||||
|
use SokoWeb\Response\Redirect;
|
||||||
|
use SokoWeb\Response\HtmlContent;
|
||||||
|
|
||||||
|
class OAuthAuthController implements IAuthenticationRequired
|
||||||
|
{
|
||||||
|
private OAuthClientRepository $oAuthClientRepository;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->oAuthClientRepository = new OAuthClientRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isAuthenticationRequired(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function auth()
|
||||||
|
{
|
||||||
|
$redirectUri = \Container::$request->query('redirect_uri');
|
||||||
|
$clientId = \Container::$request->query('client_id');
|
||||||
|
$scope = \Container::$request->query('scope') ? \Container::$request->query('scope'): '';
|
||||||
|
$state = \Container::$request->query('state');
|
||||||
|
$nonce = \Container::$request->query('nonce') ? \Container::$request->query('nonce'): '';
|
||||||
|
|
||||||
|
if (!$clientId || !$redirectUri || !$state) {
|
||||||
|
return new HtmlContent('oauth/oauth_error', ['error' => 'An invalid request was made. Please start authentication again.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$client = $this->oAuthClientRepository->getByClientId($clientId);
|
||||||
|
if ($client === null) {
|
||||||
|
return new HtmlContent('oauth/oauth_error', ['error' => 'Client is not authorized.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$redirectUriParsed = parse_url($redirectUri);
|
||||||
|
$redirectUriBase = $redirectUriParsed['scheme'] . '://' . $redirectUriParsed['host'] . $redirectUriParsed['path'];
|
||||||
|
$redirectUriQuery = [];
|
||||||
|
if (isset($redirectUriParsed['query'])) {
|
||||||
|
parse_str($redirectUriParsed['query'], $redirectUriQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($redirectUriBase, $client->getRedirectUrisArray())) {
|
||||||
|
return new HtmlContent('oauth/oauth_error', ['error' => 'Redirect URI \'' . $redirectUriBase .'\' is not allowed for this client.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ?User $user
|
||||||
|
*/
|
||||||
|
$user = \Container::$request->user();
|
||||||
|
$code = bin2hex(random_bytes(16));
|
||||||
|
$accessToken = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
|
$token = new OAuthToken();
|
||||||
|
$token->setNonce($nonce);
|
||||||
|
$token->setScope($scope);
|
||||||
|
$token->setUser($user);
|
||||||
|
$token->setCode($code);
|
||||||
|
$token->setAccessToken($accessToken);
|
||||||
|
$token->setCreatedDate(new DateTime());
|
||||||
|
$token->setExpiresDate(new DateTime('+5 minutes'));
|
||||||
|
\Container::$persistentDataManager->saveToDb($token);
|
||||||
|
|
||||||
|
$redirectUriQuery = array_merge($redirectUriQuery, [
|
||||||
|
'state' => $state,
|
||||||
|
'code' => $code
|
||||||
|
]);
|
||||||
|
$finalRedirectUri = $redirectUriBase . '?' . http_build_query($redirectUriQuery);
|
||||||
|
|
||||||
|
return new Redirect($finalRedirectUri, IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
}
|
@ -2,16 +2,9 @@
|
|||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use Firebase\JWT\JWT;
|
use Firebase\JWT\JWT;
|
||||||
use Firebase\JWT\Key;
|
|
||||||
use Firebase\JWT\SignatureInvalidException;
|
|
||||||
use Firebase\JWT\BeforeValidException;
|
|
||||||
use Firebase\JWT\ExpiredException;
|
|
||||||
use RVR\Repository\OAuthSessionRepository;
|
|
||||||
use RVR\Repository\OAuthTokenRepository;
|
use RVR\Repository\OAuthTokenRepository;
|
||||||
use RVR\Repository\UserRepository;
|
use RVR\Repository\UserRepository;
|
||||||
use RVR\PersistentData\Model\User;
|
use RVR\PersistentData\Model\User;
|
||||||
use RVR\PersistentData\Model\OAuthSession;
|
|
||||||
use RVR\PersistentData\Model\OAuthToken;
|
|
||||||
use RVR\Repository\OAuthClientRepository;
|
use RVR\Repository\OAuthClientRepository;
|
||||||
use SokoWeb\Interfaces\Response\IContent;
|
use SokoWeb\Interfaces\Response\IContent;
|
||||||
use SokoWeb\Response\JsonContent;
|
use SokoWeb\Response\JsonContent;
|
||||||
@ -20,8 +13,6 @@ class OAuthController
|
|||||||
{
|
{
|
||||||
private OAuthClientRepository $oAuthClientRepository;
|
private OAuthClientRepository $oAuthClientRepository;
|
||||||
|
|
||||||
private OAuthSessionRepository $oAuthSessionRepository;
|
|
||||||
|
|
||||||
private OAuthTokenRepository $oAuthTokenRepository;
|
private OAuthTokenRepository $oAuthTokenRepository;
|
||||||
|
|
||||||
private UserRepository $userRepository;
|
private UserRepository $userRepository;
|
||||||
@ -29,181 +20,60 @@ class OAuthController
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->oAuthClientRepository = new OAuthClientRepository();
|
$this->oAuthClientRepository = new OAuthClientRepository();
|
||||||
$this->oAuthSessionRepository = new OAuthSessionRepository();
|
|
||||||
$this->oAuthTokenRepository = new OAuthTokenRepository();
|
$this->oAuthTokenRepository = new OAuthTokenRepository();
|
||||||
$this->userRepository = new UserRepository();
|
$this->userRepository = new UserRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generateToken(): ?IContent
|
public function getToken(): ?IContent
|
||||||
{
|
{
|
||||||
$credentials = $this->getClientCredentials();
|
$clientId = \Container::$request->post('client_id');
|
||||||
|
$clientSecret = \Container::$request->post('client_secret');
|
||||||
$code = \Container::$request->post('code');
|
$code = \Container::$request->post('code');
|
||||||
$redirectUri = \Container::$request->post('redirect_uri');
|
|
||||||
|
|
||||||
if (!$credentials['clientId'] || !$code || !$redirectUri) {
|
if (!$clientId || !$clientSecret || !$code) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => 'An invalid request was made.'
|
'error' => 'An invalid request was made.'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$client = $this->oAuthClientRepository->getByClientId($credentials['clientId']);
|
$client = $this->oAuthClientRepository->getByClientId($clientId);
|
||||||
if ($client === null) {
|
if ($client === null || $client->getClientSecret() !== $clientSecret) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => 'Client is not found.'
|
'error' => 'Client is not authorized.'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$redirectUriBase = explode('?', $redirectUri)[0];
|
$token = $this->oAuthTokenRepository->getByCode($code);
|
||||||
if (!in_array($redirectUriBase, $client->getRedirectUrisArray())) {
|
if ($token === null || $token->getExpiresDate() < new DateTime()) {
|
||||||
return new JsonContent([
|
|
||||||
'error' => 'Redirect URI \'' . $redirectUriBase .'\' is not allowed for this client.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$session = $this->oAuthSessionRepository->getByCode($code);
|
|
||||||
if ($session === null || $session->getTokenClaimed() || $session->getExpiresDate() < new DateTime()) {
|
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => 'The provided code is invalid.'
|
'error' => 'The provided code is invalid.'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$codeChallenge = $session->getCodeChallenge();
|
$payload = array_merge([
|
||||||
if ($codeChallenge === null && $client->getClientSecret() !== $credentials['clientSecret']) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => 'Client is not authorized.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($session->getClientId() !== $credentials['clientId']) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => 'This code cannot be used by this client!'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($codeChallenge !== null) {
|
|
||||||
$codeVerifier = \Container::$request->post('code_verifier') ?: '';
|
|
||||||
if ($session->getCodeChallengeMethod() === 'S256') {
|
|
||||||
$hash = rtrim(strtr(base64_encode(hash('sha256', $codeVerifier, true)), '+/', '-_'), '=');
|
|
||||||
} else {
|
|
||||||
$hash = $codeVerifier;
|
|
||||||
}
|
|
||||||
if ($codeChallenge !== $hash) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => 'Code challenge failed!'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$session->setTokenClaimed(true);
|
|
||||||
\Container::$persistentDataManager->saveToDb($session);
|
|
||||||
|
|
||||||
$token = new OAuthToken();
|
|
||||||
$token->setSession($session);
|
|
||||||
$token->setCreatedDate(new DateTime());
|
|
||||||
$token->setExpiresDate(new DateTime('+1 hours'));
|
|
||||||
\Container::$persistentDataManager->saveToDb($token);
|
|
||||||
|
|
||||||
$commonPayload = [
|
|
||||||
'iss' => $_ENV['APP_URL'],
|
'iss' => $_ENV['APP_URL'],
|
||||||
'iat' => $token->getCreatedDate()->getTimestamp(),
|
'iat' => (int)$token->getCreatedDate()->getTimestamp(),
|
||||||
'nbf' => $session->getCreatedDate()->getTimestamp(),
|
'nbf' => (int)$token->getCreatedDate()->getTimestamp(),
|
||||||
'exp' => $token->getExpiresDate()->getTimestamp(),
|
'exp' => (int)$token->getExpiresDate()->getTimestamp(),
|
||||||
'aud' => $session->getClientId(),
|
'aud' => $clientId,
|
||||||
'nonce' => $session->getNonce()
|
'nonce' => $token->getNonce()
|
||||||
];
|
], $this->getUserInfoInternal(
|
||||||
$idTokenPayload = array_merge($commonPayload, $this->getUserInfoInternal(
|
$this->userRepository->getById($token->getUserId()),
|
||||||
$this->userRepository->getById($session->getUserId()),
|
$token->getScopeArray())
|
||||||
$session->getScopeArray())
|
|
||||||
);
|
);
|
||||||
$accessTokenPayload = array_merge($commonPayload, [
|
|
||||||
'jti' => $token->getId(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$privateKey = file_get_contents(ROOT . '/' . $_ENV['JWT_RSA_PRIVATE_KEY']);
|
$privateKey = file_get_contents(ROOT . '/' . $_ENV['JWT_RSA_PRIVATE_KEY']);
|
||||||
$idToken = JWT::encode($idTokenPayload, $privateKey, 'RS256', $_ENV['JWT_KEY_KID']);
|
$jwt = JWT::encode($payload, $privateKey, 'RS256');
|
||||||
$accessToken = JWT::encode($accessTokenPayload, $privateKey, 'RS256', $_ENV['JWT_KEY_KID']);
|
|
||||||
|
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'access_token' => $accessToken,
|
'access_token' => $token->getAccessToken(),
|
||||||
'expires_in' => $token->getExpiresDate()->getTimestamp() - (new DateTime())->getTimestamp(),
|
'expires_in' => $token->getExpiresDate()->getTimestamp() - (new DateTime())->getTimestamp(),
|
||||||
'scope' => $session->getScope(),
|
'scope' => $token->getScope(),
|
||||||
'id_token' => $idToken,
|
'id_token' => $jwt,
|
||||||
'token_type' => 'Bearer'
|
'token_type' => 'Bearer'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function introspectToken(): ?IContent
|
|
||||||
{
|
|
||||||
$credentials = $this->getClientCredentials();
|
|
||||||
$accessToken = \Container::$request->post('token');
|
|
||||||
|
|
||||||
if (!$credentials['clientId'] || !$credentials['clientSecret'] || !$accessToken) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => 'An invalid request was made.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$client = $this->oAuthClientRepository->getByClientId($credentials['clientId']);
|
|
||||||
if ($client === null || $client->getClientSecret() !== $credentials['clientSecret']) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => 'Client is not authorized.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$tokenValidated = $this->validateTokenAndSession($accessToken, $token, $session);
|
|
||||||
if (!$tokenValidated) {
|
|
||||||
return new JsonContent([
|
|
||||||
'active' => false
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($session->getClientId() !== $credentials['clientId']) {
|
|
||||||
return new JsonContent([
|
|
||||||
'active' => false
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonContent([
|
|
||||||
'active' => true,
|
|
||||||
'scope' => $session->getScope(),
|
|
||||||
'client_id' => $session->getClientId(),
|
|
||||||
'exp' => $token->getExpiresDate()->getTimestamp(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function revokeToken(): ?IContent
|
|
||||||
{
|
|
||||||
$credentials = $this->getClientCredentials();
|
|
||||||
$accessToken = \Container::$request->post('token');
|
|
||||||
|
|
||||||
if (!$credentials['clientId'] || !$credentials['clientSecret'] || !$accessToken) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => 'An invalid request was made.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$client = $this->oAuthClientRepository->getByClientId($credentials['clientId']);
|
|
||||||
if ($client === null || $client->getClientSecret() !== $credentials['clientSecret']) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => 'Client is not authorized.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$tokenValidated = $this->validateTokenAndSession($accessToken, $token, $session);
|
|
||||||
if (!$tokenValidated) {
|
|
||||||
return new JsonContent([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$session = $this->oAuthSessionRepository->getById($token->getSessionId());
|
|
||||||
if ($session->getClientId() !== $credentials['clientId']) {
|
|
||||||
return new JsonContent([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($token);
|
|
||||||
|
|
||||||
return new JsonContent([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUserInfo() : IContent
|
public function getUserInfo() : IContent
|
||||||
{
|
{
|
||||||
$authorization = \Container::$request->header('Authorization');
|
$authorization = \Container::$request->header('Authorization');
|
||||||
@ -214,8 +84,9 @@ class OAuthController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$accessToken = substr($authorization, strlen('Bearer '));
|
$accessToken = substr($authorization, strlen('Bearer '));
|
||||||
$tokenValidated = $this->validateTokenAndSession($accessToken, $token, $session);
|
$token = $this->oAuthTokenRepository->getByAccessToken($accessToken);
|
||||||
if (!$tokenValidated) {
|
|
||||||
|
if ($token === null || $token->getExpiresDate() < new DateTime()) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => 'The provided access token is invalid.'
|
'error' => 'The provided access token is invalid.'
|
||||||
]);
|
]);
|
||||||
@ -223,8 +94,8 @@ class OAuthController
|
|||||||
|
|
||||||
return new JsonContent(
|
return new JsonContent(
|
||||||
$this->getUserInfoInternal(
|
$this->getUserInfoInternal(
|
||||||
$this->userRepository->getById($session->getUserId()),
|
$this->userRepository->getById($token->getUserId()),
|
||||||
$session->getScopeArray()
|
$token->getScopeArray()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -235,10 +106,7 @@ class OAuthController
|
|||||||
'issuer' => $_ENV['APP_URL'],
|
'issuer' => $_ENV['APP_URL'],
|
||||||
'authorization_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.auth')->generateLink(),
|
'authorization_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.auth')->generateLink(),
|
||||||
'token_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.token')->generateLink(),
|
'token_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.token')->generateLink(),
|
||||||
'introspection_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.token.introspect')->generateLink(),
|
|
||||||
'revocation_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.token.revoke')->generateLink(),
|
|
||||||
'userinfo_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.userinfo')->generateLink(),
|
'userinfo_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.userinfo')->generateLink(),
|
||||||
'end_session_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('logout')->generateLink(),
|
|
||||||
'jwks_uri' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.certs')->generateLink(),
|
'jwks_uri' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.certs')->generateLink(),
|
||||||
'response_types_supported' =>
|
'response_types_supported' =>
|
||||||
[
|
[
|
||||||
@ -260,7 +128,6 @@ class OAuthController
|
|||||||
],
|
],
|
||||||
'token_endpoint_auth_methods_supported' =>
|
'token_endpoint_auth_methods_supported' =>
|
||||||
[
|
[
|
||||||
'client_secret_basic',
|
|
||||||
'client_secret_post',
|
'client_secret_post',
|
||||||
],
|
],
|
||||||
'claims_supported' =>
|
'claims_supported' =>
|
||||||
@ -300,66 +167,13 @@ class OAuthController
|
|||||||
'kty' => 'RSA',
|
'kty' => 'RSA',
|
||||||
'alg' => 'RS256',
|
'alg' => 'RS256',
|
||||||
'use' => 'sig',
|
'use' => 'sig',
|
||||||
'kid' => $_ENV['JWT_KEY_KID'],
|
'kid' => '1',
|
||||||
'n' => str_replace(['+', '/'], ['-', '_'], base64_encode($keyInfo['rsa']['n'])),
|
'n' => str_replace(['+', '/'], ['-', '_'], base64_encode($keyInfo['rsa']['n'])),
|
||||||
'e' => str_replace(['+', '/'], ['-', '_'], base64_encode($keyInfo['rsa']['e'])),
|
'e' => str_replace(['+', '/'], ['-', '_'], base64_encode($keyInfo['rsa']['e'])),
|
||||||
]
|
]
|
||||||
]]);
|
]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getClientCredentials(): array
|
|
||||||
{
|
|
||||||
$authorization = \Container::$request->header('Authorization');
|
|
||||||
if ($authorization !== null) {
|
|
||||||
$basicAuthEncoded = substr($authorization, strlen('Basic '));
|
|
||||||
$basicAuth = explode(':', base64_decode($basicAuthEncoded));
|
|
||||||
if (count($basicAuth) === 2) {
|
|
||||||
$clientId = rawurldecode($basicAuth[0]);
|
|
||||||
$clientSecret = rawurldecode($basicAuth[1]);
|
|
||||||
} else {
|
|
||||||
$clientId = null;
|
|
||||||
$clientSecret = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$clientId = \Container::$request->post('client_id');
|
|
||||||
$clientSecret = \Container::$request->post('client_secret');
|
|
||||||
}
|
|
||||||
return ['clientId' => $clientId, 'clientSecret' => $clientSecret];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function validateTokenAndSession(
|
|
||||||
string $accessToken,
|
|
||||||
?OAuthToken &$token,
|
|
||||||
?OAuthSession &$session): bool
|
|
||||||
{
|
|
||||||
$publicKey = file_get_contents(ROOT . '/' . $_ENV['JWT_RSA_PUBLIC_KEY']);
|
|
||||||
try {
|
|
||||||
$payload = JWT::decode($accessToken, new Key($publicKey, 'RS256'));
|
|
||||||
$token = $this->oAuthTokenRepository->getById($payload->jti);
|
|
||||||
} catch (SignatureInvalidException | BeforeValidException | ExpiredException) {
|
|
||||||
$token = null;
|
|
||||||
} catch (\UnexpectedValueException $e) {
|
|
||||||
error_log($e->getMessage() . ' Token was: ' . $accessToken);
|
|
||||||
$token = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($token === null || $token->getExpiresDate() < new DateTime()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$session = $this->oAuthSessionRepository->getById($token->getSessionId());
|
|
||||||
if ($session === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param User $user
|
|
||||||
* @param string[] $scope
|
|
||||||
* @return array<string, string>
|
|
||||||
*/
|
|
||||||
private function getUserInfoInternal(User $user, array $scope): array
|
private function getUserInfoInternal(User $user, array $scope): array
|
||||||
{
|
{
|
||||||
$userInfo = [];
|
$userInfo = [];
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
<?php namespace RVR\Controller;
|
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use RVR\PersistentData\Model\OAuthSession;
|
|
||||||
use RVR\PersistentData\Model\User;
|
|
||||||
use RVR\Repository\OAuthClientRepository;
|
|
||||||
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
|
||||||
use SokoWeb\Interfaces\Response\IRedirect;
|
|
||||||
use SokoWeb\Response\Redirect;
|
|
||||||
use SokoWeb\Response\HtmlContent;
|
|
||||||
|
|
||||||
class OAuthSessionController implements IAuthenticationRequired
|
|
||||||
{
|
|
||||||
private OAuthClientRepository $oAuthClientRepository;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->oAuthClientRepository = new OAuthClientRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isAuthenticationRequired(): bool
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function auth(): HtmlContent|Redirect
|
|
||||||
{
|
|
||||||
$redirectUri = \Container::$request->query('redirect_uri');
|
|
||||||
$clientId = \Container::$request->query('client_id');
|
|
||||||
$scope = \Container::$request->query('scope') ? \Container::$request->query('scope'): '';
|
|
||||||
$state = \Container::$request->query('state');
|
|
||||||
$nonce = \Container::$request->query('nonce') ? \Container::$request->query('nonce'): '';
|
|
||||||
$codeChallenge = \Container::$request->query('code_challenge') ?: null;
|
|
||||||
$codeChallengeMethod = \Container::$request->query('code_challenge_method') ?: null;
|
|
||||||
|
|
||||||
if (!$clientId || !$redirectUri || !$state || (!$codeChallenge && $codeChallengeMethod)) {
|
|
||||||
return new HtmlContent('oauth/oauth_error', ['error' => 'An invalid request was made. Please start authentication again.']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($codeChallenge && (strlen($codeChallenge) < 43 || strlen($codeChallenge) > 128)) {
|
|
||||||
return new HtmlContent('oauth/oauth_error', ['error' => 'Code challenge should be one between 43 and 128 characters long.']);
|
|
||||||
}
|
|
||||||
$possibleCodeChallengeMethods = ['plain', 'S256'];
|
|
||||||
if ($codeChallenge && !in_array($codeChallengeMethod, $possibleCodeChallengeMethods)) {
|
|
||||||
return new HtmlContent('oauth/oauth_error', ['error' => 'Code challenge method should be one of the following: ' . implode(',', $possibleCodeChallengeMethods)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$client = $this->oAuthClientRepository->getByClientId($clientId);
|
|
||||||
if ($client === null) {
|
|
||||||
return new HtmlContent('oauth/oauth_error', ['error' => 'Client is not found.']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$redirectUriQueryParsed = [];
|
|
||||||
if (str_contains('?', $redirectUri)) {
|
|
||||||
[$redirectUriBase, $redirectUriQuery] = explode('?', $redirectUri, 2);
|
|
||||||
parse_str($redirectUriQuery, $redirectUriQueryParsed);
|
|
||||||
} else {
|
|
||||||
$redirectUriBase = $redirectUri;
|
|
||||||
}
|
|
||||||
if (!in_array($redirectUriBase, $client->getRedirectUrisArray())) {
|
|
||||||
return new HtmlContent('oauth/oauth_error', ['error' => 'Redirect URI \'' . $redirectUriBase .'\' is not allowed for this client.']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var ?User $user
|
|
||||||
*/
|
|
||||||
$user = \Container::$request->user();
|
|
||||||
$code = bin2hex(random_bytes(16));
|
|
||||||
|
|
||||||
$session = new OAuthSession();
|
|
||||||
$session->setClientId($clientId);
|
|
||||||
$session->setNonce($nonce);
|
|
||||||
$session->setScope($scope);
|
|
||||||
$session->setCodeChallenge($codeChallenge);
|
|
||||||
$session->setCodeChallengeMethod($codeChallengeMethod);
|
|
||||||
$session->setUser($user);
|
|
||||||
$session->setCode($code);
|
|
||||||
$session->setCreatedDate(new DateTime());
|
|
||||||
$session->setExpiresDate(new DateTime('+5 minutes'));
|
|
||||||
\Container::$persistentDataManager->saveToDb($session);
|
|
||||||
|
|
||||||
$redirectUriQueryParsed = array_merge($redirectUriQueryParsed, [
|
|
||||||
'state' => $state,
|
|
||||||
'code' => $code
|
|
||||||
]);
|
|
||||||
$finalRedirectUri = $redirectUriBase . '?' . http_build_query($redirectUriQueryParsed);
|
|
||||||
|
|
||||||
return new Redirect($finalRedirectUri, IRedirect::TEMPORARY);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,182 +0,0 @@
|
|||||||
<?php namespace RVR\PersistentData\Model;
|
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use SokoWeb\PersistentData\Model\Model;
|
|
||||||
|
|
||||||
class OAuthSession extends Model
|
|
||||||
{
|
|
||||||
protected static string $table = 'oauth_sessions';
|
|
||||||
|
|
||||||
protected static array $fields = ['client_id', 'scope', 'nonce', 'code_challenge', 'code_challenge_method', 'user_id', 'code', 'created', 'expires', 'token_claimed'];
|
|
||||||
|
|
||||||
protected static array $relations = ['user' => User::class];
|
|
||||||
|
|
||||||
private static array $possibleScopeValues = ['openid', 'email', 'profile', 'union_profile'];
|
|
||||||
|
|
||||||
private static array $possibleCodeChallengeMethodValues = ['plain', 'S256'];
|
|
||||||
|
|
||||||
private string $clientId = '';
|
|
||||||
|
|
||||||
private array $scope = [];
|
|
||||||
|
|
||||||
private string $nonce = '';
|
|
||||||
|
|
||||||
private ?string $codeChallenge = null;
|
|
||||||
|
|
||||||
private ?string $codeChallengeMethod = null;
|
|
||||||
|
|
||||||
private ?User $user = null;
|
|
||||||
|
|
||||||
private ?int $userId = null;
|
|
||||||
|
|
||||||
private string $code = '';
|
|
||||||
|
|
||||||
private DateTime $created;
|
|
||||||
|
|
||||||
private DateTime $expires;
|
|
||||||
|
|
||||||
private bool $tokenClaimed = false;
|
|
||||||
|
|
||||||
public function setScopeArray(array $scope): void
|
|
||||||
{
|
|
||||||
$this->scope = array_intersect($scope, self::$possibleScopeValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setClientId(string $clientId): void
|
|
||||||
{
|
|
||||||
$this->clientId = $clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setScope(string $scope): void
|
|
||||||
{
|
|
||||||
$this->setScopeArray(explode(' ', $scope));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setNonce(string $nonce): void
|
|
||||||
{
|
|
||||||
$this->nonce = $nonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCodeChallenge(?string $codeChallenge): void
|
|
||||||
{
|
|
||||||
$this->codeChallenge = $codeChallenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCodeChallengeMethod(?string $codeChallengeMethod): void
|
|
||||||
{
|
|
||||||
if ($codeChallengeMethod !== null && !in_array($codeChallengeMethod, self::$possibleCodeChallengeMethodValues)) {
|
|
||||||
throw new \UnexpectedValueException($codeChallengeMethod . ' is not possible for challengeMethod!');
|
|
||||||
}
|
|
||||||
$this->codeChallengeMethod = $codeChallengeMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUser(User $user): void
|
|
||||||
{
|
|
||||||
$this->user = $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUserId(int $userId): void
|
|
||||||
{
|
|
||||||
$this->userId = $userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCode(string $code): void
|
|
||||||
{
|
|
||||||
$this->code = $code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCreatedDate(DateTime $created): void
|
|
||||||
{
|
|
||||||
$this->created = $created;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setExpiresDate(DateTime $expires): void
|
|
||||||
{
|
|
||||||
$this->expires = $expires;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCreated(string $created): void
|
|
||||||
{
|
|
||||||
$this->created = new DateTime($created);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setExpires(string $expires): void
|
|
||||||
{
|
|
||||||
$this->expires = new DateTime($expires);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTokenClaimed(bool $tokenClaimed): void
|
|
||||||
{
|
|
||||||
$this->tokenClaimed = $tokenClaimed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getClientId(): string
|
|
||||||
{
|
|
||||||
return $this->clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getScope(): string
|
|
||||||
{
|
|
||||||
return implode(' ', $this->scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getScopeArray(): array
|
|
||||||
{
|
|
||||||
return $this->scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getNonce(): string
|
|
||||||
{
|
|
||||||
return $this->nonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCodeChallenge(): ?string
|
|
||||||
{
|
|
||||||
return $this->codeChallenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCodeChallengeMethod(): ?string
|
|
||||||
{
|
|
||||||
return $this->codeChallengeMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUser(): ?User
|
|
||||||
{
|
|
||||||
return $this->user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUserId(): ?int
|
|
||||||
{
|
|
||||||
return $this->userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCode(): string
|
|
||||||
{
|
|
||||||
return $this->code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreatedDate(): DateTime
|
|
||||||
{
|
|
||||||
return $this->created;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreated(): string
|
|
||||||
{
|
|
||||||
return $this->created->format('Y-m-d H:i:s');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getExpiresDate(): DateTime
|
|
||||||
{
|
|
||||||
return $this->expires;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getExpires(): string
|
|
||||||
{
|
|
||||||
return $this->expires->format('Y-m-d H:i:s');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTokenClaimed(): bool
|
|
||||||
{
|
|
||||||
return $this->tokenClaimed;
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,26 +7,61 @@ class OAuthToken extends Model
|
|||||||
{
|
{
|
||||||
protected static string $table = 'oauth_tokens';
|
protected static string $table = 'oauth_tokens';
|
||||||
|
|
||||||
protected static array $fields = ['session_id', 'created', 'expires'];
|
protected static array $fields = ['scope', 'nonce', 'user_id', 'code', 'access_token', 'created', 'expires'];
|
||||||
|
|
||||||
protected static array $relations = ['session' => OAuthSession::class];
|
protected static array $relations = ['user' => User::class];
|
||||||
|
|
||||||
private ?OAuthSession $session = null;
|
private static array $possibleScopeValues = ['openid', 'email', 'profile'];
|
||||||
|
|
||||||
private ?int $sessionId = null;
|
private array $scope = [];
|
||||||
|
|
||||||
|
private string $nonce = '';
|
||||||
|
|
||||||
|
private ?User $user = null;
|
||||||
|
|
||||||
|
private ?int $userId = null;
|
||||||
|
|
||||||
|
private string $code = '';
|
||||||
|
|
||||||
|
private string $accessToken = '';
|
||||||
|
|
||||||
private DateTime $created;
|
private DateTime $created;
|
||||||
|
|
||||||
private DateTime $expires;
|
private DateTime $expires;
|
||||||
|
|
||||||
public function setSession(OAuthSession $session): void
|
public function setScopeArray(array $scope): void
|
||||||
{
|
{
|
||||||
$this->session = $session;
|
$this->scope = array_intersect($scope, self::$possibleScopeValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setSessionId(int $sessionId): void
|
public function setScope(string $scope): void
|
||||||
{
|
{
|
||||||
$this->sessionId = $sessionId;
|
$this->setScopeArray(explode(' ', $scope));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNonce(string $nonce): void
|
||||||
|
{
|
||||||
|
$this->nonce = $nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUser(User $user): void
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUserId(int $userId): void
|
||||||
|
{
|
||||||
|
$this->userId = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCode(string $code): void
|
||||||
|
{
|
||||||
|
$this->code = $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAccessToken(string $accessToken): void
|
||||||
|
{
|
||||||
|
$this->accessToken = $accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setCreatedDate(DateTime $created): void
|
public function setCreatedDate(DateTime $created): void
|
||||||
@ -49,14 +84,39 @@ class OAuthToken extends Model
|
|||||||
$this->expires = new DateTime($expires);
|
$this->expires = new DateTime($expires);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSession(): ?OAuthSession
|
public function getScope(): string
|
||||||
{
|
{
|
||||||
return $this->session;
|
return implode(' ', $this->scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSessionId(): ?int
|
public function getScopeArray(): array
|
||||||
{
|
{
|
||||||
return $this->sessionId;
|
return $this->scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNonce(): string
|
||||||
|
{
|
||||||
|
return $this->nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): ?User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserId(): ?int
|
||||||
|
{
|
||||||
|
return $this->userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCode(): string
|
||||||
|
{
|
||||||
|
return $this->code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAccessToken(): string
|
||||||
|
{
|
||||||
|
return $this->accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCreatedDate(): DateTime
|
public function getCreatedDate(): DateTime
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
<?php namespace RVR\Repository;
|
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use Generator;
|
|
||||||
use SokoWeb\Database\Query\Select;
|
|
||||||
use RVR\PersistentData\Model\OAuthSession;
|
|
||||||
|
|
||||||
class OAuthSessionRepository
|
|
||||||
{
|
|
||||||
public function getById(int $id): ?OAuthSession
|
|
||||||
{
|
|
||||||
return \Container::$persistentDataManager->selectFromDbById($id, OAuthSession::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByCode(string $code): ?OAuthSession
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('code', '=', $code);
|
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, OAuthSession::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllExpired(): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('expires', '<', (new DateTime())->format('Y-m-d H:i:s'));
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, OAuthSession::class);
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,7 +4,6 @@ use DateTime;
|
|||||||
use Generator;
|
use Generator;
|
||||||
use SokoWeb\Database\Query\Select;
|
use SokoWeb\Database\Query\Select;
|
||||||
use RVR\PersistentData\Model\OAuthToken;
|
use RVR\PersistentData\Model\OAuthToken;
|
||||||
use RVR\PersistentData\Model\OAuthSession;
|
|
||||||
|
|
||||||
class OAuthTokenRepository
|
class OAuthTokenRepository
|
||||||
{
|
{
|
||||||
@ -13,16 +12,20 @@ class OAuthTokenRepository
|
|||||||
return \Container::$persistentDataManager->selectFromDbById($id, OAuthToken::class);
|
return \Container::$persistentDataManager->selectFromDbById($id, OAuthToken::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllBySession(OAuthSession $session, bool $useRelations = false, array $withRelations = []): Generator
|
public function getByCode(string $code): ?OAuthToken
|
||||||
{
|
{
|
||||||
$select = $this->selectAllBySession($session);
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('code', '=', $code);
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, OAuthToken::class, $useRelations, $withRelations);
|
return \Container::$persistentDataManager->selectFromDb($select, OAuthToken::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function countAllBySession(OAuthSession $session): int
|
public function getByAccessToken(string $accessToken): ?OAuthToken
|
||||||
{
|
{
|
||||||
return $this->selectAllBySession($session)->count();
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('access_token', '=', $accessToken);
|
||||||
|
|
||||||
|
return \Container::$persistentDataManager->selectFromDb($select, OAuthToken::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllExpired(): Generator
|
public function getAllExpired(): Generator
|
||||||
@ -32,11 +35,4 @@ class OAuthTokenRepository
|
|||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, OAuthToken::class);
|
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, OAuthToken::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function selectAllBySession(OAuthSession $session): Select
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection, OAuthToken::getTable());
|
|
||||||
$select->where('session_id', '=', $session->getId());
|
|
||||||
return $select;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
11
web.php
11
web.php
@ -7,7 +7,7 @@ use SokoWeb\Request\Request;
|
|||||||
use SokoWeb\Request\Session;
|
use SokoWeb\Request\Session;
|
||||||
use RVR\Controller\HomeController;
|
use RVR\Controller\HomeController;
|
||||||
use RVR\Controller\LoginController;
|
use RVR\Controller\LoginController;
|
||||||
use RVR\Controller\OAuthSessionController;
|
use RVR\Controller\OAuthAuthController;
|
||||||
use RVR\Controller\OAuthController;
|
use RVR\Controller\OAuthController;
|
||||||
use RVR\Controller\UserController;
|
use RVR\Controller\UserController;
|
||||||
use RVR\Controller\UserSearchController;
|
use RVR\Controller\UserSearchController;
|
||||||
@ -29,7 +29,6 @@ if (!empty($_ENV['DEV'])) {
|
|||||||
Container::$routeCollection = new RouteCollection();
|
Container::$routeCollection = new RouteCollection();
|
||||||
|
|
||||||
Container::$routeCollection->get('home', '', [HomeController::class, 'getHome']);
|
Container::$routeCollection->get('home', '', [HomeController::class, 'getHome']);
|
||||||
Container::$routeCollection->get('oauth-config-root', '.well-known/openid-configuration', [OAuthController::class, 'getConfig']);
|
|
||||||
Container::$routeCollection->group('login', function (RouteCollection $routeCollection) {
|
Container::$routeCollection->group('login', function (RouteCollection $routeCollection) {
|
||||||
$routeCollection->get('login', '', [LoginController::class, 'getLoginForm']);
|
$routeCollection->get('login', '', [LoginController::class, 'getLoginForm']);
|
||||||
$routeCollection->post('login-action', '', [LoginController::class, 'login']);
|
$routeCollection->post('login-action', '', [LoginController::class, 'login']);
|
||||||
@ -37,10 +36,8 @@ Container::$routeCollection->group('login', function (RouteCollection $routeColl
|
|||||||
$routeCollection->get('login.google-action', 'google/code', [LoginController::class, 'loginWithGoogle']);
|
$routeCollection->get('login.google-action', 'google/code', [LoginController::class, 'loginWithGoogle']);
|
||||||
});
|
});
|
||||||
Container::$routeCollection->group('oauth', function (RouteCollection $routeCollection) {
|
Container::$routeCollection->group('oauth', function (RouteCollection $routeCollection) {
|
||||||
$routeCollection->get('oauth.auth', 'auth', [OAuthSessionController::class, 'auth']);
|
$routeCollection->get('oauth.auth', 'auth', [OAuthAuthController::class, 'auth']);
|
||||||
$routeCollection->post('oauth.token', 'token', [OAuthController::class, 'generateToken']);
|
$routeCollection->post('oauth.token', 'token', [OAuthController::class, 'getToken']);
|
||||||
$routeCollection->post('oauth.token.introspect', 'token/introspect', [OAuthController::class, 'introspectToken']);
|
|
||||||
$routeCollection->post('oauth.token.revoke', 'token/revoke', [OAuthController::class, 'revokeToken']);
|
|
||||||
$routeCollection->get('oauth.userinfo', 'userinfo', [OAuthController::class, 'getUserInfo']);
|
$routeCollection->get('oauth.userinfo', 'userinfo', [OAuthController::class, 'getUserInfo']);
|
||||||
$routeCollection->get('oauth.config', '.well-known/openid-configuration', [OAuthController::class, 'getConfig']);
|
$routeCollection->get('oauth.config', '.well-known/openid-configuration', [OAuthController::class, 'getConfig']);
|
||||||
$routeCollection->get('oauth.certs', 'certs', [OAuthController::class, 'getCerts']);
|
$routeCollection->get('oauth.certs', 'certs', [OAuthController::class, 'getCerts']);
|
||||||
@ -118,7 +115,7 @@ Container::$routeCollection->group('communities', function (RouteCollection $rou
|
|||||||
Container::$sessionHandler = new DatabaseSessionHandler(
|
Container::$sessionHandler = new DatabaseSessionHandler(
|
||||||
Container::$dbConnection,
|
Container::$dbConnection,
|
||||||
'sessions',
|
'sessions',
|
||||||
new DateTime('-1 days')
|
new DateTime('-7 days')
|
||||||
);
|
);
|
||||||
|
|
||||||
session_set_save_handler(Container::$sessionHandler, true);
|
session_set_save_handler(Container::$sessionHandler, true);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user