Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ jobs:
strategy:
matrix:
include:
- php-version: 8.2
docker-image: 'anzusystems/php:3.3.0-php82-cli'
- php-version: 8.3
docker-image: 'anzusystems/php:3.3.0-php83-cli'
docker-image: 'anzusystems/php:4.0.0-php83-cli'
- php-version: 8.4
docker-image: 'anzusystems/php:4.0.0-php84-cli'

name: PHP ${{ matrix.php-version }}
runs-on: ubuntu-latest
Expand All @@ -37,7 +37,7 @@ jobs:
run: composer install --prefer-dist --no-progress --no-ansi --no-interaction --no-scripts

- name: Run Security check
run: local-php-security-checker --path=composer.lock
run: composer audit --no-scripts

- name: Run ECS style check
run: vendor/bin/ecs check -vv
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM anzusystems/php:3.1.0-php83-cli
FROM anzusystems/php:4.0.0-php83-cli
#
### Basic arguments and variables
ARG DOCKER_USER_ID
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
}
],
"require": {
"php": ">=8.2",
"php": ">=8.3",
"ext-json": "*",
"ext-redis": "*",
"anzusystems/common-bundle": "^7.0|^8.0",
Expand All @@ -24,7 +24,7 @@
"slevomat/coding-standard": "^8.5",
"symfony/test-pack": "^1.0",
"symplify/easy-coding-standard": "^11.1",
"vimeo/psalm": "^5.0"
"vimeo/psalm": "^6.0"
},
"autoload": {
"psr-4": {
Expand Down
15 changes: 1 addition & 14 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,9 @@
<issueHandlers>
<MoreSpecificImplementedParamType errorLevel="suppress"/>
<RiskyTruthyFalsyComparison errorLevel="suppress"/>
<MissingOverrideAttribute errorLevel="suppress"/>
<UnnecessaryVarAnnotation errorLevel="suppress"/> <!-- PHPStorm doesn't understand to generics annotations yet -->

<UnusedFunctionCall>
<errorLevel type="suppress">
<referencedFunction name="array_map"/>
</errorLevel>
</UnusedFunctionCall>

<PossiblyUndefinedMethod>
<errorLevel type="suppress">
<referencedMethod name="Symfony\Component\Config\Definition\Builder\NodeParentInterface::end"/>
<referencedMethod name="Symfony\Component\Config\Definition\Builder\NodeDefinition::children"/>
<referencedMethod name="Symfony\Component\Config\Definition\Builder\NodeDefinition::canbeenabled"/>
</errorLevel>
</PossiblyUndefinedMethod>

<!-- level 3 issues - slightly lazy code writing, but provably low false-negatives -->

<DeprecatedMethod errorLevel="info"/>
Expand Down
2 changes: 1 addition & 1 deletion src/Controller/Api/AbstractAuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Event\LogoutEvent;

#[OA\Tag('Authorization')]
Expand Down
2 changes: 1 addition & 1 deletion src/Controller/Api/JsonCredentialsAuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Attribute\Route;

#[Route(name: 'auth_')]
#[OA\Tag('Authorization')]
Expand Down
4 changes: 3 additions & 1 deletion src/Controller/Api/OAuth2AuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
use AnzuSystems\AuthBundle\Domain\Process\OAuth2\GrantAccessByOAuth2TokenProcess;
use AnzuSystems\AuthBundle\Domain\Process\RefreshTokenProcess;
use AnzuSystems\AuthBundle\Util\StatelessTokenUtil;
use AnzuSystems\Contracts\Exception\AnzuException;
use AnzuSystems\SerializerBundle\Exception\SerializerException;
use OpenApi\Attributes as OA;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Attribute\Route;

#[Route(name: 'auth_')]
#[OA\Tag('Authorization')]
Expand Down Expand Up @@ -42,6 +43,7 @@ public function login(Request $request): RedirectResponse

/**
* @throws SerializerException
* @throws AnzuException
*/
#[Route('authorize', name: 'authorize', methods: [Request::METHOD_GET])]
public function callbackAuthorize(Request $request): Response
Expand Down
14 changes: 8 additions & 6 deletions src/Domain/Process/GrantAccessOnResponseProcess.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use AnzuSystems\AuthBundle\Configuration\CookieConfiguration;
use AnzuSystems\AuthBundle\Configuration\JwtConfiguration;
use AnzuSystems\AuthBundle\Contracts\RefreshTokenStorageInterface;
use AnzuSystems\AuthBundle\Domain\Process\OAuth2\GrantAccessByOAuth2TokenProcess;
use AnzuSystems\AuthBundle\Model\DeviceDto;
use AnzuSystems\AuthBundle\Model\RefreshTokenDto;
use AnzuSystems\AuthBundle\Util\HttpUtil;
Expand All @@ -17,14 +18,14 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

final class GrantAccessOnResponseProcess
final readonly class GrantAccessOnResponseProcess
{
public function __construct(
private readonly CookieConfiguration $cookieConfiguration,
private readonly JwtConfiguration $jwtConfiguration,
private readonly JwtUtil $jwtUtil,
private readonly HttpUtil $httpUtil,
private readonly RefreshTokenStorageInterface $refreshTokenStorage,
private CookieConfiguration $cookieConfiguration,
private JwtConfiguration $jwtConfiguration,
private JwtUtil $jwtUtil,
private HttpUtil $httpUtil,
private RefreshTokenStorageInterface $refreshTokenStorage,
) {
}

Expand Down Expand Up @@ -52,6 +53,7 @@ public function execute(string $userId, Request $request, Response $response = n
$response->setData([
'access_token' => $jwt->toString(),
'refresh_token' => $refreshTokenDto->toString(),
GrantAccessByOAuth2TokenProcess::TIMESTAMP_QUERY_PARAM => time(),
]);
}

Expand Down
31 changes: 19 additions & 12 deletions src/Domain/Process/OAuth2/GrantAccessByOAuth2TokenProcess.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ final class GrantAccessByOAuth2TokenProcess
{
use SerializerAwareTrait;

public const AUTH_METHOD_SSO_ID = 'sso_id';
public const AUTH_METHOD_SSO_EMAIL = 'sso_email';
public const string AUTH_METHOD_SSO_ID = 'sso_id';
public const string AUTH_METHOD_SSO_EMAIL = 'sso_email';
public const string LOGIN_STATE_QUERY_PARAM = 'loginState';
public const string TIMESTAMP_QUERY_PARAM = 'timestamp';

public function __construct(
private readonly OAuth2HttpClient $OAuth2HttpClient,
Expand Down Expand Up @@ -61,10 +63,11 @@ public function execute(Request $request): Response
return $this->createRedirectResponseForRequest($request, UserOAuthLoginState::FailureSsoCommunicationFailed);
}

if ($accessTokenDto->getJwt()) {
$jwt = $accessTokenDto->getJwt();
if ($jwt) {
// validate jwt
try {
$this->validateOAuth2AccessTokenProcess->execute($accessTokenDto->getJwt());
$this->validateOAuth2AccessTokenProcess->execute($jwt);
} catch (InvalidJwtException $exception) {
$this->logException($request, $exception);

Expand Down Expand Up @@ -98,6 +101,18 @@ public function execute(Request $request): Response
}
}

public function createRedirectResponseForRequest(Request $request, UserOAuthLoginState $loginState): RedirectResponse
{
$redirectUrl = $this->httpUtil->getAuthRedirectUrlFromRequest($request);
$redirectUrl .= '?';
$redirectUrl .= http_build_query([
self::LOGIN_STATE_QUERY_PARAM => $loginState->toString(),
self::TIMESTAMP_QUERY_PARAM => time(),
]);

return new RedirectResponse($redirectUrl);
}

/**
* @throws SerializerException
*/
Expand All @@ -111,14 +126,6 @@ private function logException(Request $request, Throwable $throwable): void
$this->appLogger->error('[Authorization] ' . $throwable->getMessage(), $arrayContext);
}

private function createRedirectResponseForRequest(Request $request, UserOAuthLoginState $loginState): RedirectResponse
{
$redirectUrl = $this->httpUtil->getAuthRedirectUrlFromRequest($request);
$redirectUrl .= '?loginState=' . $loginState->toString();

return new RedirectResponse($redirectUrl);
}

/**
* @throws AnzuException
* @throws UnsuccessfulAccessTokenRequestException
Expand Down
3 changes: 3 additions & 0 deletions src/Model/SsoUserDto.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

use AnzuSystems\SerializerBundle\Attributes\Serialize;

/**
* @psalm-suppress ClassMustBeFinal
*/
class SsoUserDto
{
#[Serialize]
Expand Down
1 change: 0 additions & 1 deletion src/Security/Authentication/ApiTokenAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\String\AbstractString;
use function Symfony\Component\String\u;

/**
Expand Down
6 changes: 5 additions & 1 deletion src/Util/HttpUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@

final class HttpUtil
{
private const COOKIE_DEVICE_ID_TTL = 31_536_000; // 1 year
private const int COOKIE_DEVICE_ID_TTL = 31_536_000; // 1 year
private const int COOKIE_JWT_SUB_TTL = 60;

public function __construct(
private readonly CookieConfiguration $cookieConfiguration,
Expand Down Expand Up @@ -101,6 +102,9 @@ public function storeJwtOnResponse(Response $response, Token $token, DateTimeImm
}

$lifetime = $expiresAt?->getTimestamp() ?? $this->jwtConfiguration->getLifetime();
// We want to prevent a situation where a cookie is still stored and sent with a request, but when the server validates it, it is already invalid.
// To avoid this, we subtract a few seconds from the expiration time and allow the user to refresh the token earlier.
$lifetime -= self::COOKIE_JWT_SUB_TTL;
$payloadCookie = $this->createCookie(
$this->cookieConfiguration->getJwtPayloadCookieName(),
$header . '.' . $claims,
Expand Down
2 changes: 1 addition & 1 deletion src/Util/StatelessTokenUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function isValidForRequest(Request $request, string $hash): bool
if ($this->enabled) {
return hash_equals(
known_string: $this->createHashForRequest($request),
user_string: base64_decode(urldecode($hash), strict: true),
user_string: (string) base64_decode(urldecode($hash), strict: true),
);
}

Expand Down