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