Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.39% covered (success)
93.39%
113 / 121
83.33% covered (success)
83.33%
25 / 30
CRAP
0.00% covered (danger)
0.00%
0 / 1
Http
93.39% covered (success)
93.39%
113 / 121
83.33% covered (success)
83.33%
25 / 30
67.26
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 setStream
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setContentType
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setContentTypeAsUrlForm
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setContentTypeAsMultipartForm
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setBearerToken
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setRefreshToken
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setRefreshTokenName
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setType
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getStream
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 stream
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getResultResponse
77.78% covered (success)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
6.40
 getContentType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBearerToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRefreshToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRefreshTokenName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getType
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
 hasStream
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasContentType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasBearerToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasRefreshToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasRefreshTokenName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasType
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
 initRequest
77.78% covered (success)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
6.40
 authenticate
80.00% covered (success)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
4.13
 validate
94.29% covered (success)
94.29%
33 / 35
0.00% covered (danger)
0.00%
0 / 1
14.04
 parseScheme
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
5.01
 createDigest
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
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-2023 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\Auth;
15
16use Pop\Http\Client;
17/**
18 * Http auth class
19 *
20 * @category   Pop
21 * @package    Pop\Auth
22 * @author     Nick Sagona, III <dev@nolainteractive.com>
23 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
24 * @license    http://www.popphp.org/license     New BSD License
25 * @version    3.3.0
26 */
27class Http extends AbstractAuth
28{
29
30    /**
31     * HTTP auth type constants
32     */
33    const AUTH_BASIC     = 'BASIC';
34    const AUTH_DIGEST    = 'DIGEST';
35    const AUTH_BEARER    = 'BEARER';
36    const AUTH_URL_DATA  = 'URL_DATA';
37    const AUTH_FORM_DATA = 'FORM_DATA';
38    const AUTH_REFRESH   = 'REFRESH';
39
40    /**
41     * Auth client stream
42     * @var Client\Stream
43     */
44    protected $stream = null;
45
46    /**
47     * Auth content type
48     * @var string
49     */
50    protected $contentType = null;
51
52    /**
53     * Auth bearer token
54     * @var string
55     */
56    protected $bearerToken = null;
57
58    /**
59     * Auth refresh token
60     * @var string
61     */
62    protected $refreshToken = null;
63
64    /**
65     * Auth refresh token name
66     * @var string
67     */
68    protected $refreshTokenName = 'refresh';
69
70    /**
71     * Auth type
72     * @var string
73     */
74    protected $type = null;
75
76    /**
77     * Scheme values
78     * @var array
79     */
80    protected $scheme = [];
81
82    /**
83     * Constructor
84     *
85     * Instantiate the Http auth adapter object
86     *
87     * @param mixed  $stream
88     * @param string $type
89     * @param string $method
90     */
91    public function __construct($stream = null, $type = null, $method = 'POST')
92    {
93        if (null !== $stream) {
94            if (is_string($stream)) {
95                $stream = new Client\Stream($stream, $method);
96            }
97            $this->setStream($stream);
98        }
99        if (null !== $type) {
100            $this->setType($type);
101        }
102    }
103
104    /**
105     * Set stream
106     *
107     * @param  Client\Stream $stream
108     * @return Http
109     */
110    public function setStream(Client\Stream $stream)
111    {
112        $this->stream = $stream;
113        return $this;
114    }
115
116    /**
117     * Set content type
118     *
119     * @param  string $contentType
120     * @return Http
121     */
122    public function setContentType($contentType)
123    {
124        $this->contentType = $contentType;
125        return $this;
126    }
127
128    /**
129     * Set content type as URL form
130     *
131     * @return Http
132     */
133    public function setContentTypeAsUrlForm()
134    {
135        $this->contentType = 'application/x-www-form-urlencoded';
136        return $this;
137    }
138
139    /**
140     * Set content type as multipart form
141     *
142     * @return Http
143     */
144    public function setContentTypeAsMultipartForm()
145    {
146        $this->contentType = 'multipart/form-data';
147        return $this;
148    }
149
150    /**
151     * Set the bearer token
152     *
153     * @param  string $bearerToken
154     * @return Http
155     */
156    public function setBearerToken($bearerToken)
157    {
158        $this->bearerToken = $bearerToken;
159        return $this;
160    }
161
162    /**
163     * Set the refresh token
164     *
165     * @param  string $refreshToken
166     * @return Http
167     */
168    public function setRefreshToken($refreshToken)
169    {
170        $this->refreshToken = $refreshToken;
171        return $this;
172    }
173
174    /**
175     * Set the refresh token name
176     *
177     * @param  string $refreshTokenName
178     * @return Http
179     */
180    public function setRefreshTokenName($refreshTokenName)
181    {
182        $this->refreshTokenName = $refreshTokenName;
183        return $this;
184    }
185
186    /**
187     * Set type
188     *
189     * @param  string $type
190     * @return Http
191     */
192    public function setType($type)
193    {
194        $this->type = $type;
195        return $this;
196    }
197
198    /**
199     * Get stream
200     *
201     * @return Client\Stream
202     */
203    public function getStream()
204    {
205        return $this->stream;
206    }
207
208    /**
209     * Get stream (alias method)
210     *
211     * @return Client\Stream
212     */
213    public function stream()
214    {
215        return $this->stream;
216    }
217
218    /**
219     * Get result response
220     *
221     * @return array
222     */
223    public function getResultResponse()
224    {
225        $resultResponse = null;
226        if (($this->stream->hasResponse()) && ($this->stream->getResponse()->hasBody())) {
227            $resultResponse = $this->stream->getResponse()->getBody()->getContent();
228            if ($this->stream->getResponse()->hasHeader('Content-Type')) {
229                if ($this->stream->getResponse()->getHeader('Content-Type')->getValue() == 'application/json') {
230                    $resultResponse = json_decode($resultResponse, true);
231                } else if ($this->stream->getResponse()->getHeader('Content-Type')->getValue() == 'application/x-www-form-urlencoded') {
232                    parse_str($resultResponse, $resultResponse);
233                }
234            }
235        }
236        return $resultResponse;
237    }
238
239    /**
240     * Get content type
241     *
242     * @return string
243     */
244    public function getContentType()
245    {
246        return $this->contentType;
247    }
248
249    /**
250     * Get the bearer token
251     *
252     * @return string
253     */
254    public function getBearerToken()
255    {
256        return $this->bearerToken;
257    }
258
259    /**
260     * Get the refresh token
261     *
262     * @return string
263     */
264    public function getRefreshToken()
265    {
266        return $this->refreshToken;
267    }
268
269    /**
270     * Get the refresh token name
271     *
272     * @return string
273     */
274    public function getRefreshTokenName()
275    {
276        return $this->refreshTokenName;
277    }
278
279    /**
280     * Get the auth type
281     *
282     * @return string
283     */
284    public function getType()
285    {
286        return $this->type;
287    }
288
289    /**
290     * Get the auth scheme
291     *
292     * @return array
293     */
294    public function getScheme()
295    {
296        return $this->scheme;
297    }
298
299    /**
300     * Has stream
301     *
302     * @return boolean
303     */
304    public function hasStream()
305    {
306        return (null !== $this->stream);
307    }
308
309    /**
310     * Has content type
311     *
312     * @return boolean
313     */
314    public function hasContentType()
315    {
316        return (null !== $this->contentType);
317    }
318
319    /**
320     * Has bearer token
321     *
322     * @return boolean
323     */
324    public function hasBearerToken()
325    {
326        return (null !== $this->bearerToken);
327    }
328
329    /**
330     * Has refresh token
331     *
332     * @return boolean
333     */
334    public function hasRefreshToken()
335    {
336        return (null !== $this->refreshToken);
337    }
338
339    /**
340     * Has refresh token name
341     *
342     * @return boolean
343     */
344    public function hasRefreshTokenName()
345    {
346        return (null !== $this->refreshTokenName);
347    }
348
349    /**
350     * Has auth type
351     *
352     * @return boolean
353     */
354    public function hasType()
355    {
356        return (null !== $this->type);
357    }
358
359    /**
360     * Has an auth scheme
361     *
362     * @return boolean
363     */
364    public function hasScheme()
365    {
366        return (!empty($this->scheme));
367    }
368
369    /**
370     * Initialize the auth request
371     *
372     * @throws Exception
373     * @return void
374     */
375    public function initRequest()
376    {
377        if (null === $this->stream) {
378            throw new Exception('Error: The stream has not been set.');
379        }
380
381        $this->stream->send();
382
383        if (($this->stream->hasResponse()) && ($this->stream->response()->hasHeaders())) {
384            $wwwHeaders = ['WWW-Authenticate','WWW-authenticate','Www-Authenticate','www-authenticate'];
385            foreach ($wwwHeaders as $wwwHeader) {
386                if ($this->stream->response()->hasHeader($wwwHeader)) {
387                    $this->type = $this->parseScheme($this->stream->response()->getHeader($wwwHeader)->getValue());
388                    break;
389                }
390            }
391        }
392    }
393
394    /**
395     * Method to authenticate
396     *
397     * @param  string $username
398     * @param  string $password
399     * @param  array  $headers
400     * @param  array  $contextOptions
401     * @param  array  $contextParams
402     * @return int
403     */
404    public function authenticate($username, $password, array $headers = [], array $contextOptions = [], array $contextParams = [])
405    {
406        $this->setUsername($username);
407        $this->setPassword($password);
408
409        if ((null === $this->type) || (empty($this->scheme) && ($this->type == self::AUTH_DIGEST))) {
410            $this->initRequest();
411        }
412
413        return $this->validate($headers, $contextOptions, $contextParams);
414    }
415
416    /**
417     * Method to validate authentication
418     *
419     * @param  array $headers
420     * @param  array $contextOptions
421     * @param  array $contextParams
422     * @return int
423     */
424    public function validate(array $headers = [], array $contextOptions = [], array $contextParams = [])
425    {
426        switch ($this->type) {
427            case self::AUTH_DIGEST:
428                $headers['Authorization'] = $this->createDigest();
429                break;
430            case self::AUTH_BASIC:
431                $headers['Authorization'] = 'Basic ' . base64_encode($this->username . ':' . $this->password);
432                break;
433            case self::AUTH_BEARER:
434                $headers['Authorization'] = 'Bearer ' . $this->bearerToken;
435                break;
436            case self::AUTH_URL_DATA:
437                $this->stream->setFields([
438                    'username' => $this->username,
439                    'password' => $this->password
440                ]);
441                $this->stream->request()->createUrlEncodedForm();
442                break;
443            case self::AUTH_FORM_DATA:
444                $this->stream->setFields([
445                    'username' => $this->username,
446                    'password' => $this->password
447                ]);
448                $this->stream->request()->createMultipartForm();
449                break;
450            case self::AUTH_REFRESH:
451                $headers['Authorization'] = 'Bearer ' . $this->bearerToken;
452                $this->stream->setFields([$this->refreshTokenName => $this->refreshToken]);
453                if ($this->contentType == 'application/json') {
454                    $this->stream->request()->createAsJson();
455                } else if ($this->contentType == 'application/x-www-form-urlencoded') {
456                    $this->stream->request()->createUrlEncodedForm();
457                } else if ($this->contentType == 'multipart/form-data') {
458                    $this->stream->request()->createMultipartForm();
459                }
460                break;
461        }
462
463        if (!empty($headers)) {
464            $this->stream->addRequestHeaders($headers);
465        }
466        if (!empty($contextOptions)) {
467            $this->stream->setContextOptions($contextOptions);
468        }
469        if (!empty($contextParams)) {
470            $this->stream->setContextParams($contextParams);
471        }
472
473        $this->stream->send();
474        $this->result = (int)(($this->stream->hasResponse()) && ($this->stream->response()->getCode() == 200));
475        return $this->result;
476    }
477
478    /**
479     * Parse the scheme
480     *
481     * @param  string $wwwAuth
482     * @return string
483     */
484    public function parseScheme($wwwAuth)
485    {
486        $type = null;
487        if (strpos($wwwAuth, ' ') !== false) {
488            $type   = substr($wwwAuth, 0, strpos($wwwAuth, ' '));
489            $scheme = explode(', ', substr($wwwAuth, (strpos($wwwAuth, ' ') + 1)));
490
491            foreach ($scheme as $sch) {
492                $sch   = trim($sch);
493                $name  = substr($sch,0, strpos($sch, '='));
494                $value = substr($sch, (strpos($sch, '=') + 1));
495                if ((substr($value, 0, 1) == '"') && (substr($value, -1) == '"')) {
496                    $value = substr($value, 1);
497                    $value = substr($value, 0, -1);
498                }
499                $this->scheme[$name] = $value;
500            }
501        } else {
502            $type = $wwwAuth;
503        }
504
505        return $type;
506    }
507
508    /**
509     * Create auth digest header string
510     *
511     * @throws Exception
512     * @return string
513     */
514    public function createDigest()
515    {
516        $relativeUri = $this->stream->getUrl();
517        if (strpos($relativeUri, '://') !== false) {
518            $relativeUri = substr($relativeUri, (strpos($relativeUri, '://') + 3));
519        }
520        $relativeUri = substr($relativeUri, strpos($relativeUri, '/'));
521
522        $scheme = $this->getScheme();
523
524        if (!isset($scheme['realm']) || !isset($scheme['nonce'])) {
525            throw new Exception('Error: The realm and/or the nonce was not successfully parsed.');
526        }
527
528        $a1 = md5($this->username . ':' . $scheme['realm'] . ':' . $this->password);
529        $a2 = md5($this->stream->getMethod() . ':' . $relativeUri);
530        $r  = md5($a1 . ':' . $scheme['nonce'] . ':' . $a2);
531
532        return 'Digest username="' . $this->username .
533            '", realm="' . $scheme['realm'] . '", nonce="' . $scheme['nonce'] .
534            '", uri="' . $relativeUri . '", response="' . $r . '"';
535    }
536
537}