HttpBasic::withTokenType()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Auth\Method;
6
7
use Psr\Http\Message\ResponseInterface;
8
use Psr\Http\Message\ServerRequestInterface;
9
use Yiisoft\Auth\AuthenticationMethodInterface;
10
use Yiisoft\Auth\IdentityInterface;
11
use Yiisoft\Auth\IdentityWithTokenRepositoryInterface;
12
use Yiisoft\Http\Header;
13
14
use function call_user_func;
15
use function count;
16
17
/**
18
 * HTTP Basic authentication method.
19
 *
20
 * @see https://tools.ietf.org/html/rfc7617
21
 *
22
 * In case authentication does not work as expected, make sure your web server passes username and password
23
 * to `$request->getServerParams()['PHP_AUTH_USER']` and `$request->getServerParams()['PHP_AUTH_PW']`
24
 * parameters. If you are using Apache with PHP-CGI, you might need to add this line to your `.htaccess` file:
25
 *
26
 * ```
27
 * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
28
 * ```
29
 */
30
final class HttpBasic implements AuthenticationMethodInterface
31
{
32
    private string $realm = 'api';
33
    private ?string $tokenType = null;
34
35
    /**
36
     * @var callable|null
37
     */
38
    private $authenticationCallback;
39
40 18
    public function __construct(private IdentityWithTokenRepositoryInterface $identityRepository)
41
    {
42 18
    }
43
44 15
    public function authenticate(ServerRequestInterface $request): ?IdentityInterface
45
    {
46 15
        [$username, $password] = $this->getAuthenticationCredentials($request);
47
48 15
        if ($this->authenticationCallback !== null && ($username !== null || $password !== null)) {
49 7
            return call_user_func($this->authenticationCallback, $username, $password, $this->identityRepository);
50
        }
51
52 8
        if ($username !== null) {
53 5
            return $this->identityRepository->findIdentityByToken($username, $this->tokenType);
54
        }
55
56 3
        return null;
57
    }
58
59 2
    public function challenge(ResponseInterface $response): ResponseInterface
60
    {
61 2
        return $response->withHeader(Header::WWW_AUTHENTICATE, "Basic realm=\"{$this->realm}\"");
62
    }
63
64
    /**
65
     * @param callable $authenticationCallback A PHP callable that will authenticate the user with the HTTP basic
66
     * authentication information. The callable should have the following signature:
67
     *
68
     * ```php
69
     * static function (
70
     *     string $username,
71
     *     string $password,
72
     *     \Yiisoft\Auth\IdentityRepositoryInterface $identityRepository
73
     * ): ?\Yiisoft\Auth\IdentityInterface
74
     * ```
75
     *
76
     * It should return an identity object that matches the username and password.
77
     * Null should be returned if there is no such identity.
78
     * The callable will be called only if current user is not authenticated.
79
     *
80
     * If not set, the username information will be considered as an access token
81
     * while the password information will be ignored.
82
     * The {@see \Yiisoft\Auth\IdentityWithTokenRepositoryInterface::findIdentityByToken()}
83
     * method will be called to authenticate an identity.
84
     */
85 8
    public function withAuthenticationCallback(callable $authenticationCallback): self
86
    {
87 8
        $new = clone $this;
88 8
        $new->authenticationCallback = $authenticationCallback;
89 8
        return $new;
90
    }
91
92
    /**
93
     * @param string $realm The HTTP authentication realm.
94
     *
95
     * @return $this
96
     *
97
     * @psalm-immutable
98
     */
99 2
    public function withRealm(string $realm): self
100
    {
101 2
        $new = clone $this;
102 2
        $new->realm = $realm;
103 2
        return $new;
104
    }
105
106
    /**
107
     * @param string|null $type Identity token type
108
     *
109
     * @return $this
110
     *
111
     * @psalm-immutable
112
     */
113 2
    public function withTokenType(?string $type): self
114
    {
115 2
        $new = clone $this;
116 2
        $new->tokenType = $type;
117 2
        return $new;
118
    }
119
120
    /**
121
     * Obtains authentication credentials from request.
122
     *
123
     * @return array ['username', 'password'] array.
124
     */
125 15
    private function getAuthenticationCredentials(ServerRequestInterface $request): array
126
    {
127 15
        $username = $request->getServerParams()['PHP_AUTH_USER'] ?? null;
128 15
        $password = $request->getServerParams()['PHP_AUTH_PW'] ?? null;
129 15
        if ($username !== null || $password !== null) {
130 6
            return [$username, $password];
131
        }
132
133
        /*
134
         * Apache with php-cgi does not pass HTTP Basic authentication to PHP by default.
135
         * To make it work, add the following line to to your .htaccess file:
136
         *
137
         * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
138
         */
139 9
        $token = $this->getTokenFromHeaders($request);
140 9
        if ($token !== null && $this->isBasicToken($token)) {
141 7
            $credentials = $this->extractCredentialsFromHeader($token);
142 7
            if (count($credentials) < 2) {
143 3
                return [$credentials[0], null];
144
            }
145
146 4
            return $credentials;
147
        }
148
149 2
        return [null, null];
150
    }
151
152 9
    private function getTokenFromHeaders(ServerRequestInterface $request): ?string
153
    {
154 9
        $header = $request->getHeaderLine(Header::AUTHORIZATION);
155 9
        if (!empty($header)) {
156 7
            return $header;
157
        }
158
159 2
        return $request->getServerParams()['REDIRECT_HTTP_AUTHORIZATION'] ?? null;
160
    }
161
162 7
    private function extractCredentialsFromHeader(string $authToken): array
163
    {
164 7
        return array_map(
165 7
            static fn ($value) => $value === '' ? null : $value,
166 7
            explode(':', base64_decode(substr($authToken, 6)), 2)
167 7
        );
168
    }
169
170 8
    private function isBasicToken(string $token): bool
171
    {
172 8
        return strncasecmp($token, 'basic', 5) === 0;
173
    }
174
}
175