Completed
Push — master ( bb7666...882899 )
by Oscar
02:21
created

DigestAuthentication   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 135
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 4

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 15
c 2
b 0
f 0
lcom 2
cbo 4
dl 0
loc 135
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getUsername() 0 4 1
A nonce() 0 6 1
A __invoke() 0 13 3
A login() 0 19 3
A checkAuthentication() 0 9 1
B parseAuthorizationHeader() 0 20 6
1
<?php
2
3
namespace Psr7Middlewares\Middleware;
4
5
use Psr\Http\Message\ServerRequestInterface;
6
use Psr7Middlewares\Middleware;
7
use Psr7Middlewares\Utils;
8
use Psr\Http\Message\ResponseInterface;
9
10
/**
11
 * Middleware to create a digest http authentication.
12
 *
13
 * @see https://tools.ietf.org/html/rfc2069#page-10
14
 */
15
class DigestAuthentication
16
{
17
    use Utils\AuthenticationTrait;
18
19
    const KEY = 'USERNAME';
20
21
    /**
22
     * @var string|null The nonce value
23
     */
24
    private $nonce;
25
26
    /**
27
     * Returns the username.
28
     *
29
     * @param ServerRequestInterface $request
30
     *
31
     * @return string|null
32
     */
33
    public static function getUsername(ServerRequestInterface $request)
34
    {
35
        return Middleware::getAttribute($request, self::KEY);
36
    }
37
38
    /**
39
     * Set the nonce value.
40
     *
41
     * @param string $nonce
42
     *
43
     * @return self
44
     */
45
    public function nonce($nonce)
46
    {
47
        $this->nonce = $nonce;
48
49
        return $this;
50
    }
51
52
    /**
53
     * Execute the middleware.
54
     *
55
     * @param ServerRequestInterface  $request
56
     * @param ResponseInterface       $response
57
     * @param callable                $next
58
     *
59
     * @return ResponseInterface
60
     */
61
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
62
    {
63
        if ($this->login($request, $username)) {
64
            return $next(
65
                Middleware::setAttribute($request, self::KEY, $username),
66
                $response
67
            );
68
        }
69
70
        return $response
71
            ->withStatus(401)
72
            ->withHeader('WWW-Authenticate', 'Digest realm="'.$this->realm.'",qop="auth",nonce="'.($this->nonce ?: uniqid()).'",opaque="'.md5($this->realm).'"');
73
    }
74
75
    /**
76
     * Login or check the user credentials.
77
     *
78
     * @param ServerRequestInterface $request
79
     * @param string|null            $username
80
     *
81
     * @return bool
82
     */
83
    private function login(ServerRequestInterface $request, &$username)
84
    {
85
        //Check header
86
        $authorization = self::parseAuthorizationHeader($request->getHeaderLine('Authorization'));
87
88
        if (!$authorization) {
89
            return false;
90
        }
91
92
        //Check whether user exists
93
        if (!isset($this->users[$authorization['username']])) {
94
            return false;
95
        }
96
97
        $username = $authorization['username'];
98
99
        //Check authentication
100
        return $this->checkAuthentication($authorization, $request->getMethod(), $this->users[$username]);
101
    }
102
103
    /**
104
     * Validates the user authentication.
105
     *
106
     * @param array  $authorization
107
     * @param string $method
108
     * @param string $password
109
     *
110
     * @return bool
111
     */
112
    private function checkAuthentication(array $authorization, $method, $password)
113
    {
114
        $A1 = md5("{$authorization['username']}:{$this->realm}:{$password}");
115
        $A2 = md5("{$method}:{$authorization['uri']}");
116
117
        $validResponse = md5("{$A1}:{$authorization['nonce']}:{$authorization['nc']}:{$authorization['cnonce']}:{$authorization['qop']}:{$A2}");
118
119
        return $authorization['response'] === $validResponse;
120
    }
121
122
    /**
123
     * Parses the authorization header for a basic authentication.
124
     *
125
     * @param string $header
126
     *
127
     * @return false|array
128
     */
129
    private static function parseAuthorizationHeader($header)
130
    {
131
        if (strpos($header, 'Digest') !== 0) {
132
            return false;
133
        }
134
135
        $needed_parts = ['nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1];
136
        $data = [];
137
138
        preg_match_all('@('.implode('|', array_keys($needed_parts)).')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', substr($header, 7), $matches, PREG_SET_ORDER);
139
140
        if ($matches) {
141
            foreach ($matches as $m) {
142
                $data[$m[1]] = $m[3] ? $m[3] : $m[4];
143
                unset($needed_parts[$m[1]]);
144
            }
145
        }
146
147
        return empty($needed_parts) ? $data : false;
148
    }
149
}
150