Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
39 / 39 |
|
100.00% |
5 / 5 |
CRAP | |
100.00% |
1 / 1 |
BasicAuthentication | |
100.00% |
39 / 39 |
|
100.00% |
5 / 5 |
20 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
27 / 27 |
|
100.00% |
1 / 1 |
12 | |||
logout | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
loginRequired | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getUserTable | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getUsername | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace Miniframe\Middleware; |
4 | |
5 | use Miniframe\Core\AbstractMiddleware; |
6 | use Miniframe\Core\Config; |
7 | use Miniframe\Core\Request; |
8 | use Miniframe\Core\Response; |
9 | use Miniframe\Response\RedirectResponse; |
10 | use Miniframe\Response\UnauthorizedResponse; |
11 | use RuntimeException; |
12 | |
13 | /** |
14 | * Basic HTTP Authentication class |
15 | * |
16 | * This middleware adds basic HTTP authentication to your application. |
17 | */ |
18 | class BasicAuthentication extends AbstractMiddleware |
19 | { |
20 | /** |
21 | * The username |
22 | * |
23 | * @var string|null |
24 | */ |
25 | protected $username = null; |
26 | |
27 | /** |
28 | * Forces the user to be logged in |
29 | * |
30 | * @param Request $request Reference to the Request object. |
31 | * @param Config $config Reference to the Config object. |
32 | */ |
33 | public function __construct(Request $request, Config $config) |
34 | { |
35 | parent::__construct($request, $config); |
36 | $continueWhenNoCredentials = false; |
37 | |
38 | // Checks if logging in is required in the current state |
39 | if (!$this->loginRequired()) { |
40 | $continueWhenNoCredentials = true; |
41 | } |
42 | |
43 | // Check if the current path is part of the exclusion list |
44 | $fullPath = '/' . implode('/', $request->getPath()); // $_SERVER['REQUEST_URI'] can't be used since |
45 | // that doesn't apply on Console requests |
46 | $excludeList = |
47 | $config->has('authentication', 'exclude') |
48 | ? (array)$config->get('authentication', 'exclude') |
49 | : []; |
50 | foreach ($excludeList as $excludePath) { |
51 | if (!is_string($excludePath)) { |
52 | // @codeCoverageIgnoreStart |
53 | throw new RuntimeException('Invalid exclude path: ' . var_export($excludePath, true)); |
54 | // @codeCoverageIgnoreEnd |
55 | } |
56 | $regex = '/^' . str_replace(['?', '\\*'], ['.', '.*?'], preg_quote($excludePath, '/')) . '$/'; |
57 | if (preg_match($regex, $fullPath)) { |
58 | $continueWhenNoCredentials = true; |
59 | } |
60 | } |
61 | |
62 | // Fetches the realm |
63 | $realm = $this->config->get('authentication', 'realm'); |
64 | if (!is_string($realm)) { |
65 | // @codeCoverageIgnoreStart |
66 | $realm = 'Basic authentication'; |
67 | // @codeCoverageIgnoreEnd |
68 | } |
69 | |
70 | // Check if we have credentials |
71 | if ( |
72 | !is_string($request->getServer('PHP_AUTH_USER')) || |
73 | !is_string($request->getServer('PHP_AUTH_PW')) |
74 | ) { |
75 | if ($continueWhenNoCredentials) { |
76 | return; |
77 | } else { |
78 | throw new UnauthorizedResponse($realm); |
79 | } |
80 | } |
81 | |
82 | // Fetch user table |
83 | $userTable = $this->getUserTable(); |
84 | |
85 | // Is the username valid? |
86 | if (!isset($userTable[$request->getServer('PHP_AUTH_USER')])) { |
87 | throw new UnauthorizedResponse($realm); |
88 | } |
89 | |
90 | // Is the password valid? |
91 | if (!password_verify($request->getServer('PHP_AUTH_PW'), $userTable[$request->getServer('PHP_AUTH_USER')])) { |
92 | throw new UnauthorizedResponse($realm); |
93 | } |
94 | |
95 | // We passed the BasicAuthentication middleware, continue. |
96 | $this->username = $request->getServer('PHP_AUTH_USER'); |
97 | } |
98 | |
99 | /** |
100 | * Returns a response that forces the user to log out |
101 | * |
102 | * @return Response |
103 | */ |
104 | public function logout(): Response |
105 | { |
106 | $host = $this->request->getServer('HTTP_HOST'); |
107 | $url = $this->request->isHttpsRequest() ? 'https' : 'http'; |
108 | $url .= '://logout@' . (is_string($host) ? $host : '') . $this->config->get('framework', 'base_href'); |
109 | return new RedirectResponse($url); |
110 | } |
111 | |
112 | /** |
113 | * Checks if logging in is required in the current state |
114 | * |
115 | * @return boolean |
116 | */ |
117 | protected function loginRequired(): bool |
118 | { |
119 | // Don't do authentication on shell requests |
120 | if ($this->request->isShellRequest()) { |
121 | return false; |
122 | } |
123 | |
124 | return true; |
125 | } |
126 | |
127 | /** |
128 | * Returns the user table |
129 | * |
130 | * @return array<string, string> |
131 | */ |
132 | protected function getUserTable(): array |
133 | { |
134 | // Fetch user table |
135 | $userTable = $this->config->get('authentication', 'user'); |
136 | if (!is_array($userTable)) { |
137 | throw new \RuntimeException('The usertable is invalid. Please check your configuration.'); |
138 | } |
139 | return $userTable; |
140 | } |
141 | |
142 | /** |
143 | * Returns the username |
144 | * |
145 | * @return string|null |
146 | */ |
147 | public function getUsername(): ?string |
148 | { |
149 | return $this->username; |
150 | } |
151 | } |