Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.68% covered (success)
92.68%
38 / 41
60.00% covered (warning)
60.00%
3 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
BasicAuthentication
92.68% covered (success)
92.68%
38 / 41
60.00% covered (warning)
60.00%
3 / 5
20.16
0.00% covered (danger)
0.00%
0 / 1
 __construct
93.10% covered (success)
93.10%
27 / 29
0.00% covered (danger)
0.00%
0 / 1
12.05
 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
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
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                throw new RuntimeException('Invalid exclude path: ' . var_export($excludePath, true));
53            }
54            $regex = '/^' . str_replace(['?', '\\*'], ['.', '.*?'], preg_quote($excludePath, '/')) . '$/';
55            if (preg_match($regex, $fullPath)) {
56                $continueWhenNoCredentials = true;
57            }
58        }
59
60        // Fetches the realm
61        $realm = $this->config->get('authentication', 'realm');
62        if (!is_string($realm)) {
63            $realm = 'Basic authentication';
64        }
65
66        // Check if we have credentials
67        if (
68            !is_string($request->getServer('PHP_AUTH_USER')) ||
69            !is_string($request->getServer('PHP_AUTH_PW'))
70        ) {
71            if ($continueWhenNoCredentials) {
72                return;
73            } else {
74                throw new UnauthorizedResponse($realm);
75            }
76        }
77
78        // Fetch user table
79        $userTable = $this->getUserTable();
80
81        // Is the username valid?
82        if (!isset($userTable[$request->getServer('PHP_AUTH_USER')])) {
83            throw new UnauthorizedResponse($realm);
84        }
85
86        // Is the password valid?
87        if (!password_verify($request->getServer('PHP_AUTH_PW'), $userTable[$request->getServer('PHP_AUTH_USER')])) {
88            throw new UnauthorizedResponse($realm);
89        }
90
91        // We passed the BasicAuthentication middleware, continue.
92        $this->username = $request->getServer('PHP_AUTH_USER');
93    }
94
95    /**
96     * Returns a response that forces the user to log out
97     *
98     * @return Response
99     */
100    public function logout(): Response
101    {
102        $host = $this->request->getServer('HTTP_HOST');
103        $url = $this->request->isHttpsRequest() ? 'https' : 'http';
104        $url .= '://logout@' . (is_string($host) ? $host : '') . $this->config->get('framework', 'base_href');
105        return new RedirectResponse($url);
106    }
107
108    /**
109     * Checks if logging in is required in the current state
110     *
111     * @return boolean
112     */
113    protected function loginRequired(): bool
114    {
115        // Don't do authentication on shell requests
116        if ($this->request->isShellRequest()) {
117            return false;
118        }
119
120        return true;
121    }
122
123    /**
124     * Returns the user table
125     *
126     * @return array<string, string>
127     */
128    protected function getUserTable(): array
129    {
130        // Fetch user table
131        $userTable = $this->config->get('authentication', 'user');
132        if (!is_array($userTable)) {
133            throw new \RuntimeException('The usertable is invalid. Please check your configuration.');
134        }
135        return $userTable;
136    }
137
138    /**
139     * Returns the username
140     *
141     * @return string|null
142     */
143    public function getUsername(): ?string
144    {
145        return $this->username;
146    }
147}