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.0.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  string $username
148     * @param  string $password
149     * @param  array  $digest
150     * @return Auth
151     */
152    public static function createDigest(Auth\Digest $digest): Auth
153    {
154        $auth = new static('Authorization', null, null, $digest->getUsername(), $digest->getPassword());
155        $auth->setDigest($digest);
156        return $auth;
157    }
158
159    /**
160     * Parse header
161     *
162     * @param  mixed $header
163     * @param  array $options
164     * @throws Exception|Auth\Exception
165     * @return Auth
166     */
167    public static function parse(mixed $header, array $options = []): Auth
168    {
169        $auth = new static();
170
171        if (!($header instanceof Header)) {
172            $header = Header::parse($header);
173        }
174
175        $headerName = (strtolower((string)$header->getName()) == 'www-authenticate') ? 'Authorization' : $header->getName();
176        $auth->setHeader($headerName);
177
178        if (count($header->getValues()) == 1) {
179            $value = $header->getValue();
180        } else {
181            $value = $header->getValuesAsStrings('; ');
182        }
183
184        if ((($value instanceof Header\Value) && !empty($value->getScheme()) && (trim($value->getScheme()) == 'Digest')) ||
185            (isset($options['scheme']) && ($options['scheme'] == 'digest'))) {
186            if (!isset($options['password'])) {
187                throw new Exception('Error: The password option must be passed.');
188            }
189            if (strtolower((string)$header->getName()) == 'www-authenticate') {
190                if (!isset($options['username']) || !isset($options['uri'])) {
191                    throw new Exception('Error: The username and URI options must be passed.');
192                }
193                $auth->setDigest(Auth\Digest::createFromWwwAuth($header, $options['username'], $options['password'], $options['uri']));
194            } else {
195                $auth->setDigest(Auth\Digest::createFromHeader($header, $options['password']));
196            }
197        } else if (str_starts_with($value, 'Basic')) {
198            $auth->setScheme('Basic');
199            $creds = base64_decode(trim(substr($value, 5)));
200            if (($creds !== false) && (str_contains($creds, ':'))) {
201                [$username, $password] = explode(':', $creds);
202                $auth->setUsername($username)
203                    ->setPassword($password);
204            }
205        } else if (str_starts_with($value, 'Bearer')) {
206            $auth->setScheme('Bearer');
207            $auth->setToken(trim(substr($value, 6)));
208        } else {
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}