diff --git a/oidc_provider/tests/test_authorization_code_flow.py b/oidc_provider/tests/test_authorize_endpoint.py similarity index 53% rename from oidc_provider/tests/test_authorization_code_flow.py rename to oidc_provider/tests/test_authorize_endpoint.py index 8d118bf..9122235 100644 --- a/oidc_provider/tests/test_authorization_code_flow.py +++ b/oidc_provider/tests/test_authorize_endpoint.py @@ -1,12 +1,15 @@ +import urllib + from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth.models import AnonymousUser from django.core.urlresolvers import reverse from django.test import RequestFactory from django.test import TestCase + from oidc_provider import settings +from oidc_provider.models import * from oidc_provider.tests.utils import * from oidc_provider.views import * -import urllib class AuthorizationCodeFlowTestCase(TestCase): @@ -15,26 +18,9 @@ class AuthorizationCodeFlowTestCase(TestCase): self.factory = RequestFactory() self.user = create_fake_user() self.client = create_fake_client(response_type='code') + self.state = uuid.uuid4().hex - def _create_authorize_url(self, response_type, scope=['openid', 'email']): - """ - Generate an OpenID Authentication Request using the fake client data. - """ - path = reverse('oidc_provider:authorize') - - query_str = urllib.urlencode({ - 'client_id': self.client.client_id, - 'response_type': response_type, - 'redirect_uri': self.client.default_redirect_uri, - 'scope': ' '.join(scope), - 'state': 'abcdefg', - }).replace('+', '%20') - - url = path + '?' + query_str - - return url - - def test_authorize_invalid_parameters(self): + def test_missing_parameters(self): """ If the request fails due to a missing, invalid, or mismatching redirection URI, or if the client identifier is missing or invalid, @@ -43,6 +29,7 @@ class AuthorizationCodeFlowTestCase(TestCase): See: https://tools.ietf.org/html/rfc6749#section-4.1.2.1 """ url = reverse('oidc_provider:authorize') + request = self.factory.get(url) response = AuthorizeView.as_view()(request) @@ -50,7 +37,7 @@ class AuthorizationCodeFlowTestCase(TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(bool(response.content), True) - def test_authorize_invalid_response_type(self): + def test_invalid_response_type(self): """ The OP informs the RP by using the Error Response parameters defined in Section 4.1.2.1 of OAuth 2.0. @@ -58,7 +45,15 @@ class AuthorizationCodeFlowTestCase(TestCase): See: http://openid.net/specs/openid-connect-core-1_0.html#AuthError """ # Create an authorize request with an unsupported response_type. - url = self._create_authorize_url(response_type='code id_token') + query_str = urllib.urlencode({ + 'client_id': self.client.client_id, + 'response_type': 'something_wrong', + 'redirect_uri': self.client.default_redirect_uri, + 'scope': 'openid email', + 'state': self.state, + }).replace('+', '%20') + + url = reverse('oidc_provider:authorize') + '?' + query_str request = self.factory.get(url) @@ -71,14 +66,22 @@ class AuthorizationCodeFlowTestCase(TestCase): query_exists = 'error=' in response['Location'] self.assertEqual(query_exists, True) - def test_authorize_user_not_logged(self): + def test_codeflow_user_not_logged(self): """ The Authorization Server attempts to Authenticate the End-User by redirecting to the login view. See: http://openid.net/specs/openid-connect-core-1_0.html#Authenticates """ - url = self._create_authorize_url(response_type='code') + query_str = urllib.urlencode({ + 'client_id': self.client.client_id, + 'response_type': 'code', + 'redirect_uri': self.client.default_redirect_uri, + 'scope': 'openid email', + 'state': self.state, + }).replace('+', '%20') + + url = reverse('oidc_provider:authorize') + '?' + query_str request = self.factory.get(url) request.user = AnonymousUser() @@ -98,7 +101,7 @@ class AuthorizationCodeFlowTestCase(TestCase): is_next_ok = False self.assertEqual(is_next_ok, True) - def test_authorize_user_consent(self): + def test_codeflow_user_consent_inputs(self): """ Once the End-User is authenticated, the Authorization Server MUST obtain an authorization decision before releasing information to @@ -107,8 +110,17 @@ class AuthorizationCodeFlowTestCase(TestCase): See: http://openid.net/specs/openid-connect-core-1_0.html#Consent """ response_type = 'code' + state = 'openid email' - url = self._create_authorize_url(response_type=response_type) + query_str = urllib.urlencode({ + 'client_id': self.client.client_id, + 'response_type': response_type, + 'redirect_uri': self.client.default_redirect_uri, + 'scope': state, + 'state': self.state, + }).replace('+', '%20') + + url = reverse('oidc_provider:authorize') + '?' + query_str request = self.factory.get(url) # Simulate that the user is logged. @@ -116,9 +128,8 @@ class AuthorizationCodeFlowTestCase(TestCase): response = AuthorizeView.as_view()(request) - # Check if hidden inputs exists in the form, also - # check if their values are valid. - + # Check if hidden inputs exists in the form, + # also if their values are valid. input_html = '' to_check = { @@ -131,3 +142,64 @@ class AuthorizationCodeFlowTestCase(TestCase): is_input_ok = input_html.format(key, value) in response.content self.assertEqual(is_input_ok, True, msg='Hidden input for "'+key+'" fails.') + + def test_codeflow_user_consent_response(self): + """ + First, + if the user denied the consent we must ensure that + the error response parameters are added to the query component + of the Redirection URI. + + Second, + if the user allow the RP then the server MUST return + the parameters defined in Section 4.1.2 of OAuth 2.0 [RFC6749] + by adding them as query parameters to the redirect_uri. + """ + response_type = 'code' + scope = 'openid email' + + url = reverse('oidc_provider:authorize') + + post_data = { + 'client_id': self.client.client_id, + 'redirect_uri': self.client.default_redirect_uri, + 'response_type': response_type, + 'scope': scope, + 'state': self.state, + } + + request = self.factory.post(url, data=post_data) + # Simulate that the user is logged. + request.user = self.user + + response = AuthorizeView.as_view()(request) + + # Because user doesn't allow app, SHOULD exists an error parameter + # in the query. + self.assertEqual('error=' in response['Location'], True) + self.assertEqual('access_denied' in response['Location'], True) + + # Simulate user authorization. + post_data['allow'] = 'Accept' # Should be the value of the button. + + request = self.factory.post(url, data=post_data) + # Simulate that the user is logged. + request.user = self.user + + response = AuthorizeView.as_view()(request) + + # Validate the code returned by the OP. + code = (response['Location'].split('code='))[1].split('&')[0] + try: + code = Code.objects.get(code=code) + if (code.client == self.client) or (code.user == self.user): + is_code_ok = True + else: + is_code_ok = False + except: + is_code_ok = False + self.assertEqual(is_code_ok, True) + + # Check if the state is returned. + state = (response['Location'].split('state='))[1].split('&')[0] + self.assertEqual(state == self.state, True)