Passed
Push — master ( b89529...ab2bfc )
by Tobias
04:53
created

DigestAuthMiddleware::getQOP()   C

Complexity

Conditions 8
Paths 11

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 22.916

Importance

Changes 0
Metric Value
cc 8
eloc 12
nc 11
nop 0
dl 0
loc 23
ccs 5
cts 13
cp 0.3846
crap 22.916
rs 6.1403
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Buzz\Middleware;
6
7
use Psr\Http\Message\RequestInterface;
8
use Psr\Http\Message\ResponseInterface;
9
10
class DigestAuthMiddleware implements MiddlewareInterface
11
{
12
    private $username;
13
14
    private $password;
15
16
    private $realm;
17
18
    private $algorithm;
19
20
    private $authenticationMethod;
21
22
    private $clientNonce;
23
24
    private $domain;
25
26
    private $entityBody;
27
28
    private $method;
29
30
    private $nonce;
31
32
    private $nonceCount;
33
34
    private $opaque;
35
36
    private $uri;
37
38
    /** @var string[] Quality of Protection */
39
    private $qop = [];
40
41
    /**
42
     * QOP options: Only one of the following can be set at any time. setOptions will throw an exception otherwise.
43
     * OPTION_QOP_AUTH_INT       - Always use auth-int   (if available)
44
     * OPTION_QOP_AUTH           - Always use auth       (even if auth-int available).
45
     */
46
    const OPTION_QOP_AUTH_INT = 1;
47
48
    const OPTION_QOP_AUTH = 2;
49
50
    /**
51
     * Ignore server request to downgrade authentication from Digest to Basic.
52
     * Breaks RFC compatibility, but ensures passwords are never sent using base64 which is trivial for an attacker to decode.
53
     */
54
    const OPTION_IGNORE_DOWNGRADE_REQUEST = 4;
55
56
    /**
57
     * Discard Client Nonce on each request.
58
     */
59
    const OPTION_DISCARD_CLIENT_NONCE = 8;
60
61
    private $options;
62
63
    /**
64
     * Set OPTION_QOP_BEST_AVAILABLE and OPTION_DISCARD_CLIENT_NONCE by default.
65
     */
66
    public function __construct(string $username = null, string $password = null, string $realm = null)
67
    {
68
        $this->setUsername($username);
69
        $this->setPassword($password);
70
        $this->setRealm($realm);
71
        $this->setOptions(self::OPTION_QOP_AUTH_INT & self::OPTION_DISCARD_CLIENT_NONCE);
72
    }
73
74
    /**
75
     * Populates uri, method and entityBody used to generate the Authentication header using the specified request object.
76
     * Appends the Authentication header if it is present and has been able to be calculated.
77
     */
78
    public function handleRequest(RequestInterface $request, callable $next)
79
    {
80
        $this->setUri($request->getUri()->getPath());
81
        $this->setMethod(strtoupper($request->getMethod()));
82
        $this->setEntityBody($request->getBody()->__toString());
83
84
        if (null !== $header = $this->getHeader()) {
85
            $request = $request->withHeader('Authorization', $header);
86
        }
87
88
        return $next($request);
89
    }
90
91
    /**
92
     * Passes the returned server headers to parseServerHeaders() to check if any authentication variables need to be set.
93
     * Inteprets the returned status code and attempts authentication if status is 401 (Authentication Required) by resending
94
     * the last request with an Authentication header.
95
     */
96
    public function handleResponse(RequestInterface $request, ResponseInterface $response, callable $next)
97
    {
98
        $this->parseServerHeaders($response);
99
100
        return $next($request, $response);
101
    }
102
103
    /**
104
     * Sets the password to be used to authenticate the client.
105
     *
106
     * @param string $password The password
107
     */
108
    public function setPassword(?string $password): void
109
    {
110
        $this->password = $password;
111
    }
112
113
    /**
114
     * Sets the realm to be used to authenticate the client.
115
     *
116
     * @param string $realm The realm
117
     */
118
    public function setRealm(?string $realm): void
119
    {
120
        $this->realm = $realm;
121
    }
122
123
    /**
124
     * Sets the username to be used to authenticate the client.
125
     *
126
     * @param string $username The username
127
     */
128
    public function setUsername(?string $username): void
129
    {
130
        $this->username = $username;
131
    }
132
133
    /**
134
     * Sets the options to be used by this class.
135
     *
136
     * @param mixed $options a bitmask of the constants defined in this class
137
     */
138
    public function setOptions($options): void
139
    {
140
        if ($options & self::OPTION_QOP_AUTH_INT) {
141
            if ($options & self::OPTION_QOP_AUTH) {
142
                throw new \InvalidArgumentException('DigestAuthMiddleware: Only one value of OPTION_QOP_AUTH_INT or OPTION_QOP_AUTH may be set.');
143
            }
144
            $this->options = $this->options | self::OPTION_QOP_AUTH_INT;
145
        } elseif ($options & self::OPTION_QOP_AUTH) {
146
            $this->options = $this->options | self::OPTION_QOP_AUTH;
147
        }
148
149
        if ($options & self::OPTION_IGNORE_DOWNGRADE_REQUEST) {
150
            $this->options = $this->options | self::OPTION_IGNORE_DOWNGRADE_REQUEST;
151
        }
152
153
        if ($options & self::OPTION_DISCARD_CLIENT_NONCE) {
154
            $this->options = $this->options | self::OPTION_DISCARD_CLIENT_NONCE;
155
        }
156
    }
157
158
    /**
159
     * Discards the Client Nonce forcing the generation of a new Client Nonce on the next request.
160
     */
161
    private function discardClientNonce(): void
162
    {
163
        $this->clientNonce = null;
164
    }
165
166
    /**
167
     * Returns the hashing algorithm to be used to generate the digest value. Currently only returns MD5.
168
     *
169
     * @return string the hashing algorithm to be used
170
     */
171
    private function getAlgorithm(): ?string
172
    {
173
        if (null == $this->algorithm) {
174
            $this->algorithm = 'MD5';
175
        }
176
177
        return $this->algorithm;
178
    }
179
180
    /**
181
     * Returns the authentication method requested by the server.
182
     * If OPTION_IGNORE_DOWNGRADE_REQUEST is set this will always return "Digest".
183
     *
184
     * @return string returns either "Digest" or "Basic"
185
     */
186
    private function getAuthenticationMethod(): ?string
187
    {
188
        if ($this->options & self::OPTION_IGNORE_DOWNGRADE_REQUEST) {
189
            return 'Digest';
190
        }
191
192
        return $this->authenticationMethod;
193
    }
194
195
    /**
196
     * Returns either the current value of clientNonce or generates a new value if clientNonce is null.
197
     * Also increments nonceCount.
198
     *
199
     * @return string Returns either the current value of clientNonce the newly generated clientNonce;
200
     */
201
    private function getClientNonce(): ?string
202
    {
203
        if (null == $this->clientNonce) {
204
            $this->clientNonce = uniqid();
205
206
            if (null == $this->nonceCount) {
207
                // If nonceCount is not set then set it to 00000001.
208
                $this->nonceCount = '00000001';
209
            } else {
210
                // If it is set then increment it.
211
                ++$this->nonceCount;
212
                // Ensure nonceCount is zero-padded at the start of the string to a length of 8
213
                while (strlen($this->nonceCount) < 8) {
214
                    $this->nonceCount = '0'.$this->nonceCount;
215
                }
216
            }
217
        }
218
219
        return $this->clientNonce;
220
    }
221
222
    /**
223
     * Returns a space separated list of uris that the server nonce can be used to generate an authentication response against.
224
     *
225
     * @return string space separated list of uris
226
     */
227
    private function getDomain(): ?string
228
    {
229
        return $this->domain;
230
    }
231
232
    /**
233
     * Returns the entity body of the current request.
234
     * The entity body is the request before it has been encoded with the content-encoding and minus the request headers.
235
     *
236
     * @return string the full entity-body
237
     */
238
    private function getEntityBody(): ?string
239
    {
240
        return (string) $this->entityBody;
241
    }
242
243
    /**
244
     * Calculates the value of HA1 according to RFC 2617 and RFC 2069.
245
     *
246
     * @return string|null The value of HA1
247
     */
248
    private function getHA1(): ?string
249
    {
250
        $username = $this->getUsername();
251
        $password = $this->getPassword();
252
        $realm = $this->getRealm();
253
254
        if (($username) && ($password) && ($realm)) {
255
            $algorithm = $this->getAlgorithm();
256
257
            if ('MD5' === $algorithm) {
258
                $A1 = "{$username}:{$realm}:{$password}";
259
260
                return $this->hash($A1);
261
            } elseif ('MD5-sess' === $algorithm) {
262
                $nonce = $this->getNonce();
263
                $cnonce = $this->getClientNonce();
264
                if (($nonce) && ($cnonce)) {
265
                    $A1 = $this->hash("{$username}:{$realm}:{$password}").":{$nonce}:{$cnonce}";
266
267
                    return $this->hash($A1);
268
                }
269
            }
270
        }
271
272
        return null;
273
    }
274
275
    /**
276
     * Calculates the value of HA2 according to RFC 2617 and RFC 2069.
277
     *
278
     * @return string The value of HA2
279
     */
280
    private function getHA2(): ?string
281
    {
282
        $method = $this->getMethod();
283
        $uri = $this->getUri();
284
285
        if (($method) && ($uri)) {
286
            $qop = $this->getQOP();
287
288
            if (null === $qop || 'auth' === $qop) {
289
                $A2 = "{$method}:{$uri}";
290
            } elseif ('auth-int' === $qop) {
291
                $entityBody = (string) $this->getEntityBody();
292
                $A2 = "{$method}:{$uri}:".(string) $this->hash($entityBody);
293
            } else {
294
                return null;
295
            }
296
297
            $HA2 = $this->hash($A2);
298
299
            return $HA2;
300
        }
301
302
        return null;
303
    }
304
305
    /**
306
     * Returns the full Authentication header for use in authenticating the client with either Digest or Basic authentication.
307
     *
308
     * @return string the Authentication header to be sent to the server
309
     */
310
    private function getHeader(): ?string
311
    {
312
        if ('Digest' == $this->getAuthenticationMethod()) {
313
            $username = $this->getUsername();
314
            $realm = $this->getRealm();
315
            $nonce = $this->getNonce();
316
            $response = $this->getResponse();
317
            if (($username) && ($realm) && ($nonce) && ($response)) {
318
                $uri = $this->getUri();
319
                $opaque = $this->getOpaque();
320
                $qop = $this->getQOP();
321
322
                $header = 'Digest';
323
                $header .= ' username="'.$username.'",';
324
                $header .= ' realm="'.$realm.'",';
325
                $header .= ' nonce="'.$nonce.'",';
326
                $header .= ' response="'.$response.'",';
327
328
                if ($uri) {
329
                    $header .= ' uri="'.$uri.'",';
330
                }
331
                if ($opaque) {
332
                    $header .= ' opaque="'.$opaque.'",';
333
                }
334
335
                if ($qop) {
336
                    $header .= ' qop='.$qop.',';
337
338
                    $cnonce = $this->getClientNonce();
339
                    $nc = $this->getNonceCount();
340
341
                    if ($cnonce) {
342
                        $header .= ' nc='.$nc.',';
343
                    }
344
                    if ($cnonce) {
345
                        $header .= ' cnonce="'.$cnonce.'",';
346
                    }
347
                }
348
349
                // Remove the last comma from the header
350
                $header = substr($header, 0, strlen($header) - 1);
351
                // Discard the Client Nonce if OPTION_DISCARD_CLIENT_NONCE is set.
352
                if ($this->options & self::OPTION_DISCARD_CLIENT_NONCE) {
353
                    $this->discardClientNonce();
354
                }
355
356
                return $header;
357
            }
358
        }
359
        if ('Basic' == $this->getAuthenticationMethod()) {
360
            $username = $this->getUsername();
361
            $password = $this->getPassword();
362
            if (($username) && ($password)) {
363
                $header = 'Basic '.base64_encode("{$username}:{$password}");
364
365
                return $header;
366
            }
367
        }
368
369
        return null;
370
    }
371
372
    /**
373
     * Returns the HTTP method used in the current request.
374
     *
375
     * @return string one of GET,POST,PUT,DELETE or HEAD
376
     */
377
    private function getMethod(): ?string
378
    {
379
        return $this->method;
380
    }
381
382
    /**
383
     * Returns the value of nonce we have received in the server headers.
384
     *
385
     * @return string the value of the server nonce
386
     */
387
    private function getNonce(): ?string
388
    {
389
        return $this->nonce;
390
    }
391
392
    /**
393
     * Returns the current nonce counter for the client nonce.
394
     *
395
     * @return string an eight digit zero-padded string which reflects the number of times the clientNonce has been generated
396
     */
397
    private function getNonceCount(): ?string
398
    {
399
        return $this->nonceCount;
400
    }
401
402
    /**
403
     * Returns the opaque value that was sent to us from the server.
404
     *
405
     * @return string the value of opaque
406
     */
407
    private function getOpaque(): ?string
408
    {
409
        return $this->opaque;
410
    }
411
412
    /**
413
     * Returns the plaintext password for the client.
414
     *
415
     * @return string the value of password
416
     */
417
    private function getPassword(): ?string
418
    {
419
        return $this->password;
420
    }
421
422
    /**
423
     * Returns either the realm specified by the client, or the realm specified by the server.
424
     * If the server set the value of realm then anything set by our client is overwritten.
425
     *
426
     * @return string the value of realm
427
     */
428
    private function getRealm(): ?string
429
    {
430
        return $this->realm;
431
    }
432
433
    /**
434
     * Calculates the value of response according to RFC 2617 and RFC 2069.
435
     *
436
     * @return string The value of response
437
     */
438
    private function getResponse(): ?string
439
    {
440
        $HA1 = $this->getHA1();
441
        $nonce = $this->getNonce();
442
        $HA2 = $this->getHA2();
443
444
        if (null !== $HA1 && ($nonce) && null !== $HA2) {
445
            $qop = $this->getQOP();
446
447
            if (empty($qop)) {
448
                $response = $this->hash("{$HA1}:{$nonce}:{$HA2}");
449
450
                return $response;
451
            }
452
453
            $cnonce = $this->getClientNonce();
454
            $nc = $this->getNonceCount();
455
            if (($cnonce) && ($nc)) {
456
                $response = $this->hash("{$HA1}:{$nonce}:{$nc}:{$cnonce}:{$qop}:{$HA2}");
457
458
                return $response;
459
            }
460
        }
461
462
        return null;
463
    }
464
465
    /**
466
     * Returns the Quality of Protection to be used when authenticating with the server.
467
     *
468
     * @return string this will either be auth-int or auth
469
     */
470
    private function getQOP(): ?string
471
    {
472
        // Has the server specified any options for Quality of Protection
473
        if (count($this->qop) > 0) {
474
            if ($this->options & self::OPTION_QOP_AUTH_INT) {
475
                if (in_array('auth-int', $this->qop)) {
476
                    return 'auth-int';
477
                }
478
                if (in_array('auth', $this->qop)) {
479
                    return 'auth';
480
                }
481
            }
482
            if ($this->options & self::OPTION_QOP_AUTH) {
483
                if (in_array('auth', $this->qop)) {
484
                    return 'auth';
485
                }
486
                if (in_array('auth-int', $this->qop)) {
487
                    return 'auth-int';
488
                }
489
            }
490
        }
491
        // Server has not specified any value for Quality of Protection so return null
492
        return null;
493
    }
494
495
    /**
496
     * Returns the username set by the client to authenticate with the server.
497
     *
498
     * @return string The value of username
499
     */
500
    private function getUsername(): ?string
501
    {
502
        return $this->username;
503
    }
504
505
    /**
506
     * Returns the uri that we are requesting access to.
507
     *
508
     * @return string The value of uri
509
     */
510
    private function getUri(): ?string
511
    {
512
        return $this->uri;
513
    }
514
515
    /**
516
     * Calculates the hash for a given value using the algorithm specified by the server.
517
     *
518
     * @param string $value The value to be hashed
519
     *
520
     * @return string the hashed value
521
     */
522
    private function hash($value): ?string
523
    {
524
        $algorithm = $this->getAlgorithm();
525
        if (('MD5' == $algorithm) || ('MD5-sess' == $algorithm)) {
526
            return hash('md5', $value);
527
        }
528
529
        return null;
530
    }
531
532
    /**
533
     * Parses the Authentication-Info header received from the server and calls the relevant setter method on each variable received.
534
     *
535
     * @param string $authenticationInfo the full Authentication-Info header
536
     */
537
    private function parseAuthenticationInfoHeader(string $authenticationInfo): void
538
    {
539
        $nameValuePairs = $this->parseNameValuePairs($authenticationInfo);
540
        foreach ($nameValuePairs as $name => $value) {
541
            switch ($name) {
542
                case 'message-qop':
543
544
                    break;
545
                case 'nextnonce':
546
                    // This function needs to only set the Nonce once the rspauth has been verified.
547
                    $this->setNonce($value);
548
549
                    break;
550
                case 'rspauth':
551
                    // Check server rspauth value
552
                    break;
553
            }
554
        }
555
    }
556
557
    /**
558
     * Parses a string of name=value pairs separated by commas and returns and array with the name as the index.
559
     *
560
     * @param string $nameValuePairs the string containing the name=value pairs
561
     *
562
     * @return array an array with the name used as the index and the values stored within
563
     */
564
    private function parseNameValuePairs(string $nameValuePairs): array
565
    {
566
        $parsedNameValuePairs = [];
567
        $nameValuePairs = explode(',', $nameValuePairs);
568
        foreach ($nameValuePairs as $nameValuePair) {
569
            // Trim the Whitespace from the start and end of the name value pair string
570
            $nameValuePair = trim($nameValuePair);
571
            // Split $nameValuePair (name=value) into $name and $value
572
            list($name, $value) = explode('=', $nameValuePair, 2);
573
            // Remove quotes if the string is quoted
574
            $value = $this->unquoteString($value);
575
            // Add pair to array[name] => value
576
            $parsedNameValuePairs[$name] = $value;
577
        }
578
579
        return $parsedNameValuePairs;
580
    }
581
582
    /**
583
     * Parses the server headers received and checks for WWW-Authenticate and Authentication-Info headers.
584
     * Calls parseWwwAuthenticateHeader() and parseAuthenticationInfoHeader() respectively if either of these headers are present.
585
     *
586
     * @param ResponseInterface $response
587
     */
588
    private function parseServerHeaders(ResponseInterface $response): void
589
    {
590
        // Check to see if the WWW-Authenticate header is present and if so set $authHeader
591
        if (!empty($header = $response->getHeaderLine('www-authenticate'))) {
592
            $this->parseWwwAuthenticateHeader($header);
593
        }
594
595
        // Check to see if the Authentication-Info header is present and if so set $authInfo
596
        if (!empty($header = $response->getHeaderLine('authentication-info'))) {
597
            $this->parseAuthenticationInfoHeader($header);
598
        }
599
    }
600
601
    /**
602
     * Parses the WWW-Authenticate header received from the server and calls the relevant setter method on each variable received.
603
     *
604
     * @param string $wwwAuthenticate the full WWW-Authenticate header
605
     */
606
    private function parseWwwAuthenticateHeader(string $wwwAuthenticate): void
607
    {
608
        if ('Digest ' == substr($wwwAuthenticate, 0, 7)) {
609
            $this->setAuthenticationMethod('Digest');
610
            // Remove "Digest " from start of header
611
            $wwwAuthenticate = substr($wwwAuthenticate, 7, strlen($wwwAuthenticate) - 7);
612
613
            $nameValuePairs = $this->parseNameValuePairs($wwwAuthenticate);
614
615
            foreach ($nameValuePairs as $name => $value) {
616
                switch ($name) {
617
                    case 'algorithm':
618
                        $this->setAlgorithm($value);
619
620
                        break;
621
                    case 'domain':
622
                        $this->setDomain($value);
623
624
                        break;
625
                    case 'nonce':
626
                        $this->setNonce($value);
627
628
                        break;
629
                    case 'realm':
630
                        $this->setRealm($value);
631
632
                        break;
633
                    case 'opaque':
634
                        $this->setOpaque($value);
635
636
                        break;
637
                    case 'qop':
638
                        $this->setQOP(explode(',', $value));
639
640
                        break;
641
                }
642
            }
643
        }
644
        if ('Basic ' == substr($wwwAuthenticate, 0, 6)) {
645
            $this->setAuthenticationMethod('Basic');
646
            // Remove "Basic " from start of header
647
            $wwwAuthenticate = substr($wwwAuthenticate, 6, strlen($wwwAuthenticate) - 6);
648
649
            $nameValuePairs = $this->parseNameValuePairs($wwwAuthenticate);
650
651
            foreach ($nameValuePairs as $name => $value) {
652
                switch ($name) {
653
                    case 'realm':
654
                        $this->setRealm($value);
655
656
                        break;
657
                }
658
            }
659
        }
660
    }
661
662
    /**
663
     * Sets the hashing algorithm to be used. Currently only uses MD5 specified by either MD5 or MD5-sess.
664
     * RFCs are currently in draft stage for the proposal of SHA-256 and SHA-512-256.
665
     * Support will be added once the RFC leaves the draft stage.
666
     *
667
     * @param string $algorithm the algorithm the server has requested to use
668
     *
669
     * @throws \InvalidArgumentException if $algorithm is set to anything other than MD5 or MD5-sess
670
     */
671
    private function setAlgorithm(string $algorithm): void
672
    {
673
        if (('MD5' == $algorithm) || ('MD5-sess' == $algorithm)) {
674
            $this->algorithm = $algorithm;
675
        } else {
676
            throw new \InvalidArgumentException('DigestAuthMiddleware: Only MD5 and MD5-sess algorithms are currently supported.');
677
        }
678
    }
679
680
    /**
681
     * Sets authentication method to be used. Options are "Digest" and "Basic".
682
     * If the server and the client are unable to authenticate using Digest then the RFCs state that the server should attempt
683
     * to authenticate the client using Basic authentication. This ensures that we adhere to that behaviour.
684
     * This does however create the possibilty of a downgrade attack so it may be an idea to add a way of disabling this functionality
685
     * as Basic authentication is trivial to decrypt and exposes the username/password to a man-in-the-middle attack.
686
     *
687
     * @param string $authenticationMethod the authentication method requested by the server
688
     *
689
     * @throws \InvalidArgumentException If $authenticationMethod is set to anything other than Digest or Basic
690
     */
691
    private function setAuthenticationMethod(string $authenticationMethod): void
692
    {
693
        if ('Digest' === $authenticationMethod || 'Basic' === $authenticationMethod) {
694
            $this->authenticationMethod = $authenticationMethod;
695
        } else {
696
            throw new \InvalidArgumentException('DigestAuthMiddleware: Only Digest and Basic authentication methods are currently supported.');
697
        }
698
    }
699
700
    /**
701
     * Sets the domain to be authenticated against. THIS IS NOT TO BE CONFUSED WITH THE HOSTNAME/DOMAIN.
702
     * This is specified by the RFC to be a list of uris separated by spaces that the client will be allowed to access.
703
     * An RFC in draft stage is proposing the removal of this functionality, it does not seem to be in widespread use.
704
     *
705
     * @param string $domain the list of uris separated by spaces that the client will be able to access upon successful authentication
706
     */
707
    private function setDomain(string $domain): void
708
    {
709
        $this->domain = $domain;
710
    }
711
712
    /**
713
     * Sets the Entity Body of the Request for use with qop=auth-int.
714
     *
715
     * @param string $entityBody the body of the entity (The unencoded request minus the headers)
716
     */
717
    private function setEntityBody(string $entityBody = null): void
718
    {
719
        $this->entityBody = $entityBody;
720
    }
721
722
    /**
723
     * Sets the HTTP method being used for the request.
724
     *
725
     * @param string $method The HTTP method
726
     *
727
     * @throws \InvalidArgumentException if $method is set to anything other than GET,POST,PUT,DELETE or HEAD
728
     */
729
    private function setMethod(string $method = null): void
730
    {
731
        if ('GET' == $method) {
732
            $this->method = 'GET';
733
734
            return;
735
        }
736
        if ('POST' == $method) {
737
            $this->method = 'POST';
738
739
            return;
740
        }
741
        if ('PUT' == $method) {
742
            $this->method = 'PUT';
743
744
            return;
745
        }
746
        if ('DELETE' == $method) {
747
            $this->method = 'DELETE';
748
749
            return;
750
        }
751
        if ('HEAD' == $method) {
752
            $this->method = 'HEAD';
753
754
            return;
755
        }
756
757
        throw new \InvalidArgumentException('DigestAuthMiddleware: Only GET,POST,PUT,DELETE,HEAD HTTP methods are currently supported.');
758
    }
759
760
    /**
761
     * Sets the value of nonce.
762
     *
763
     * @param string $nonce The server nonce value
764
     */
765
    private function setNonce(string $nonce = null): void
766
    {
767
        $this->nonce = $nonce;
768
    }
769
770
    /**
771
     * Sets the value of opaque.
772
     *
773
     * @param string $opaque The opaque value
774
     */
775
    private function setOpaque(string $opaque): void
776
    {
777
        $this->opaque = $opaque;
778
    }
779
780
    /**
781
     * Sets the acceptable value(s) for the quality of protection used by the server. Supported values are auth and auth-int.
782
     * TODO: This method should give precedence to using qop=auth-int first as this offers integrity protection.
783
     *
784
     * @param array $qop an array with the values of qop that the server has specified it will accept
785
     *
786
     * @throws \InvalidArgumentException if $qop contains any values other than auth-int or auth
787
     */
788
    private function setQOP(array $qop = []): void
789
    {
790
        $this->qop = [];
791
        foreach ($qop as $protection) {
792
            $protection = trim($protection);
793
            if ('auth-int' == $protection) {
794
                $this->qop[] = 'auth-int';
795
            } elseif ('auth' == $protection) {
796
                $this->qop[] = 'auth';
797
            } else {
798
                throw new \InvalidArgumentException('DigestAuthMiddleware: Only auth-int and auth are supported Quality of Protection mechanisms.');
799
            }
800
        }
801
    }
802
803
    /**
804
     * Sets the value of uri.
805
     *
806
     * @param string $uri The uri
807
     */
808
    private function setUri(string $uri = null): void
809
    {
810
        $this->uri = $uri;
811
    }
812
813
    /**
814
     * If a string contains quotation marks at either end this function will strip them. Otherwise it will remain unchanged.
815
     *
816
     * @param string $str the string to be stripped of quotation marks
817
     *
818
     * @return string returns the original string without the quotation marks at either end
819
     */
820
    private function unquoteString(string $str = null): ?string
821
    {
822
        if ($str) {
823
            if ('"' == substr($str, 0, 1)) {
824
                $str = substr($str, 1, strlen($str) - 1);
825
            }
826
            if ('"' == substr($str, strlen($str) - 1, 1)) {
827
                $str = substr($str, 0, strlen($str) - 1);
828
            }
829
        }
830
831
        return $str;
832
    }
833
}
834