Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
112 / 112
100.00% covered (success)
100.00%
32 / 32
CRAP
100.00% covered (success)
100.00%
1 / 1
Auth
100.00% covered (success)
100.00%
112 / 112
100.00% covered (success)
100.00%
32 / 32
70
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 createBasic
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createBearer
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createDigest
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 parse
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
19
 setHeader
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setScheme
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setToken
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setUsername
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setPassword
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setDigest
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getHeader
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getScheme
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUsername
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPassword
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDigest
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasScheme
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasUsername
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasPassword
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasDigest
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasAuthHeader
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isBasic
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isBearer
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isDigest
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAuthHeader
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAuthHeaderAsArray
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getAuthHeaderAsString
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 createAuthHeader
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
13
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Http;
15
16use Pop\Mime\Part\Header;
17
18/**
19 * HTTP auth header class
20 *
21 * @category   Pop
22 * @package    Pop\Http
23 * @author     Nick Sagona, III <dev@nolainteractive.com>
24 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
25 * @license    http://www.popphp.org/license     New BSD License
26 * @version    5.2.0
27 */
28class Auth
29{
30
31    /**
32     * Auth header name
33     * @var string
34     */
35    protected string $header = 'Authorization';
36
37    /**
38     * Auth scheme
39     * @var ?string
40     */
41    protected ?string $scheme = null;
42
43    /**
44     * Auth token
45     * @var ?string
46     */
47    protected ?string $token = null;
48
49    /**
50     * Auth username
51     * @var ?string
52     */
53    protected ?string $username = null;
54
55    /**
56     * Auth password
57     * @var ?string
58     */
59    protected ?string $password = null;
60
61    /**
62     * Digest
63     * @var ?Auth\Digest
64     */
65    protected ?Auth\Digest $digest = null;
66
67    /**
68     * Auth header object
69     * @var ?Header
70     */
71    protected ?Header $authHeader = null;
72
73    /**
74     * Constructor
75     *
76     * Instantiate the auth object
77     *
78     * @param string       $header
79     * @param ?string      $scheme
80     * @param ?string      $token
81     * @param ?string      $username
82     * @param ?string      $password
83     * @param ?Auth\Digest $digest
84     */
85    public function __construct(
86        string $header = 'Authorization', ?string $scheme = null, ?string $token = null,
87        ?string $username = null, ?string $password = null, ?Auth\Digest $digest = null
88    )
89    {
90        $this->setHeader($header);
91        if ($scheme !== null) {
92            $this->setScheme($scheme);
93        }
94        if ($token !== null) {
95            $this->setToken($token);
96        }
97        if ($username !== null) {
98            $this->setUsername($username);
99        }
100        if ($password !== null) {
101            $this->setPassword($password);
102        }
103        if ($digest !== null) {
104            $this->setDigest($digest);
105        }
106    }
107
108    /**
109     * Create basic auth
110     *
111     * @param  string $username
112     * @param  string $password
113     * @return Auth
114     */
115    public static function createBasic(string $username, string $password): Auth
116    {
117        return new static('Authorization', 'Basic', null, $username, $password);
118    }
119
120    /**
121     * Create bearer auth
122     *
123     * @param  string $token
124     * @return Auth
125     */
126    public static function createBearer(string $token): Auth
127    {
128        return new static('Authorization', 'Bearer', $token);
129    }
130
131    /**
132     * Create key auth
133     *
134     * @param  string  $token
135     * @param  string  $header
136     * @param  ?string $scheme
137     * @return Auth
138     */
139    public static function createKey(string $token, string $header = 'Authorization', ?string $scheme = null): Auth
140    {
141        return new static($header, $scheme, $token);
142    }
143
144    /**
145     * Create digest auth
146     *
147     * @param  Auth\Digest $digest
148     * @return Auth
149     */
150    public static function createDigest(Auth\Digest $digest): Auth
151    {
152        $auth = new static('Authorization', null, null, $digest->getUsername(), $digest->getPassword());
153        $auth->setDigest($digest);
154        return $auth;
155    }
156
157    /**
158     * Parse header
159     *
160     * @param  mixed $header
161     * @param  array $options
162     * @throws Exception|Auth\Exception
163     * @return Auth
164     */
165    public static function parse(mixed $header, array $options = []): Auth
166    {
167        $auth = new static();
168
169        if (!($header instanceof Header)) {
170            $header = Header::parse($header);
171        }
172
173        $headerName = (strtolower((string)$header->getName()) == 'www-authenticate') ? 'Authorization' : $header->getName();
174        $auth->setHeader($headerName);
175
176        if (count($header->getValues()) == 1) {
177            $value = $header->getValue();
178        } else {
179            $value = $header->getValuesAsStrings('; ');
180        }
181
182        if ((($value instanceof Header\Value) && !empty($value->getScheme()) && (trim($value->getScheme()) == 'Digest')) ||
183            (isset($options['scheme']) && ($options['scheme'] == 'digest'))) {
184            if (!isset($options['password'])) {
185                throw new Exception('Error: The password option must be passed.');
186            }
187            if (strtolower((string)$header->getName()) == 'www-authenticate') {
188                if (!isset($options['username']) || !isset($options['uri'])) {
189                    throw new Exception('Error: The username and URI options must be passed.');
190                }
191                $auth->setDigest(Auth\Digest::createFromWwwAuth($header, $options['username'], $options['password'], $options['uri']));
192            } else {
193                $auth->setDigest(Auth\Digest::createFromHeader($header, $options['password']));
194            }
195        } else if (str_starts_with($value, 'Basic')) {
196            $auth->setScheme('Basic');
197            $creds = base64_decode(trim(substr($value, 5)));
198            if (($creds !== false) && (str_contains($creds, ':'))) {
199                [$username, $password] = explode(':', $creds);
200                $auth->setUsername($username)
201                    ->setPassword($password);
202            }
203        } else if (str_starts_with($value, 'Bearer')) {
204            $auth->setScheme('Bearer');
205            $auth->setToken(trim(substr($value, 6)));
206        } else {
207            if (isset($options['scheme']) && (str_starts_with($value, $options['scheme']))) {
208                $value = substr($value, strlen($options['scheme']));
209                $auth->setScheme($options['scheme']);
210            }
211            $auth->setToken($value);
212        }
213
214        return $auth;
215    }
216
217    /**
218     * Set the header
219     *
220     * @param  string $header
221     * @return Auth
222     */
223    public function setHeader(string $header): Auth
224    {
225        $this->header = $header;
226        return $this;
227    }
228
229    /**
230     * Set the scheme
231     *
232     * @param  string $scheme
233     * @return Auth
234     */
235    public function setScheme(string $scheme): Auth
236    {
237        $this->scheme = $scheme;
238        return $this;
239    }
240
241    /**
242     * Set the token
243     *
244     * @param  string $token
245     * @return Auth
246     */
247    public function setToken(string $token): Auth
248    {
249        $this->token = $token;
250        return $this;
251    }
252
253    /**
254     * Set the $username
255     *
256     * @param  string $username
257     * @return Auth
258     */
259    public function setUsername(string $username): Auth
260    {
261        $this->username = $username;
262        return $this;
263    }
264
265    /**
266     * Set the password
267     *
268     * @param  string $password
269     * @return Auth
270     */
271    public function setPassword(string $password): Auth
272    {
273        $this->password = $password;
274        if ($this->digest !== null) {
275            $this->digest->setPassword($password);
276        }
277        return $this;
278    }
279
280    /**
281     * Set digest
282     *
283     * @param  Auth\Digest $digest
284     * @return Auth
285     */
286    public function setDigest(Auth\Digest $digest): Auth
287    {
288        $this->digest = $digest;
289        $this->setUsername($digest->getUsername());
290        $this->setPassword($digest->getPassword());
291        return $this;
292    }
293
294    /**
295     * Get the header
296     *
297     * @return string
298     */
299    public function getHeader(): string
300    {
301        return $this->header;
302    }
303
304    /**
305     * Get the scheme
306     *
307     * @return string
308     */
309    public function getScheme(): string
310    {
311        return $this->scheme;
312    }
313
314    /**
315     * Get the token
316     *
317     * @return string
318     */
319    public function getToken(): string
320    {
321        return $this->token;
322    }
323
324    /**
325     * Get the $username
326     *
327     * @return string
328     */
329    public function getUsername(): string
330    {
331        return $this->username;
332    }
333
334    /**
335     * Get the password
336     *
337     * @return string
338     */
339    public function getPassword(): string
340    {
341        return $this->password;
342    }
343
344    /**
345     * Get digest
346     *
347     * @return Auth\Digest
348     */
349    public function getDigest(): Auth\Digest
350    {
351        return $this->digest;
352    }
353
354    /**
355     * Has scheme
356     *
357     * @return bool
358     */
359    public function hasScheme(): bool
360    {
361        return ($this->scheme !== null);
362    }
363
364    /**
365     * Has token
366     *
367     * @return bool
368     */
369    public function hasToken(): bool
370    {
371        return ($this->token !== null);
372    }
373
374    /**
375     * Has $username
376     *
377     * @return bool
378     */
379    public function hasUsername(): bool
380    {
381        return ($this->username !== null);
382    }
383
384    /**
385     * Has password
386     *
387     * @return bool
388     */
389    public function hasPassword(): bool
390    {
391        return ($this->password !== null);
392    }
393
394    /**
395     * Has digest
396     *
397     * @return bool
398     */
399    public function hasDigest(): bool
400    {
401        return ($this->digest !== null);
402    }
403
404    /**
405     * Has auth header
406     *
407     * @return bool
408     */
409    public function hasAuthHeader(): bool
410    {
411        return ($this->authHeader !== null);
412    }
413
414    /**
415     * Determine if the auth is basic
416     *
417     * @return bool
418     */
419    public function isBasic(): bool
420    {
421        return (strtolower((string)$this->scheme) == 'basic');
422    }
423
424    /**
425     * Determine if the auth is bearer
426     *
427     * @return bool
428     */
429    public function isBearer(): bool
430    {
431        return (strtolower((string)$this->scheme) == 'bearer');
432    }
433
434    /**
435     * Determine if the auth is digest
436     *
437     * @return bool
438     */
439    public function isDigest(): bool
440    {
441        return $this->hasDigest();
442    }
443
444    /**
445     * Get auth header value object
446     *
447     * @return Header
448     */
449    public function getAuthHeader(): Header
450    {
451        return $this->authHeader;
452    }
453
454    /**
455     * Get auth header value as an array
456     *
457     * @param  bool $assoc
458     * @throws Exception
459     * @return array
460     */
461    public function getAuthHeaderAsArray(bool $assoc = true): array
462    {
463        $this->createAuthHeader();
464
465        return ($assoc) ?
466            [$this->authHeader->getName() => $this->authHeader->getValue()]:
467            [$this->authHeader->getName(), $this->authHeader->getValue()];
468    }
469
470    /**
471     * Get auth header value as a string
472     *
473     * @param  bool $crlf
474     * @throws Exception
475     * @return string
476     */
477    public function getAuthHeaderAsString(bool $crlf = false): string
478    {
479        $this->createAuthHeader();
480
481        $headerValue = $this->authHeader->render();
482        if ($crlf) {
483            $headerValue .= "\r\n";
484        }
485
486        return $headerValue;
487    }
488
489    /**
490     * Create auth header
491     *
492     * @throws Exception
493     * @return Header
494     */
495    public function createAuthHeader(): Header
496    {
497        if (($this->isBasic()) && (($this->username === null) || ($this->password === null))) {
498            throw new Exception('Error: The username and password values must be set for basic authorization');
499        } else if (($this->isDigest()) && (!$this->digest->isValid())) {
500            throw new Exception(implode('\n', $this->digest->getErrors()));
501        } else if (!($this->isBasic()) && !($this->isDigest()) && ($this->token === null)) {
502            throw new Exception('Error: The token is not set');
503        }
504
505        $value = new Header\Value();
506
507        if ($this->isBasic()) {
508            $value->setScheme('Basic ');
509            $value->setValue(base64_encode($this->username . ':' . $this->password));
510            $value = 'Basic ' . base64_encode($this->username . ':' . $this->password);
511        } else if ($this->isDigest()) {
512            $this->digest->createDigestString($value);
513        } else if ($this->isBearer()) {
514            $value->setScheme('Bearer ');
515            $value->setValue($this->token);
516        } else {
517            if (!empty($this->scheme)) {
518                $value->setScheme($this->scheme);
519            }
520            $value->setValue($this->token);
521        }
522
523        $this->authHeader = new Header($this->header, $value);
524        return $this->authHeader;
525    }
526
527    /**
528     * To string
529     *
530     * @return string
531     */
532    public function __toString(): string
533    {
534        return $this->getAuthHeaderAsString();
535    }
536
537}