@@ -403,6 +403,46 @@ def test_code_post_auth_deny_custom_redirect_uri_scheme(self):
403
403
self .assertIn ('custom-scheme://example.com?' , response ['Location' ])
404
404
self .assertIn ("error=access_denied" , response ['Location' ])
405
405
406
+ def test_code_post_auth_redirection_uri_with_querystring (self ):
407
+ """
408
+ Tests that a redirection uri with query string is allowed
409
+ and query string is retained on redirection.
410
+ See http://tools.ietf.org/html/rfc6749#section-3.1.2
411
+ """
412
+ self .client .login (username = "test_user" , password = "123456" )
413
+
414
+ form_data = {
415
+ 'client_id' : self .application .client_id ,
416
+ 'state' : 'random_state_string' ,
417
+ 'scope' : 'read write' ,
418
+ 'redirect_uri' : 'http://example.com?foo=bar' ,
419
+ 'response_type' : 'code' ,
420
+ 'allow' : True ,
421
+ }
422
+
423
+ response = self .client .post (reverse ('oauth2_provider:authorize' ), data = form_data )
424
+ self .assertEqual (response .status_code , 302 )
425
+ self .assertIn ("http://example.com?foo=bar" , response ['Location' ])
426
+ self .assertIn ("code=" , response ['Location' ])
427
+
428
+ def test_code_post_auth_fails_when_redirect_uri_path_is_invalid (self ):
429
+ """
430
+ Tests that a redirection uri is matched using scheme + netloc + path
431
+ """
432
+ self .client .login (username = "test_user" , password = "123456" )
433
+
434
+ form_data = {
435
+ 'client_id' : self .application .client_id ,
436
+ 'state' : 'random_state_string' ,
437
+ 'scope' : 'read write' ,
438
+ 'redirect_uri' : 'http://example.com/a?foo=bar' ,
439
+ 'response_type' : 'code' ,
440
+ 'allow' : True ,
441
+ }
442
+
443
+ response = self .client .post (reverse ('oauth2_provider:authorize' ), data = form_data )
444
+ self .assertEqual (response .status_code , 400 )
445
+
406
446
407
447
class TestAuthorizationCodeTokenView (BaseTest ):
408
448
def get_auth (self ):
@@ -759,6 +799,108 @@ def test_malicious_redirect_uri(self):
759
799
response = self .client .post (reverse ('oauth2_provider:token' ), data = token_request_data )
760
800
self .assertEqual (response .status_code , 401 )
761
801
802
+ def test_code_exchange_succeed_when_redirect_uri_match (self ):
803
+ """
804
+ Tests code exchange succeed when redirect uri matches the one used for code request
805
+ """
806
+ self .client .login (username = "test_user" , password = "123456" )
807
+
808
+ # retrieve a valid authorization code
809
+ authcode_data = {
810
+ 'client_id' : self .application .client_id ,
811
+ 'state' : 'random_state_string' ,
812
+ 'scope' : 'read write' ,
813
+ 'redirect_uri' : 'http://example.it?foo=bar' ,
814
+ 'response_type' : 'code' ,
815
+ 'allow' : True ,
816
+ }
817
+ response = self .client .post (reverse ('oauth2_provider:authorize' ), data = authcode_data )
818
+ query_dict = parse_qs (urlparse (response ['Location' ]).query )
819
+ authorization_code = query_dict ['code' ].pop ()
820
+
821
+ # exchange authorization code for a valid access token
822
+ token_request_data = {
823
+ 'grant_type' : 'authorization_code' ,
824
+ 'code' : authorization_code ,
825
+ 'redirect_uri' : 'http://example.it?foo=bar'
826
+ }
827
+ auth_headers = self .get_basic_auth_header (self .application .client_id , self .application .client_secret )
828
+
829
+ response = self .client .post (reverse ('oauth2_provider:token' ), data = token_request_data , ** auth_headers )
830
+ self .assertEqual (response .status_code , 200 )
831
+
832
+ content = json .loads (response .content .decode ("utf-8" ))
833
+ self .assertEqual (content ['token_type' ], "Bearer" )
834
+ self .assertEqual (content ['scope' ], "read write" )
835
+ self .assertEqual (content ['expires_in' ], oauth2_settings .ACCESS_TOKEN_EXPIRE_SECONDS )
836
+
837
+ def test_code_exchange_fails_when_redirect_uri_does_not_match (self ):
838
+ """
839
+ Tests code exchange fails when redirect uri does not match the one used for code request
840
+ """
841
+ self .client .login (username = "test_user" , password = "123456" )
842
+
843
+ # retrieve a valid authorization code
844
+ authcode_data = {
845
+ 'client_id' : self .application .client_id ,
846
+ 'state' : 'random_state_string' ,
847
+ 'scope' : 'read write' ,
848
+ 'redirect_uri' : 'http://example.it?foo=bar' ,
849
+ 'response_type' : 'code' ,
850
+ 'allow' : True ,
851
+ }
852
+ response = self .client .post (reverse ('oauth2_provider:authorize' ), data = authcode_data )
853
+ query_dict = parse_qs (urlparse (response ['Location' ]).query )
854
+ authorization_code = query_dict ['code' ].pop ()
855
+
856
+ # exchange authorization code for a valid access token
857
+ token_request_data = {
858
+ 'grant_type' : 'authorization_code' ,
859
+ 'code' : authorization_code ,
860
+ 'redirect_uri' : 'http://example.it?foo=baraa'
861
+ }
862
+ auth_headers = self .get_basic_auth_header (self .application .client_id , self .application .client_secret )
863
+
864
+ response = self .client .post (reverse ('oauth2_provider:token' ), data = token_request_data , ** auth_headers )
865
+ self .assertEqual (response .status_code , 401 )
866
+
867
+ def test_code_exchange_succeed_when_redirect_uri_match_with_multiple_query_params (self ):
868
+ """
869
+ Tests code exchange succeed when redirect uri matches the one used for code request
870
+ """
871
+ self .client .login (username = "test_user" , password = "123456" )
872
+ self .application .redirect_uris = "http://localhost http://example.com?foo=bar"
873
+ self .application .save ()
874
+
875
+ # retrieve a valid authorization code
876
+ authcode_data = {
877
+ 'client_id' : self .application .client_id ,
878
+ 'state' : 'random_state_string' ,
879
+ 'scope' : 'read write' ,
880
+ 'redirect_uri' : 'http://example.com?bar=baz&foo=bar' ,
881
+ 'response_type' : 'code' ,
882
+ 'allow' : True ,
883
+ }
884
+ response = self .client .post (reverse ('oauth2_provider:authorize' ), data = authcode_data )
885
+ query_dict = parse_qs (urlparse (response ['Location' ]).query )
886
+ authorization_code = query_dict ['code' ].pop ()
887
+
888
+ # exchange authorization code for a valid access token
889
+ token_request_data = {
890
+ 'grant_type' : 'authorization_code' ,
891
+ 'code' : authorization_code ,
892
+ 'redirect_uri' : 'http://example.com?bar=baz&foo=bar'
893
+ }
894
+ auth_headers = self .get_basic_auth_header (self .application .client_id , self .application .client_secret )
895
+
896
+ response = self .client .post (reverse ('oauth2_provider:token' ), data = token_request_data , ** auth_headers )
897
+ self .assertEqual (response .status_code , 200 )
898
+
899
+ content = json .loads (response .content .decode ("utf-8" ))
900
+ self .assertEqual (content ['token_type' ], "Bearer" )
901
+ self .assertEqual (content ['scope' ], "read write" )
902
+ self .assertEqual (content ['expires_in' ], oauth2_settings .ACCESS_TOKEN_EXPIRE_SECONDS )
903
+
762
904
763
905
class TestAuthorizationCodeProtectedResource (BaseTest ):
764
906
def test_resource_access_allowed (self ):
0 commit comments