Passed
Push — master ( ce8c92...7c0481 )
by Alexander
01:29
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 2
        return $response->withHeader(Header::WWW_AUTHENTICATE, "Basic realm=\"{$this->realm}\"");
61
    }
62
63
    /**
64
     * @param callable $authenticationCallback A PHP callable that will authenticate the user with the HTTP basic
65
     * authentication information. The callable should have the following signature:
66
     *
67
     * ```php
68
     * static function (
69
     *     string $username,
70
     *     string $password,
71
     *     \Yiisoft\Auth\IdentityRepositoryInterface $identityRepository
72
     * ): ?\Yiisoft\Auth\IdentityInterface
73
     * ```
74
     *
75
     * It should return an identity object that matches the username and password.
76
     * Null should be returned if there is no such identity.
77
     * The callable will be called only if current user is not authenticated.
78
     *
79
     * If not set, the username information will be considered as an access token
80
     * while the password information will be ignored. The {@see \Yiisoft\Auth\IdentityRepositoryInterface::findIdentityByToken()}
81
     * method will be called to authenticate an identity.
82
     * @return self
83
     */
84 8
    public function withAuthenticationCallback(callable $authenticationCallback): self
85
    {
86 8
        $new = clone $this;
87 8
        $new->authenticationCallback = $authenticationCallback;
88 8
        return $new;
89
    }
90
91
    /**
92
     * @param string $realm The HTTP authentication realm.
93
     * @return $this
94
     */
95 2
    public function withRealm(string $realm): self
96
    {
97 2
        $new = clone $this;
98 2
        $new->realm = $realm;
99 2
        return $new;
100
    }
101
102
    /**
103
     * Obtains authentication credentials from request.
104
     *
105
     * @param ServerRequestInterface $request
106
     * @return array ['username', 'password'] array.
107
     */
108 14
    private function getAuthenticationCredentials(ServerRequestInterface $request): array
109
    {
110 14
        $username = $request->getServerParams()['PHP_AUTH_USER'] ?? null;
111 14
        $password = $request->getServerParams()['PHP_AUTH_PW'] ?? null;
112 14
        if ($username !== null || $password !== null) {
113 6
            return [$username, $password];
114
        }
115
116
        /*
117
         * Apache with php-cgi does not pass HTTP Basic authentication to PHP by default.
118
         * To make it work, add the following line to to your .htaccess file:
119
         *
120
         * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
121
         */
122 8
        $token = $this->getTokenFromHeaders($request);
123 8
        if ($token !== null && $this->isBasicToken($token)) {
124 6
            $credentials = $this->extractCredentialsFromHeader($token);
125 6
            if (\count($credentials) < 2) {
126 2
                return [$credentials[0], null];
127
            }
128
129 4
            return $credentials;
130
        }
131
132 2
        return [null, null];
133
    }
134
135 8
    private function getTokenFromHeaders(ServerRequestInterface $request): ?string
136
    {
137 8
        $header = $request->getHeaderLine(Header::AUTHORIZATION);
138 8
        if (!empty($header)) {
139 6
            return $header;
140
        }
141
142 2
        return $request->getServerParams()['REDIRECT_HTTP_AUTHORIZATION'] ?? null;
143
    }
144
145 6
    private function extractCredentialsFromHeader(string $authToken): array
146
    {
147 6
        return array_map(
148 6
            fn ($value) => $value === '' ? null : $value,
149 6
            explode(':', base64_decode(substr($authToken, 6)), 2)
150
        );
151
    }
152
153 7
    private function isBasicToken(string $token): bool
154
    {
155 7
        return strncasecmp($token, 'basic', 5) === 0;
156
    }
157
}
158