Passed
Pull Request — master (#18)
by Alexander
02:54 queued 01:23
created

HttpBasic::withAuthenticationCallback()   A

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