1
1
<?php
2
- require_once ( 'OAuth2ServerException.php ' ) ;
3
- require_once ( 'OAuth2AuthenticateException.php ' ) ;
4
- require_once ( 'OAuth2RedirectException.php ' ) ;
2
+ require 'OAuth2ServerException.php ' ;
3
+ require 'OAuth2AuthenticateException.php ' ;
4
+ require 'OAuth2RedirectException.php ' ;
5
5
/**
6
6
* @mainpage
7
7
* OAuth 2.0 server in PHP, originally written for
@@ -85,6 +85,7 @@ class OAuth2 {
85
85
const CONFIG_TOKEN_TYPE = 'token_type ' ; // Token type to respond with. Currently only "Bearer" supported.
86
86
const CONFIG_WWW_REALM = 'realm ' ;
87
87
const CONFIG_ENFORCE_INPUT_REDIRECT = 'enforce_redirect ' ; // Set to true to enforce redirect_uri on input for both authorize and token steps.
88
+ const CONFIG_ENFORCE_STATE = 'enforce_state ' ; // Set to true to enforce state to be passed in authorization (see http://tools.ietf.org/html/draft-ietf-oauth-v2-21#section-10.12)
88
89
89
90
/**
90
91
* Regex to filter out the client identifier (described in Section 2 of IETF draft).
@@ -354,6 +355,7 @@ protected function setDefaultOptions() {
354
355
self ::CONFIG_WWW_REALM => self ::DEFAULT_WWW_REALM ,
355
356
self ::CONFIG_TOKEN_TYPE => self ::TOKEN_TYPE_BEARER ,
356
357
self ::CONFIG_ENFORCE_INPUT_REDIRECT => FALSE ,
358
+ self ::CONFIG_ENFORCE_STATE => FALSE ,
357
359
self ::CONFIG_SUPPORTED_SCOPES => array () // This is expected to be passed in on construction. Scopes can be an aribitrary string.
358
360
);
359
361
}
@@ -551,15 +553,13 @@ private function checkScope($required_scope, $available_scope) {
551
553
* This would be called from the "/token" endpoint as defined in the spec.
552
554
* Obviously, you can call your endpoint whatever you want.
553
555
*
554
- * FIXME: According to section 4.1.3 (http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.3),
555
- * if the "Authorization Request" (auth_code) contained the redirect_uri, then it becomes mandatory when requesting the access token.
556
- * This is *not* currently enforced in this library.
557
- *
558
556
* @param $inputData - The draft specifies that the parameters should be
559
557
* retrieved from POST, but you can override to whatever method you like.
560
558
* @throws OAuth2ServerException
561
559
*
562
560
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4
561
+ * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-21#section-10.6
562
+ * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-21#section-4.1.3
563
563
*
564
564
* @ingroup oauth2_section_4
565
565
*/
@@ -617,10 +617,13 @@ public function grantAccessToken(array $inputData = NULL, array $authHeaders = N
617
617
if ($ stored === NULL || $ client [0 ] != $ stored ["client_id " ])
618
618
throw new OAuth2ServerException (self ::HTTP_BAD_REQUEST , self ::ERROR_INVALID_GRANT , "Refresh token doesn't exist or is invalid for the client " );
619
619
620
- // Validate the redirect URI
620
+ // Validate the redirect URI. They must match EXACTLY (as opposed to authorization step where they only need to have the same beginning).
621
+ // See http://tools.ietf.org/html/draft-ietf-oauth-v2-21#section-10.6
622
+ // Because we store the validated redirect_uri in getAuthorizeParams() (which is either the stored or input param) we are able to
623
+ // do this check everytime (regardless of whether a confidential or public client is being used).
621
624
$ missing = (!$ stored ["redirect_uri " ] && !$ input ["redirect_uri " ]); // both stored and supplied are missing - we must have at least one!
622
- if ($ missing || ! $ this -> validateRedirectUri ( $ input ["redirect_uri " ], $ stored ["redirect_uri " ]) )
623
- throw new OAuth2ServerException (self ::HTTP_BAD_REQUEST , self ::ERROR_REDIRECT_URI_MISMATCH , "The redirect URI is missing or invalid " );
625
+ if ($ missing || $ input ["redirect_uri " ] !== $ stored ["redirect_uri " ])
626
+ throw new OAuth2ServerException (self ::HTTP_BAD_REQUEST , self ::ERROR_REDIRECT_URI_MISMATCH , "The redirect URI is missing or do not match " );
624
627
625
628
if ($ stored ["expires " ] < time ())
626
629
throw new OAuth2ServerException (self ::HTTP_BAD_REQUEST , self ::ERROR_INVALID_GRANT , "The authorization code has expired " );
@@ -702,7 +705,7 @@ public function grantAccessToken(array $inputData = NULL, array $authHeaders = N
702
705
703
706
// Check scope, if provided
704
707
if ($ input ["scope " ] && (!is_array ($ stored ) || !isset ($ stored ["scope " ]) || !$ this ->checkScope ($ input ["scope " ], $ stored ["scope " ])))
705
- throw new OAuth2ServerException (self ::HTTP_BAD_REQUEST , self ::ERROR_INVALID_SCOPE );
708
+ throw new OAuth2ServerException (self ::HTTP_BAD_REQUEST , self ::ERROR_INVALID_SCOPE , ' An unsupported scope was requested. ' );
706
709
707
710
$ user_id = isset ($ stored ['user_id ' ]) ? $ stored ['user_id ' ] : null ;
708
711
$ token = $ this ->createAccessToken ($ client [0 ], $ user_id , $ stored ['scope ' ]);
@@ -739,7 +742,7 @@ protected function getClientCredentials(array $inputData, array $authHeaders) {
739
742
return array ($ authHeaders ['PHP_AUTH_USER ' ], $ authHeaders ['PHP_AUTH_PW ' ]);
740
743
}
741
744
elseif (empty ($ inputData ['client_id ' ])) { // No credentials were specified
742
- throw new OAuth2ServerException (self ::HTTP_BAD_REQUEST , self ::ERROR_INVALID_CLIENT );
745
+ throw new OAuth2ServerException (self ::HTTP_BAD_REQUEST , self ::ERROR_INVALID_CLIENT , ' Client id was not found in the headers or body ' );
743
746
}
744
747
else {
745
748
// This method is not recommended, but is supported by specification
@@ -751,8 +754,10 @@ protected function getClientCredentials(array $inputData, array $authHeaders) {
751
754
752
755
/**
753
756
* Pull the authorization request data out of the HTTP request.
754
- * The redirect_uri is OPTIONAL as per draft 20. But your implenetation can enforce it
755
- * by setting CONFIG_ENFORCE_INPUT_REDIRECT to true.
757
+ * - The redirect_uri is OPTIONAL as per draft 20. But your implementation can enforce it
758
+ * by setting CONFIG_ENFORCE_INPUT_REDIRECT to true.
759
+ * - The state is OPTIONAL but recommended to enforce CSRF. Draft 21 states, however, that
760
+ * CSRF protection is MANDATORY. You can enforce this by setting the CONFIG_ENFORCE_STATE to true.
756
761
*
757
762
* @param $inputData - The draft specifies that the parameters should be
758
763
* retrieved from GET, but you can override to whatever method you like.
@@ -762,6 +767,7 @@ protected function getClientCredentials(array $inputData, array $authHeaders) {
762
767
*
763
768
* @throws OAuth2ServerException
764
769
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.1
770
+ * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-21#section-10.12
765
771
*
766
772
* @ingroup oauth2_section_3
767
773
*/
@@ -786,8 +792,8 @@ public function getAuthorizeParams(array $inputData = NULL) {
786
792
787
793
// Get client details
788
794
$ stored = $ this ->storage ->getClientDetails ($ input ["client_id " ]);
789
- if (empty ( $ stored) || ! is_array ( $ stored ) ) {
790
- throw new OAuth2ServerException (self ::HTTP_BAD_REQUEST , self ::ERROR_INVALID_CLIENT );
795
+ if ($ stored === FALSE ) {
796
+ throw new OAuth2ServerException (self ::HTTP_BAD_REQUEST , self ::ERROR_INVALID_CLIENT , " Client id does not exist " );
791
797
}
792
798
793
799
// Make sure a valid redirect_uri was supplied. If specified, it must match the stored URI.
@@ -801,7 +807,7 @@ public function getAuthorizeParams(array $inputData = NULL) {
801
807
throw new OAuth2ServerException (self ::HTTP_BAD_REQUEST , self ::ERROR_REDIRECT_URI_MISMATCH , 'The redirect URI is mandatory and was not supplied. ' );
802
808
}
803
809
if ($ stored ["redirect_uri " ] && $ input ["redirect_uri " ] && !$ this ->validateRedirectUri ($ input ["redirect_uri " ], $ stored ["redirect_uri " ])) {
804
- throw new OAuth2ServerException (self ::HTTP_BAD_REQUEST , self ::ERROR_REDIRECT_URI_MISMATCH , 'The rediect URI provided is missing or does not match ' );
810
+ throw new OAuth2ServerException (self ::HTTP_BAD_REQUEST , self ::ERROR_REDIRECT_URI_MISMATCH , 'The redirect URI provided is missing or does not match ' );
805
811
}
806
812
807
813
// Select the redirect URI
@@ -819,7 +825,11 @@ public function getAuthorizeParams(array $inputData = NULL) {
819
825
820
826
// Validate that the requested scope is supported
821
827
if ($ input ["scope " ] && !$ this ->checkScope ($ input ["scope " ], $ this ->getVariable (self ::CONFIG_SUPPORTED_SCOPES )))
822
- throw new OAuth2RedirectException ($ input ["redirect_uri " ], self ::ERROR_INVALID_SCOPE , NULL , $ input ["state " ]);
828
+ throw new OAuth2RedirectException ($ input ["redirect_uri " ], self ::ERROR_INVALID_SCOPE , 'An unsupported scope was requested. ' , $ input ["state " ]);
829
+
830
+ // Validate state parameter exists (if configured to enforce this)
831
+ if ($ this ->getVariable (self ::CONFIG_ENFORCE_STATE ) && !$ input ["state " ])
832
+ throw new OAuth2RedirectException ($ input ["redirect_uri " ], self ::ERROR_INVALID_REQUEST , "The state parameter is required. " );
823
833
824
834
// Return retrieved client details together with input
825
835
return ($ input + $ stored );
@@ -1047,8 +1057,8 @@ protected function genAuthCode() {
1047
1057
*/
1048
1058
protected function getAuthorizationHeader () {
1049
1059
return array (
1050
- 'PHP_AUTH_USER ' => $ _SERVER ['PHP_AUTH_USER ' ],
1051
- 'PHP_AUTH_PW ' => $ _SERVER ['PHP_AUTH_PW ' ]
1060
+ 'PHP_AUTH_USER ' => isset ( $ _SERVER ['PHP_AUTH_USER ' ]) ? $ _SERVER [ ' PHP_AUTH_USER ' ] : '' ,
1061
+ 'PHP_AUTH_PW ' => isset ( $ _SERVER ['PHP_AUTH_PW ' ]) ? $ _SERVER [ ' PHP_AUTH_PW ' ] : '' ,
1052
1062
);
1053
1063
}
1054
1064
0 commit comments