100.00% covered (success)
@@ -401,65 +401,65 @@
189
190
191 public function __construct ( array $config ) {
-
192 $config = array_merge ( [
-
193 'csrfMiddleware' => new Middleware \ DoubleSubmitCookieCsrfMiddleware ( self :: DEFAULT_CSRF_KEY ) ,
+
192 $config = array_merge ( [
+
193 'csrfMiddleware' => new Middleware \ DoubleSubmitCookieCsrfMiddleware ( self :: DEFAULT_CSRF_KEY ) ,
194 'logger' => null ,
-
195 self :: HANDLE_NON_INDIEAUTH_REQUEST => function ( ServerRequestInterface $request ) { return null ; } ,
+
195 self :: HANDLE_NON_INDIEAUTH_REQUEST => function ( ServerRequestInterface $request ) { return null ; } ,
196 'tokenStorage' => null ,
197 'httpGetWithEffectiveUrl' => null ,
-
198 'authorizationForm' => new DefaultAuthorizationForm ( ) ,
+
198 'authorizationForm' => new DefaultAuthorizationForm ( ) ,
199 'exceptionTemplatePath' => __DIR__ . '/../templates/default_exception_response.html.php' ,
200 ] , $config ) ;
201
-
202 if ( ! is_string ( $config [ 'exceptionTemplatePath' ] ) ) {
+
202 if ( ! is_string ( $config [ 'exceptionTemplatePath' ] ) ) {
203 throw new BadMethodCallException ( "\$config['exceptionTemplatePath'] must be a string (path)." ) ;
204 }
-
205 $this -> exceptionTemplatePath = $config [ 'exceptionTemplatePath' ] ;
+
205 $this -> exceptionTemplatePath = $config [ 'exceptionTemplatePath' ] ;
206
-
207 $secret = $config [ 'secret' ] ?? '' ;
-
208 if ( ! is_string ( $secret ) || strlen ( $secret ) < 64 ) {
+
207 $secret = $config [ 'secret' ] ?? '' ;
+
208 if ( ! is_string ( $secret ) || strlen ( $secret ) < 64 ) {
209 throw new BadMethodCallException ( "\$config['secret'] must be a string with a minimum length of 64 characters." ) ;
210 }
-
211 $this -> secret = $secret ;
+
211 $this -> secret = $secret ;
212
-
213 if ( ! is_null ( $config [ 'logger' ] ) && ! $config [ 'logger' ] instanceof LoggerInterface ) {
+
213 if ( ! is_null ( $config [ 'logger' ] ) && ! $config [ 'logger' ] instanceof LoggerInterface ) {
214 throw new BadMethodCallException ( "\$config['logger'] must be an instance of \\Psr\\Log\\LoggerInterface or null." ) ;
215 }
-
216 $this -> logger = $config [ 'logger' ] ?? new NullLogger ( ) ;
+
216 $this -> logger = $config [ 'logger' ] ?? new NullLogger ( ) ;
217
-
218 if ( ! ( array_key_exists ( self :: HANDLE_AUTHENTICATION_REQUEST , $config ) and is_callable ( $config [ self :: HANDLE_AUTHENTICATION_REQUEST ] ) ) ) {
+
218 if ( ! ( array_key_exists ( self :: HANDLE_AUTHENTICATION_REQUEST , $config ) and is_callable ( $config [ self :: HANDLE_AUTHENTICATION_REQUEST ] ) ) ) {
219 throw new BadMethodCallException ( '$callbacks[\'' . self :: HANDLE_AUTHENTICATION_REQUEST . '\'] must be present and callable.' ) ;
220 }
-
221 $this -> handleAuthenticationRequestCallback = $config [ self :: HANDLE_AUTHENTICATION_REQUEST ] ;
+
221 $this -> handleAuthenticationRequestCallback = $config [ self :: HANDLE_AUTHENTICATION_REQUEST ] ;
222
-
223 if ( ! is_callable ( $config [ self :: HANDLE_NON_INDIEAUTH_REQUEST ] ) ) {
+
223 if ( ! is_callable ( $config [ self :: HANDLE_NON_INDIEAUTH_REQUEST ] ) ) {
224 throw new BadMethodCallException ( "\$config['" . self :: HANDLE_NON_INDIEAUTH_REQUEST . "'] must be callable" ) ;
225 }
-
226 $this -> handleNonIndieAuthRequest = $config [ self :: HANDLE_NON_INDIEAUTH_REQUEST ] ;
+
226 $this -> handleNonIndieAuthRequest = $config [ self :: HANDLE_NON_INDIEAUTH_REQUEST ] ;
227
-
228 $tokenStorage = $config [ 'tokenStorage' ] ;
-
229 if ( ! $tokenStorage instanceof Storage \ TokenStorageInterface ) {
-
230 if ( is_string ( $tokenStorage ) ) {
+
228 $tokenStorage = $config [ 'tokenStorage' ] ;
+
229 if ( ! $tokenStorage instanceof Storage \ TokenStorageInterface ) {
+
230 if ( is_string ( $tokenStorage ) ) {
231
-
232 $tokenStorage = new Storage \ FilesystemJsonStorage ( $tokenStorage , $this -> secret ) ;
+
232 $tokenStorage = new Storage \ FilesystemJsonStorage ( $tokenStorage , $this -> secret ) ;
233 } else {
234 throw new BadMethodCallException ( "\$config['tokenStorage'] parameter must be either a string (path) or an instance of Taproot\IndieAuth\TokenStorageInterface." ) ;
235 }
236 }
-
237 trySetLogger ( $tokenStorage , $this -> logger ) ;
-
238 $this -> tokenStorage = $tokenStorage ;
+
237 trySetLogger ( $tokenStorage , $this -> logger ) ;
+
238 $this -> tokenStorage = $tokenStorage ;
239
-
240 $csrfMiddleware = $config [ 'csrfMiddleware' ] ;
-
241 if ( ! $csrfMiddleware instanceof MiddlewareInterface ) {
+
240 $csrfMiddleware = $config [ 'csrfMiddleware' ] ;
+
241 if ( ! $csrfMiddleware instanceof MiddlewareInterface ) {
242 throw new BadMethodCallException ( "\$config['csrfMiddleware'] must be null or implement MiddlewareInterface." ) ;
243 }
-
244 trySetLogger ( $csrfMiddleware , $this -> logger ) ;
-
245 $this -> csrfMiddleware = $csrfMiddleware ;
+
244 trySetLogger ( $csrfMiddleware , $this -> logger ) ;
+
245 $this -> csrfMiddleware = $csrfMiddleware ;
246
-
247 $httpGetWithEffectiveUrl = $config [ 'httpGetWithEffectiveUrl' ] ;
-
248 if ( is_null ( $httpGetWithEffectiveUrl ) ) {
-
249 if ( class_exists ( '\GuzzleHttp\Client' ) ) {
-
250 $httpGetWithEffectiveUrl = function ( string $uri ) {
+
247 $httpGetWithEffectiveUrl = $config [ 'httpGetWithEffectiveUrl' ] ;
+
248 if ( is_null ( $httpGetWithEffectiveUrl ) ) {
+
249 if ( class_exists ( '\GuzzleHttp\Client' ) ) {
+
250 $httpGetWithEffectiveUrl = function ( string $uri ) : array {
251
252
253 $resp = ( new \ GuzzleHttp \ Client ( [
@@ -485,15 +485,15 @@
273 throw new BadMethodCallException ( "\$config['httpGetWithEffectiveUrl'] must be callable." ) ;
274 }
275 }
-
276 trySetLogger ( $httpGetWithEffectiveUrl , $this -> logger ) ;
-
277 $this -> httpGetWithEffectiveUrl = $httpGetWithEffectiveUrl ;
+
276 trySetLogger ( $httpGetWithEffectiveUrl , $this -> logger ) ;
+
277 $this -> httpGetWithEffectiveUrl = $httpGetWithEffectiveUrl ;
278
-
279 if ( ! $config [ 'authorizationForm' ] instanceof AuthorizationFormInterface ) {
+
279 if ( ! $config [ 'authorizationForm' ] instanceof AuthorizationFormInterface ) {
280 throw new BadMethodCallException ( "When provided, \$config['authorizationForm'] must implement Taproot\IndieAuth\Callback\AuthorizationForm." ) ;
281 }
-
282 $this -> authorizationForm = $config [ 'authorizationForm' ] ;
-
283 trySetLogger ( $this -> authorizationForm , $this -> logger ) ;
-
284 }
+
282 $this -> authorizationForm = $config [ 'authorizationForm' ] ;
+
283 trySetLogger ( $this -> authorizationForm , $this -> logger ) ;
+
284 }
285
286 public function getTokenStorage ( ) : TokenStorageInterface {
287 return $this -> tokenStorage ;
@@ -535,10 +535,10 @@
323
324
325 public function handleAuthorizationEndpointRequest ( ServerRequestInterface $request ) : ResponseInterface {
-
326 $this -> logger -> info ( 'Handling an IndieAuth Authorization Endpoint request.' ) ;
+
326 $this -> logger -> info ( 'Handling an IndieAuth Authorization Endpoint request.' ) ;
327
328
-
329 if ( isIndieAuthAuthorizationCodeRedeemingRequest ( $request ) ) {
+
329 if ( isIndieAuthAuthorizationCodeRedeemingRequest ( $request ) ) {
330 $this -> logger -> info ( 'Handling a request to redeem an authorization code for profile information.' ) ;
331
332 $bodyParams = $request -> getParsedBody ( ) ;
@@ -621,44 +621,44 @@
409
410
411
-
412 return $this -> csrfMiddleware -> process ( $request , new Middleware \ ClosureRequestHandler ( function ( ServerRequestInterface $request ) {
+
412 return $this -> csrfMiddleware -> process ( $request , new Middleware \ ClosureRequestHandler ( function ( ServerRequestInterface $request ) {
413
414
415
416 try {
-
417 $queryParams = $request -> getQueryParams ( ) ;
+
417 $queryParams = $request -> getQueryParams ( ) ;
418
419
420
421
-
422 list ( $clientIdResponse , $clientIdEffectiveUrl , $clientIdMf2 ) = [ null , null , null ] ;
+
422 list ( $clientIdResponse , $clientIdEffectiveUrl , $clientIdMf2 ) = [ null , null , null ] ;
423
424
425
-
426 if ( isIndieAuthAuthorizationRequest ( $request , [ 'get' , 'post' ] ) ) {
-
427 $this -> logger -> info ( 'Handling an authorization request' , [ 'method' => $request -> getMethod ( ) ] ) ;
+
426 if ( isIndieAuthAuthorizationRequest ( $request , [ 'get' , 'post' ] ) ) {
+
427 $this -> logger -> info ( 'Handling an authorization request' , [ 'method' => $request -> getMethod ( ) ] ) ;
428
429
-
430 if ( ! isset ( $queryParams [ 'client_id' ] ) || false === filter_var ( $queryParams [ 'client_id' ] , FILTER_VALIDATE_URL ) || ! isClientIdentifier ( $queryParams [ 'client_id' ] ) ) {
-
431 $this -> logger -> warning ( "The client_id provided in an authorization request was not valid." , $queryParams ) ;
-
432 throw IndieAuthException :: create ( IndieAuthException :: INVALID_CLIENT_ID , $request ) ;
+
430 if ( ! isset ( $queryParams [ 'client_id' ] ) || false === filter_var ( $queryParams [ 'client_id' ] , FILTER_VALIDATE_URL ) || ! isClientIdentifier ( $queryParams [ 'client_id' ] ) ) {
+
431 $this -> logger -> warning ( "The client_id provided in an authorization request was not valid." , $queryParams ) ;
+
432 throw IndieAuthException :: create ( IndieAuthException :: INVALID_CLIENT_ID , $request ) ;
433 }
434
435
-
436 if ( ! isset ( $queryParams [ 'redirect_uri' ] ) || false === filter_var ( $queryParams [ 'redirect_uri' ] , FILTER_VALIDATE_URL ) ) {
-
437 $this -> logger -> warning ( "The client_id provided in an authorization request was not valid." , $queryParams ) ;
-
438 throw IndieAuthException :: create ( IndieAuthException :: INVALID_REDIRECT_URI , $request ) ;
+
436 if ( ! isset ( $queryParams [ 'redirect_uri' ] ) || false === filter_var ( $queryParams [ 'redirect_uri' ] , FILTER_VALIDATE_URL ) ) {
+
437 $this -> logger -> warning ( "The client_id provided in an authorization request was not valid." , $queryParams ) ;
+
438 throw IndieAuthException :: create ( IndieAuthException :: INVALID_REDIRECT_URI , $request ) ;
439 }
440
441
442
443
-
444 $currentRequestHash = hashAuthorizationRequestParameters ( $request , $this -> secret ) ;
-
445 if ( ! isset ( $queryParams [ self :: HASH_QUERY_STRING_KEY ] ) or is_null ( $currentRequestHash ) or ! hash_equals ( $currentRequestHash , $queryParams [ self :: HASH_QUERY_STRING_KEY ] ) ) {
+
444 $currentRequestHash = hashAuthorizationRequestParameters ( $request , $this -> secret ) ;
+
445 if ( ! isset ( $queryParams [ self :: HASH_QUERY_STRING_KEY ] ) or is_null ( $currentRequestHash ) or ! hash_equals ( $currentRequestHash , $queryParams [ self :: HASH_QUERY_STRING_KEY ] ) ) {
446
447
448
-
449 if ( ! urlComponentsMatch ( $queryParams [ 'client_id' ] , $queryParams [ 'redirect_uri' ] , [ PHP_URL_SCHEME , PHP_URL_HOST , PHP_URL_PORT ] ) ) {
+
449 if ( ! urlComponentsMatch ( $queryParams [ 'client_id' ] , $queryParams [ 'redirect_uri' ] , [ PHP_URL_SCHEME , PHP_URL_HOST , PHP_URL_PORT ] ) ) {
450
451
452
@@ -710,312 +710,317 @@
498
499
500
-
501 if ( ! isValidState ( $queryParams [ 'state' ] ) ) {
-
502 $this -> logger -> warning ( "The state provided in an authorization request was not valid." , $queryParams ) ;
-
503 throw IndieAuthException :: create ( IndieAuthException :: INVALID_STATE , $request ) ;
+
501 if ( ! isset ( $queryParams [ 'state' ] ) or ! isValidState ( $queryParams [ 'state' ] ) ) {
+
502 $this -> logger -> warning ( "The state provided in an authorization request was not valid." , $queryParams ) ;
+
503 throw IndieAuthException :: create ( IndieAuthException :: INVALID_STATE , $request ) ;
504 }
505
506
-
507 if ( ! isValidCodeChallenge ( $queryParams [ 'code_challenge' ] ) ) {
-
508 $this -> logger -> warning ( "The code_challenge provided in an authorization request was not valid." , $queryParams ) ;
-
509 throw IndieAuthException :: create ( IndieAuthException :: INVALID_CODE_CHALLENGE , $request ) ;
+
507 if ( ! isset ( $queryParams [ 'code_challenge' ] ) or ! isValidCodeChallenge ( $queryParams [ 'code_challenge' ] ) ) {
+
508 $this -> logger -> warning ( "The code_challenge provided in an authorization request was not valid." , $queryParams ) ;
+
509 throw IndieAuthException :: create ( IndieAuthException :: INVALID_CODE_CHALLENGE , $request ) ;
510 }
511
-
512
-
513
-
514
-
515
-
516
-
517 if ( array_key_exists ( 'scope' , $queryParams ) && ! isValidScope ( $queryParams [ 'scope' ] ) ) {
-
518 $this -> logger -> warning ( "The scope provided in an authorization request was not valid." , $queryParams ) ;
-
519 throw IndieAuthException :: create ( IndieAuthException :: INVALID_SCOPE , $request ) ;
-
520 }
-
521
-
522
-
523 if ( array_key_exists ( 'me' , $queryParams ) ) {
-
524 $queryParams [ 'me' ] = IndieAuthClient :: normalizeMeURL ( $queryParams [ 'me' ] ) ;
-
525
-
526 if ( false === $queryParams [ 'me' ] || ! isProfileUrl ( $queryParams [ 'me' ] ) ) {
-
527 $queryParams [ 'me' ] = null ;
-
528 }
-
529 }
-
530
-
531
-
532
-
533
-
534 $hash = hashAuthorizationRequestParameters ( $request , $this -> secret ) ;
-
535
-
536 $redirectQueryParams = $queryParams ;
-
537 $redirectQueryParams [ self :: HASH_QUERY_STRING_KEY ] = $hash ;
-
538 $authenticationRedirect = $request -> getUri ( ) -> withQuery ( buildQueryString ( $redirectQueryParams ) ) -> __toString ( ) ;
-
539
-
540
-
541 $this -> logger -> info ( 'Calling handle_authentication_request callback' ) ;
-
542 $authenticationResult = call_user_func ( $this -> handleAuthenticationRequestCallback , $request , $authenticationRedirect , $queryParams [ 'me' ] ?? null ) ;
-
543
-
544
-
545 if ( $authenticationResult instanceof ResponseInterface ) {
-
546 return $authenticationResult ;
-
547 } elseif ( is_array ( $authenticationResult ) ) {
-
548
-
549 if ( ! array_key_exists ( 'me' , $authenticationResult ) ) {
-
550 $this -> logger -> error ( 'The handle_authentication_request callback returned an array with no me key.' , [ 'array' => $authenticationResult ] ) ;
-
551 throw IndieAuthException :: create ( IndieAuthException :: AUTHENTICATION_CALLBACK_MISSING_ME_PARAM , $request ) ;
-
552 }
-
553
-
554
-
555 if ( isAuthorizationApprovalRequest ( $request ) ) {
-
556
-
557
-
558
-
559 if ( ! array_key_exists ( self :: HASH_QUERY_STRING_KEY , $queryParams ) ) {
-
560 $this -> logger -> warning ( "An authorization approval request did not have a " . self :: HASH_QUERY_STRING_KEY . " parameter." ) ;
-
561 throw IndieAuthException :: create ( IndieAuthException :: AUTHORIZATION_APPROVAL_REQUEST_MISSING_HASH , $request ) ;
-
562 }
-
563
-
564 $expectedHash = hashAuthorizationRequestParameters ( $request , $this -> secret ) ;
-
565 if ( ! isset ( $queryParams [ self :: HASH_QUERY_STRING_KEY ] ) or is_null ( $expectedHash ) or ! hash_equals ( $expectedHash , $queryParams [ self :: HASH_QUERY_STRING_KEY ] ) ) {
-
566 $this -> logger -> warning ( "The hash provided in the URL was invalid!" , [
-
567 'expected' => $expectedHash ,
-
568 'actual' => $queryParams [ self :: HASH_QUERY_STRING_KEY ]
-
569 ] ) ;
-
570 throw IndieAuthException :: create ( IndieAuthException :: AUTHORIZATION_APPROVAL_REQUEST_INVALID_HASH , $request ) ;
-
571 }
-
572
-
573
-
574 $code = array_merge ( $authenticationResult , [
-
575 'client_id' => $queryParams [ 'client_id' ] ,
-
576 'redirect_uri' => $queryParams [ 'redirect_uri' ] ,
-
577 'state' => $queryParams [ 'state' ] ,
-
578 'code_challenge' => $queryParams [ 'code_challenge' ] ,
-
579 'code_challenge_method' => $queryParams [ 'code_challenge_method' ] ,
-
580 'requested_scope' => $queryParams [ 'scope' ] ?? '' ,
-
581 ] ) ;
-
582
-
583
-
584 $code = $this -> authorizationForm -> transformAuthorizationCode ( $request , $code ) ;
-
585
-
586
-
587 $authCode = $this -> tokenStorage -> createAuthCode ( $code ) ;
-
588 if ( is_null ( $authCode ) ) {
-
589
-
590
-
591 $this -> logger -> error ( "Saving the authorization code failed and returned false without raising an exception." ) ;
-
592 throw IndieAuthException :: create ( IndieAuthException :: INTERNAL_ERROR_REDIRECT , $request ) ;
-
593 }
-
594
-
595
-
596 return new Response ( 302 , [
-
597 'Location' => appendQueryParams ( $queryParams [ 'redirect_uri' ] , [
-
598 'code' => $authCode ,
-
599 'state' => $code [ 'state' ]
-
600 ] ) ,
-
601 'Cache-control' => 'no-cache'
-
602 ] ) ;
-
603 }
-
604
-
605
-
606
-
607
-
608
-
609
-
610
-
611
-
612
-
613 if ( is_null ( $clientIdResponse ) || is_null ( $clientIdEffectiveUrl ) || is_null ( $clientIdMf2 ) ) {
-
614 try {
-
615
-
616 list ( $clientIdResponse , $clientIdEffectiveUrl ) = call_user_func ( $this -> httpGetWithEffectiveUrl , $queryParams [ 'client_id' ] ) ;
-
617 $clientIdMf2 = Mf2 \ parse ( (string) $clientIdResponse -> getBody ( ) , $clientIdEffectiveUrl ) ;
-
618 } catch ( ClientExceptionInterface | RequestExceptionInterface | NetworkExceptionInterface $e ) {
-
619 $this -> logger -> error ( "Caught an HTTP exception while trying to fetch the client_id. Returning an error response." , [
-
620 'client_id' => $queryParams [ 'client_id' ] ,
-
621 'exception' => $e -> __toString ( )
-
622 ] ) ;
-
623
-
624
-
625
-
626 throw IndieAuthException :: create ( IndieAuthException :: INTERNAL_ERROR_REDIRECT , $request , $e ) ;
-
627 } catch ( Exception $e ) {
-
628 $this -> logger -> error ( "Caught an unknown exception while trying to fetch the client_id. Returning an error response." , [
-
629 'exception' => $e -> __toString ( )
-
630 ] ) ;
-
631
-
632 throw IndieAuthException :: create ( IndieAuthException :: INTERNAL_ERROR_REDIRECT , $request , $e ) ;
-
633 }
-
634 }
-
635
-
636
-
637 $clientHApps = M \ findMicroformatsByProperty ( M \ findMicroformatsByType ( $clientIdMf2 , 'h-app' ) , 'url' , $queryParams [ 'client_id' ] ) ;
-
638 $clientHApp = empty ( $clientHApps ) ? null : $clientHApps [ 0 ] ;
-
639
-
640
-
641 return $this -> authorizationForm -> showForm ( $request , $authenticationResult , $authenticationRedirect , $clientHApp )
-
642 -> withAddedHeader ( 'Cache-control' , 'no-cache' ) ;
-
643 }
-
644 }
-
645
-
646
-
647
-
648 $nonIndieAuthRequestResult = call_user_func ( $this -> handleNonIndieAuthRequest , $request ) ;
-
649 if ( $nonIndieAuthRequestResult instanceof ResponseInterface ) {
-
650 return $nonIndieAuthRequestResult ;
-
651 } else {
-
652
-
653
-
654 throw IndieAuthException :: create ( IndieAuthException :: INTERNAL_ERROR , $request ) ;
-
655 }
-
656 } catch ( IndieAuthException $e ) {
-
657
-
658 return $this -> handleException ( $e ) ;
-
659 } catch ( Exception $e ) {
-
660
-
661 $this -> logger -> error ( " Caught unknown exception: { $e } " ) ;
-
662 return $this -> handleException ( IndieAuthException :: create ( 0 , $request , $e ) ) ;
-
663 }
-
664 } ) ) ;
-
665 }
-
666
-
667
-
668
-
669
-
670
-
671
-
672
-
673
-
674
-
675
-
676
-
677
-
678
-
679
-
680
-
681
-
682
-
683
-
684
-
685 public function handleTokenEndpointRequest ( ServerRequestInterface $request ) : ResponseInterface {
-
686 if ( isIndieAuthAuthorizationCodeRedeemingRequest ( $request ) ) {
-
687 $this -> logger -> info ( 'Handling a request to redeem an authorization code for profile information.' ) ;
-
688
-
689 $bodyParams = $request -> getParsedBody ( ) ;
-
690
-
691 if ( ! isset ( $bodyParams [ 'code' ] ) ) {
-
692 $this -> logger -> warning ( 'The exchange request was missing the code parameter. Returning an error response.' ) ;
-
693 return new Response ( 400 , [ 'content-type' => 'application/json' ] , json_encode ( [
-
694 'error' => 'invalid_request' ,
-
695 'error_description' => 'The code parameter was missing.'
-
696 ] ) ) ;
-
697 }
-
698
-
699
-
700
-
701
-
702
-
703 try {
-
704
-
705
-
706 $tokenData = $this -> tokenStorage -> exchangeAuthCodeForAccessToken ( $bodyParams [ 'code' ] , function ( array $authCode ) use ( $request , $bodyParams ) {
-
707
-
708 $requiredParameters = [ 'client_id' , 'redirect_uri' , 'code_verifier' ] ;
-
709 $missingRequiredParameters = array_filter ( $requiredParameters , function ( $p ) use ( $bodyParams ) {
-
710 return ! array_key_exists ( $p , $bodyParams ) || empty ( $bodyParams [ $p ] ) ;
-
711 } ) ;
-
712 if ( ! empty ( $missingRequiredParameters ) ) {
-
713 $this -> logger -> warning ( 'The exchange request was missing required parameters. Returning an error response.' , [ 'missing' => $missingRequiredParameters ] ) ;
-
714 throw IndieAuthException :: create ( IndieAuthException :: INVALID_REQUEST , $request ) ;
-
715 }
-
716
-
717
-
718 if ( $authCode [ 'client_id' ] !== $bodyParams [ 'client_id' ]
-
719 || $authCode [ 'redirect_uri' ] !== $bodyParams [ 'redirect_uri' ] ) {
-
720 $this -> logger -> error ( "The provided client_id and/or redirect_uri did not match those stored in the token." ) ;
-
721 throw IndieAuthException :: create ( IndieAuthException :: INVALID_GRANT , $request ) ;
-
722 }
-
723
-
724
-
725
-
726 if ( ! hash_equals ( $authCode [ 'code_challenge' ] , generatePKCECodeChallenge ( $bodyParams [ 'code_verifier' ] ) ) ) {
-
727 $this -> logger -> error ( "The provided code_verifier did not hash to the stored code_challenge" ) ;
-
728 throw IndieAuthException :: create ( IndieAuthException :: INVALID_GRANT , $request ) ;
-
729 }
-
730
-
731
-
732 if ( empty ( $authCode [ 'scope' ] ) ) {
-
733 $this -> logger -> error ( "An exchange request for a token with an empty scope was sent to the token endpoint." ) ;
-
734 throw IndieAuthException :: create ( IndieAuthException :: INVALID_GRANT , $request ) ;
-
735 }
-
736 } ) ;
-
737 } catch ( IndieAuthException $e ) {
-
738
-
739 return new Response ( 400 , [ 'content-type' => 'application/json' ] , json_encode ( [
-
740 'error' => $e -> getInfo ( ) [ 'error' ] ,
-
741 'error_description' => $e -> getMessage ( )
-
742 ] ) ) ;
-
743 }
-
744
-
745 if ( is_null ( $tokenData ) ) {
-
746 $this -> logger -> error ( 'Attempting to exchange an auth code for a token resulted in null.' , $bodyParams ) ;
-
747 return new Response ( 400 , [ 'content-type' => 'application/json' ] , json_encode ( [
-
748 'error' => 'invalid_grant' ,
-
749 'error_description' => 'The provided credentials were not valid.'
-
750 ] ) ) ;
-
751 }
-
752
-
753
-
754
-
755
-
756 return new Response ( 200 , [
-
757 'content-type' => 'application/json' ,
-
758 'cache-control' => 'no-store' ,
-
759 ] , json_encode ( array_merge ( [
-
760
-
761 'token_type' => 'Bearer'
-
762 ] , array_filter ( $tokenData , function ( string $k ) {
-
763
-
764
-
765 return ! in_array ( $k , [ 'code_challenge' , 'code_challenge_method' ] ) ;
-
766 } , ARRAY_FILTER_USE_KEY ) ) ) ) ;
-
767 }
-
768
-
769 return new Response ( 400 , [ 'content-type' => 'application/json' ] , json_encode ( [
-
770 'error' => 'invalid_request' ,
-
771 'error_description' => 'Request to token endpoint was not a valid code exchange request.'
-
772 ] ) ) ;
-
773 }
-
774
-
775
-
776
-
777
-
778
-
779
-
780 protected function handleException ( IndieAuthException $exception ) : ResponseInterface {
-
781 $exceptionData = $exception -> getInfo ( ) ;
-
782
-
783 if ( $exceptionData [ 'statusCode' ] == 302 ) {
-
784
-
785 $redirectQueryParams = [
-
786 'error' => $exceptionData [ 'error' ] ?? 'invalid_request' ,
-
787 'error_description' => (string) $exception
-
788 ] ;
-
789
-
790
-
791 if ( $exception -> getCode ( ) !== IndieAuthException :: INVALID_STATE ) {
-
792 $redirectQueryParams [ 'state' ] = $exception -> getRequest ( ) -> getQueryParams ( ) [ 'state' ] ;
-
793 }
+
512 if ( ! isset ( $queryParams [ 'code_challenge_method' ] ) or ! in_array ( $queryParams [ 'code_challenge_method' ] , [ 'S256' , 'plain' ] ) ) {
+
513 $this -> logger -> error ( "The code_challenge_method parameter was missing or invalid." , $queryParams ) ;
+
514 throw IndieAuthException :: create ( IndieAuthException :: INVALID_CODE_CHALLENGE , $request ) ;
+
515 }
+
516
+
517
+
518
+
519
+
520
+
521
+
522 if ( array_key_exists ( 'scope' , $queryParams ) && ! isValidScope ( $queryParams [ 'scope' ] ) ) {
+
523 $this -> logger -> warning ( "The scope provided in an authorization request was not valid." , $queryParams ) ;
+
524 throw IndieAuthException :: create ( IndieAuthException :: INVALID_SCOPE , $request ) ;
+
525 }
+
526
+
527
+
528 if ( array_key_exists ( 'me' , $queryParams ) ) {
+
529 $queryParams [ 'me' ] = IndieAuthClient :: normalizeMeURL ( $queryParams [ 'me' ] ) ;
+
530
+
531 if ( false === $queryParams [ 'me' ] || ! isProfileUrl ( $queryParams [ 'me' ] ) ) {
+
532 $queryParams [ 'me' ] = null ;
+
533 }
+
534 }
+
535
+
536
+
537
+
538
+
539 $hash = hashAuthorizationRequestParameters ( $request , $this -> secret ) ;
+
540
+
541 $redirectQueryParams = $queryParams ;
+
542 $redirectQueryParams [ self :: HASH_QUERY_STRING_KEY ] = $hash ;
+
543 $authenticationRedirect = $request -> getUri ( ) -> withQuery ( buildQueryString ( $redirectQueryParams ) ) -> __toString ( ) ;
+
544
+
545
+
546 $this -> logger -> info ( 'Calling handle_authentication_request callback' ) ;
+
547 $authenticationResult = call_user_func ( $this -> handleAuthenticationRequestCallback , $request , $authenticationRedirect , $queryParams [ 'me' ] ?? null ) ;
+
548
+
549
+
550 if ( $authenticationResult instanceof ResponseInterface ) {
+
551 return $authenticationResult ;
+
552 } elseif ( is_array ( $authenticationResult ) ) {
+
553
+
554 if ( ! array_key_exists ( 'me' , $authenticationResult ) ) {
+
555 $this -> logger -> error ( 'The handle_authentication_request callback returned an array with no me key.' , [ 'array' => $authenticationResult ] ) ;
+
556 throw IndieAuthException :: create ( IndieAuthException :: AUTHENTICATION_CALLBACK_MISSING_ME_PARAM , $request ) ;
+
557 }
+
558
+
559
+
560 if ( isAuthorizationApprovalRequest ( $request ) ) {
+
561
+
562
+
563
+
564 if ( ! array_key_exists ( self :: HASH_QUERY_STRING_KEY , $queryParams ) ) {
+
565 $this -> logger -> warning ( "An authorization approval request did not have a " . self :: HASH_QUERY_STRING_KEY . " parameter." ) ;
+
566 throw IndieAuthException :: create ( IndieAuthException :: AUTHORIZATION_APPROVAL_REQUEST_MISSING_HASH , $request ) ;
+
567 }
+
568
+
569 $expectedHash = hashAuthorizationRequestParameters ( $request , $this -> secret ) ;
+
570 if ( ! isset ( $queryParams [ self :: HASH_QUERY_STRING_KEY ] ) or is_null ( $expectedHash ) or ! hash_equals ( $expectedHash , $queryParams [ self :: HASH_QUERY_STRING_KEY ] ) ) {
+
571 $this -> logger -> warning ( "The hash provided in the URL was invalid!" , [
+
572 'expected' => $expectedHash ,
+
573 'actual' => $queryParams [ self :: HASH_QUERY_STRING_KEY ]
+
574 ] ) ;
+
575 throw IndieAuthException :: create ( IndieAuthException :: AUTHORIZATION_APPROVAL_REQUEST_INVALID_HASH , $request ) ;
+
576 }
+
577
+
578
+
579 $code = array_merge ( $authenticationResult , [
+
580 'client_id' => $queryParams [ 'client_id' ] ,
+
581 'redirect_uri' => $queryParams [ 'redirect_uri' ] ,
+
582 'state' => $queryParams [ 'state' ] ,
+
583 'code_challenge' => $queryParams [ 'code_challenge' ] ,
+
584 'code_challenge_method' => $queryParams [ 'code_challenge_method' ] ,
+
585 'requested_scope' => $queryParams [ 'scope' ] ?? '' ,
+
586 ] ) ;
+
587
+
588
+
589 $code = $this -> authorizationForm -> transformAuthorizationCode ( $request , $code ) ;
+
590
+
591
+
592 $authCode = $this -> tokenStorage -> createAuthCode ( $code ) ;
+
593 if ( is_null ( $authCode ) ) {
+
594
+
595
+
596 $this -> logger -> error ( "Saving the authorization code failed and returned false without raising an exception." ) ;
+
597 throw IndieAuthException :: create ( IndieAuthException :: INTERNAL_ERROR_REDIRECT , $request ) ;
+
598 }
+
599
+
600
+
601 return new Response ( 302 , [
+
602 'Location' => appendQueryParams ( $queryParams [ 'redirect_uri' ] , [
+
603 'code' => $authCode ,
+
604 'state' => $code [ 'state' ]
+
605 ] ) ,
+
606 'Cache-control' => 'no-cache'
+
607 ] ) ;
+
608 }
+
609
+
610
+
611
+
612
+
613
+
614
+
615
+
616
+
617
+
618 if ( is_null ( $clientIdResponse ) || is_null ( $clientIdEffectiveUrl ) || is_null ( $clientIdMf2 ) ) {
+
619 try {
+
620
+
621 list ( $clientIdResponse , $clientIdEffectiveUrl ) = call_user_func ( $this -> httpGetWithEffectiveUrl , $queryParams [ 'client_id' ] ) ;
+
622 $clientIdMf2 = Mf2 \ parse ( (string) $clientIdResponse -> getBody ( ) , $clientIdEffectiveUrl ) ;
+
623 } catch ( ClientExceptionInterface | RequestExceptionInterface | NetworkExceptionInterface $e ) {
+
624 $this -> logger -> error ( "Caught an HTTP exception while trying to fetch the client_id. Returning an error response." , [
+
625 'client_id' => $queryParams [ 'client_id' ] ,
+
626 'exception' => $e -> __toString ( )
+
627 ] ) ;
+
628
+
629
+
630
+
631 throw IndieAuthException :: create ( IndieAuthException :: INTERNAL_ERROR_REDIRECT , $request , $e ) ;
+
632 } catch ( Exception $e ) {
+
633 $this -> logger -> error ( "Caught an unknown exception while trying to fetch the client_id. Returning an error response." , [
+
634 'exception' => $e -> __toString ( )
+
635 ] ) ;
+
636
+
637 throw IndieAuthException :: create ( IndieAuthException :: INTERNAL_ERROR_REDIRECT , $request , $e ) ;
+
638 }
+
639 }
+
640
+
641
+
642 $clientHApps = M \ findMicroformatsByProperty ( M \ findMicroformatsByType ( $clientIdMf2 , 'h-app' ) , 'url' , $queryParams [ 'client_id' ] ) ;
+
643 $clientHApp = empty ( $clientHApps ) ? null : $clientHApps [ 0 ] ;
+
644
+
645
+
646 return $this -> authorizationForm -> showForm ( $request , $authenticationResult , $authenticationRedirect , $clientHApp )
+
647 -> withAddedHeader ( 'Cache-control' , 'no-cache' ) ;
+
648 }
+
649 }
+
650
+
651
+
652
+
653 $nonIndieAuthRequestResult = call_user_func ( $this -> handleNonIndieAuthRequest , $request ) ;
+
654 if ( $nonIndieAuthRequestResult instanceof ResponseInterface ) {
+
655 return $nonIndieAuthRequestResult ;
+
656 } else {
+
657
+
658
+
659 throw IndieAuthException :: create ( IndieAuthException :: INTERNAL_ERROR , $request ) ;
+
660 }
+
661 } catch ( IndieAuthException $e ) {
+
662
+
663 return $this -> handleException ( $e ) ;
+
664 } catch ( Exception $e ) {
+
665
+
666 $this -> logger -> error ( " Caught unknown exception: { $e } " ) ;
+
667 return $this -> handleException ( IndieAuthException :: create ( 0 , $request , $e ) ) ;
+
668 }
+
669 } ) ) ;
+
670 }
+
671
+
672
+
673
+
674
+
675
+
676
+
677
+
678
+
679
+
680
+
681
+
682
+
683
+
684
+
685
+
686
+
687
+
688
+
689
+
690 public function handleTokenEndpointRequest ( ServerRequestInterface $request ) : ResponseInterface {
+
691 if ( isIndieAuthAuthorizationCodeRedeemingRequest ( $request ) ) {
+
692 $this -> logger -> info ( 'Handling a request to redeem an authorization code for profile information.' ) ;
+
693
+
694 $bodyParams = $request -> getParsedBody ( ) ;
+
695
+
696 if ( ! isset ( $bodyParams [ 'code' ] ) ) {
+
697 $this -> logger -> warning ( 'The exchange request was missing the code parameter. Returning an error response.' ) ;
+
698 return new Response ( 400 , [ 'content-type' => 'application/json' ] , json_encode ( [
+
699 'error' => 'invalid_request' ,
+
700 'error_description' => 'The code parameter was missing.'
+
701 ] ) ) ;
+
702 }
+
703
+
704
+
705
+
706
+
707
+
708 try {
+
709
+
710
+
711 $tokenData = $this -> tokenStorage -> exchangeAuthCodeForAccessToken ( $bodyParams [ 'code' ] , function ( array $authCode ) use ( $request , $bodyParams ) {
+
712
+
713 $requiredParameters = [ 'client_id' , 'redirect_uri' , 'code_verifier' ] ;
+
714 $missingRequiredParameters = array_filter ( $requiredParameters , function ( $p ) use ( $bodyParams ) {
+
715 return ! array_key_exists ( $p , $bodyParams ) || empty ( $bodyParams [ $p ] ) ;
+
716 } ) ;
+
717 if ( ! empty ( $missingRequiredParameters ) ) {
+
718 $this -> logger -> warning ( 'The exchange request was missing required parameters. Returning an error response.' , [ 'missing' => $missingRequiredParameters ] ) ;
+
719 throw IndieAuthException :: create ( IndieAuthException :: INVALID_REQUEST , $request ) ;
+
720 }
+
721
+
722
+
723 if ( $authCode [ 'client_id' ] !== $bodyParams [ 'client_id' ]
+
724 || $authCode [ 'redirect_uri' ] !== $bodyParams [ 'redirect_uri' ] ) {
+
725 $this -> logger -> error ( "The provided client_id and/or redirect_uri did not match those stored in the token." ) ;
+
726 throw IndieAuthException :: create ( IndieAuthException :: INVALID_GRANT , $request ) ;
+
727 }
+
728
+
729
+
730
+
731 if ( ! hash_equals ( $authCode [ 'code_challenge' ] , generatePKCECodeChallenge ( $bodyParams [ 'code_verifier' ] ) ) ) {
+
732 $this -> logger -> error ( "The provided code_verifier did not hash to the stored code_challenge" ) ;
+
733 throw IndieAuthException :: create ( IndieAuthException :: INVALID_GRANT , $request ) ;
+
734 }
+
735
+
736
+
737 if ( empty ( $authCode [ 'scope' ] ) ) {
+
738 $this -> logger -> error ( "An exchange request for a token with an empty scope was sent to the token endpoint." ) ;
+
739 throw IndieAuthException :: create ( IndieAuthException :: INVALID_GRANT , $request ) ;
+
740 }
+
741 } ) ;
+
742 } catch ( IndieAuthException $e ) {
+
743
+
744 return new Response ( 400 , [ 'content-type' => 'application/json' ] , json_encode ( [
+
745 'error' => $e -> getInfo ( ) [ 'error' ] ,
+
746 'error_description' => $e -> getMessage ( )
+
747 ] ) ) ;
+
748 }
+
749
+
750 if ( is_null ( $tokenData ) ) {
+
751 $this -> logger -> error ( 'Attempting to exchange an auth code for a token resulted in null.' , $bodyParams ) ;
+
752 return new Response ( 400 , [ 'content-type' => 'application/json' ] , json_encode ( [
+
753 'error' => 'invalid_grant' ,
+
754 'error_description' => 'The provided credentials were not valid.'
+
755 ] ) ) ;
+
756 }
+
757
+
758
+
759
+
760
+
761 return new Response ( 200 , [
+
762 'content-type' => 'application/json' ,
+
763 'cache-control' => 'no-store' ,
+
764 ] , json_encode ( array_merge ( [
+
765
+
766 'token_type' => 'Bearer'
+
767 ] , array_filter ( $tokenData , function ( string $k ) {
+
768
+
769
+
770 return ! in_array ( $k , [ 'code_challenge' , 'code_challenge_method' ] ) ;
+
771 } , ARRAY_FILTER_USE_KEY ) ) ) ) ;
+
772 }
+
773
+
774 return new Response ( 400 , [ 'content-type' => 'application/json' ] , json_encode ( [
+
775 'error' => 'invalid_request' ,
+
776 'error_description' => 'Request to token endpoint was not a valid code exchange request.'
+
777 ] ) ) ;
+
778 }
+
779
+
780
+
781
+
782
+
783
+
784
+
785 protected function handleException ( IndieAuthException $exception ) : ResponseInterface {
+
786 $exceptionData = $exception -> getInfo ( ) ;
+
787
+
788 if ( $exceptionData [ 'statusCode' ] == 302 ) {
+
789
+
790 $redirectQueryParams = [
+
791 'error' => $exceptionData [ 'error' ] ?? 'invalid_request' ,
+
792 'error_description' => (string) $exception
+
793 ] ;
794
-
795 return new Response ( $exceptionData [ 'statusCode' ] , [
-
796 'Location' => appendQueryParams ( (string) $exception -> getRequest ( ) -> getQueryParams ( ) [ 'redirect_uri' ] , $redirectQueryParams )
-
797 ] ) ;
-
798 } else {
-
799
-
800 return new Response ( $exception -> getStatusCode ( ) , [ 'content-type' => 'text/html' ] , renderTemplate ( $this -> exceptionTemplatePath , [
-
801 'request' => $exception -> getRequest ( ) ,
-
802 'exception' => $exception
-
803 ] ) ) ;
-
804 }
-
805 }
-
806 }
+
795
+
796 if ( $exception -> getCode ( ) !== IndieAuthException :: INVALID_STATE ) {
+
797 $redirectQueryParams [ 'state' ] = $exception -> getRequest ( ) -> getQueryParams ( ) [ 'state' ] ;
+
798 }
+
799
+
800 return new Response ( $exceptionData [ 'statusCode' ] , [
+
801 'Location' => appendQueryParams ( (string) $exception -> getRequest ( ) -> getQueryParams ( ) [ 'redirect_uri' ] , $redirectQueryParams )
+
802 ] ) ;
+
803 } else {
+
804
+
805 return new Response ( $exception -> getStatusCode ( ) , [ 'content-type' => 'text/html' ] , renderTemplate ( $this -> exceptionTemplatePath , [
+
806 'request' => $exception -> getRequest ( ) ,
+
807 'exception' => $exception
+
808 ] ) ) ;
+
809 }
+
810 }
+
811 }
@@ -1026,7 +1031,7 @@
Legend
Executed Not Executed Dead Code
- Generated by php-code-coverage 9.2.6 using PHP 7.4.19 with Xdebug 3.0.4 and PHPUnit 9.5.5 at Mon Jun 14 23:14:13 UTC 2021.
+ Generated by php-code-coverage 9.2.6 using PHP 7.4.19 with Xdebug 3.0.4 and PHPUnit 9.5.5 at Wed Jun 16 21:41:10 UTC 2021.
diff --git a/docs/coverage/Storage/FilesystemJsonStorage.php.html b/docs/coverage/Storage/FilesystemJsonStorage.php.html
index 33a7b55..b5aca98 100644
--- a/docs/coverage/Storage/FilesystemJsonStorage.php.html
+++ b/docs/coverage/Storage/FilesystemJsonStorage.php.html
@@ -419,30 +419,30 @@
38
39
40 public function __construct ( string $path , string $secret , ? int $authCodeTtl = null , ? int $accessTokenTtl = null , $cleanUpNow = false , ? LoggerInterface $logger = null ) {
- 41 $this -> logger = $logger ?? new NullLogger ( ) ;
+ 41 $this -> logger = $logger ?? new NullLogger ( ) ;
42
- 43 if ( strlen ( $secret ) < 64 ) {
+ 43 if ( strlen ( $secret ) < 64 ) {
44 throw new Exception ( "\$secret must be a string with a minimum length of 64 characters. Make one with Taproot\IndieAuth\generateRandomString(64)" ) ;
45 }
- 46 $this -> secret = $secret ;
+ 46 $this -> secret = $secret ;
47
- 48 $this -> path = rtrim ( $path , DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR ;
+ 48 $this -> path = rtrim ( $path , DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR ;
49
- 50 $this -> authCodeTtl = $authCodeTtl ?? self :: DEFAULT_AUTH_CODE_TTL ;
- 51 $this -> accessTokenTtl = $accessTokenTtl ?? self :: DEFAULT_ACCESS_TOKEN_TTL ;
+ 50 $this -> authCodeTtl = $authCodeTtl ?? self :: DEFAULT_AUTH_CODE_TTL ;
+ 51 $this -> accessTokenTtl = $accessTokenTtl ?? self :: DEFAULT_ACCESS_TOKEN_TTL ;
52
- 53 @ mkdir ( $this -> path , 0777 , true ) ;
+ 53 @ mkdir ( $this -> path , 0777 , true ) ;
54
- 55 if ( $cleanUpNow ) {
+ 55 if ( $cleanUpNow ) {
56 $this -> deleteExpiredTokens ( ) ;
57 }
- 58 }
+ 58 }
59
60
61
62 public function setLogger ( LoggerInterface $logger ) {
- 63 $this -> logger = $logger ;
- 64 }
+ 63 $this -> logger = $logger ;
+ 64 }
65
66
67
@@ -663,7 +663,7 @@
Legend
Executed Not Executed Dead Code
- Generated by php-code-coverage 9.2.6 using PHP 7.4.19 with Xdebug 3.0.4 and PHPUnit 9.5.5 at Mon Jun 14 23:14:13 UTC 2021.
+ Generated by php-code-coverage 9.2.6 using PHP 7.4.19 with Xdebug 3.0.4 and PHPUnit 9.5.5 at Wed Jun 16 21:41:10 UTC 2021.
diff --git a/docs/coverage/Storage/Sqlite3Storage.php.html b/docs/coverage/Storage/Sqlite3Storage.php.html
index cbd00c1..2d01a02 100644
--- a/docs/coverage/Storage/Sqlite3Storage.php.html
+++ b/docs/coverage/Storage/Sqlite3Storage.php.html
@@ -79,7 +79,7 @@
Legend
Executed Not Executed Dead Code
- Generated by php-code-coverage 9.2.6 using PHP 7.4.19 with Xdebug 3.0.4 and PHPUnit 9.5.5 at Mon Jun 14 23:14:13 UTC 2021.
+ Generated by php-code-coverage 9.2.6 using PHP 7.4.19 with Xdebug 3.0.4 and PHPUnit 9.5.5 at Wed Jun 16 21:41:10 UTC 2021.
diff --git a/docs/coverage/Storage/TokenStorageInterface.php.html b/docs/coverage/Storage/TokenStorageInterface.php.html
index b780ef7..c38a5f4 100644
--- a/docs/coverage/Storage/TokenStorageInterface.php.html
+++ b/docs/coverage/Storage/TokenStorageInterface.php.html
@@ -235,7 +235,7 @@
Legend
Executed Not Executed Dead Code
- Generated by php-code-coverage 9.2.6 using PHP 7.4.19 with Xdebug 3.0.4 and PHPUnit 9.5.5 at Mon Jun 14 23:14:13 UTC 2021.
+ Generated by php-code-coverage 9.2.6 using PHP 7.4.19 with Xdebug 3.0.4 and PHPUnit 9.5.5 at Wed Jun 16 21:41:10 UTC 2021.
diff --git a/docs/coverage/Storage/dashboard.html b/docs/coverage/Storage/dashboard.html
index 5d0eb95..e4064c9 100644
--- a/docs/coverage/Storage/dashboard.html
+++ b/docs/coverage/Storage/dashboard.html
@@ -142,7 +142,7 @@
diff --git a/docs/coverage/Storage/index.html b/docs/coverage/Storage/index.html
index 33db78b..be1da76 100644
--- a/docs/coverage/Storage/index.html
+++ b/docs/coverage/Storage/index.html
@@ -137,7 +137,7 @@