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

HttpBasic::setAuth()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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