Passed
Pull Request — master (#18)
by Alexander
01:34
created

HttpBasic::getAuthenticationCredentials()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 25
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 11
c 0
b 0
f 0
nc 4
nop 1
dl 0
loc 25
ccs 12
cts 12
cp 1
crap 6
rs 9.2222
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\IdentityRepositoryInterface;
12
use Yiisoft\Http\Header;
13
14
/**
15
 * HTTP Basic authentication method.
16
 *
17
 * @see https://tools.ietf.org/html/rfc7617
18
 *
19
 * In case authentication does not work as expected, make sure your web server passes
20
 * username and password to `$request->getServerParams()['PHP_AUTH_USER']` and `$request->getServerParams()['PHP_AUTH_PW']`
21
 * parameters. If you are using Apache with PHP-CGI, you might need to add this line to your `.htaccess` file:
22
 *
23
 * ```
24
 * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
25
 * ```
26
 */
27
final class HttpBasic implements AuthenticationMethodInterface
28
{
29
    private string $realm = 'api';
30
31
    /**
32
     * @var callable|null
33
     */
34
    private $authenticationCallback;
35
36
    private IdentityRepositoryInterface $identityRepository;
37
38 17
    public function __construct(IdentityRepositoryInterface $identityRepository)
39
    {
40 17
        $this->identityRepository = $identityRepository;
41 17
    }
42
43 14
    public function authenticate(ServerRequestInterface $request): ?IdentityInterface
44
    {
45 14
        [$username, $password] = $this->getAuthenticationCredentials($request);
46
47 14
        if ($this->authenticationCallback !== null && ($username !== null || $password !== null)) {
48 7
            return \call_user_func($this->authenticationCallback, $username, $password, $this->identityRepository);
49
        }
50
51 7
        if ($username !== null) {
52 4
            return $this->identityRepository->findIdentityByToken($username, self::class);
53
        }
54
55 3
        return null;
56
    }
57
58 2
    public function challenge(ResponseInterface $response): ResponseInterface
59
    {
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. The {@see \Yiisoft\Auth\IdentityRepositoryInterface::findIdentityByToken()}
82
     * method will be called to authenticate an identity.
83
     * @return self
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
     * @return $this
95
     */
96 2
    public function withRealm(string $realm): self
97
    {
98 2
        $new = clone $this;
99 2
        $new->realm = $realm;
100 2
        return $new;
101
    }
102
103
    /**
104
     * Obtains authentication credentials from request.
105
     *
106
     * @param ServerRequestInterface $request
107
     * @return array ['username', 'password'] array.
108
     */
109 14
    private function getAuthenticationCredentials(ServerRequestInterface $request): array
110
    {
111 14
        $username = $request->getServerParams()['PHP_AUTH_USER'] ?? null;
112 14
        $password = $request->getServerParams()['PHP_AUTH_PW'] ?? null;
113 14
        if ($username !== null || $password !== null) {
114 6
            return [$username, $password];
115
        }
116
117
        /*
118
         * Apache with php-cgi does not pass HTTP Basic authentication to PHP by default.
119
         * To make it work, add the following line to to your .htaccess file:
120
         *
121
         * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
122
         */
123 8
        $token = $this->getTokenFromHeaders($request);
124 8
        if ($token !== null && $this->isBasicToken($token)) {
125 6
            $credentials = $this->extractCredentialsFromHeader($token);
126 6
            if (\count($credentials) < 2) {
127 2
                return [$credentials[0], null];
128
            }
129
130 4
            return $credentials;
131
        }
132
133 2
        return [null, null];
134
    }
135
136 8
    private function getTokenFromHeaders(ServerRequestInterface $request): ?string
137
    {
138 8
        $header = $request->getHeaderLine(Header::AUTHORIZATION);
139 8
        if (!empty($header)) {
140 6
            return $header;
141
        }
142
143 2
        return $request->getServerParams()['REDIRECT_HTTP_AUTHORIZATION'] ?? null;
144
    }
145
146 6
    private function extractCredentialsFromHeader(string $authToken): array
147
    {
148 6
        return array_map(
149 6
            fn ($value) => $value === '' ? null : $value,
150 6
            explode(':', base64_decode(substr($authToken, 6)), 2)
151
        );
152
    }
153
154 7
    private function isBasicToken(string $token): bool
155
    {
156 7
        return strncasecmp($token, 'basic', 5) === 0;
157
    }
158
}
159