Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
AuthChain
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
5 / 5
9
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setOAuth2Authenticator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 authenticate
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
5
 getAuthenticatedUserId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAuthSource
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * Authentication chain for API requests.
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2026-02-09
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Auth;
21
22use phpMyFAQ\User\CurrentUser;
23use Symfony\Component\HttpFoundation\Request;
24
25final class AuthChain
26{
27    private ?int $authenticatedUserIdStorage = null;
28
29    private ?string $authSourceStorage = null;
30
31    public ?int $authenticatedUserId {
32        get => $this->authenticatedUserIdStorage;
33    }
34
35    public ?string $authSource {
36        get => $this->authSourceStorage;
37    }
38
39    /** @var callable(Request): ?int|null */
40    private $oauth2Authenticator = null;
41
42    public function __construct(
43        private readonly CurrentUser $currentUser,
44        private readonly ApiKeyAuthenticator $apiKeyAuthenticator,
45    ) {
46    }
47
48    /**
49     * @param callable(Request): ?int $authenticator
50     */
51    public function setOAuth2Authenticator(callable $authenticator): void
52    {
53        $this->oauth2Authenticator = $authenticator;
54    }
55
56    /**
57     * @param string[] $requiredScopes
58     */
59    public function authenticate(Request $request, array $requiredScopes = []): bool
60    {
61        $this->authenticatedUserIdStorage = null;
62        $this->authSourceStorage = null;
63
64        if ($this->currentUser->isLoggedIn()) {
65            $this->authenticatedUserIdStorage = $this->currentUser->getUserId();
66            $this->authSourceStorage = 'session';
67            return true;
68        }
69
70        if ($this->apiKeyAuthenticator->authenticate($request, $requiredScopes)) {
71            $this->authenticatedUserIdStorage = $this->apiKeyAuthenticator->getAuthenticatedUserId();
72            $this->authSourceStorage = 'api_key';
73            return $this->authenticatedUserIdStorage !== null;
74        }
75
76        if ($this->oauth2Authenticator !== null) {
77            $userId = ($this->oauth2Authenticator)($request);
78            if ($userId !== null) {
79                $this->authenticatedUserIdStorage = $userId;
80                $this->authSourceStorage = 'oauth2';
81                return true;
82            }
83        }
84
85        return false;
86    }
87
88    public function getAuthenticatedUserId(): ?int
89    {
90        return $this->authenticatedUserId;
91    }
92
93    public function getAuthSource(): ?string
94    {
95        return $this->authSource;
96    }
97}