Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
39 / 39
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
BasicAuthentication
100.00% covered (success)
100.00%
39 / 39
100.00% covered (success)
100.00%
5 / 5
20
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
12
 logout
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 loginRequired
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getUserTable
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getUsername
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Miniframe\Middleware;
4
5use Miniframe\Core\AbstractMiddleware;
6use Miniframe\Core\Config;
7use Miniframe\Core\Request;
8use Miniframe\Core\Response;
9use Miniframe\Response\RedirectResponse;
10use Miniframe\Response\UnauthorizedResponse;
11use RuntimeException;
12
13/**
14 * Basic HTTP Authentication class
15 *
16 * This middleware adds basic HTTP authentication to your application.
17 */
18class 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}