def test_grant_authorization_code(self, settings, client, user): # Secure connection check with settings(DEBUG=False): resp = client.get(URL_TOKEN, {}) assert resp.status_code == 403 settings.DEBUG = True resp = client.post(URL_TOKEN, {'grant_type': 'a'}) assert resp.status_code == 400 assert resp.content_json['error'] == 'unsupported_grant_type' client_1 = Client(user=user, title='OClient') client_1.save() redirect_1 = RedirectionEndpoint(client=client_1, uri='http://redirect-test.com') redirect_1.save() code_1 = AuthorizationCode(user=user, client=client_1, uri=redirect_1.uri) code_1.save() Scope(identifier='scope1').save() # Missing client authentication data. resp = client.post(URL_TOKEN, {'grant_type': 'authorization_code', 'scope': 'scope1'}) assert resp.status_code == 401 assert resp.content_json['error'] == 'invalid_client' # Missing all required params. resp = client.post( URL_TOKEN, {'grant_type': 'authorization_code', 'scope': 'scope1', 'client_id': client_1.identifier, 'client_secret': client_1.password}) assert resp.status_code == 400 assert resp.content_json['error'] == 'invalid_request' # Missing redirect URI. resp = client.post( URL_TOKEN, {'grant_type': 'authorization_code', 'scope': 'scope1', 'code': 'wrong_code', 'client_id': client_1.identifier, 'client_secret': client_1.password}) assert resp.status_code == 400 assert resp.content_json['error'] == 'invalid_request' # Missing code. resp = client.post( URL_TOKEN, {'grant_type': 'authorization_code', 'scope': 'scope1', 'redirect_uri': 'http://wrong-url.com', 'client_id': client_1.identifier, 'client_secret': client_1.password}) assert resp.status_code == 400 assert resp.content_json['error'] == 'invalid_request' # Wrong code. resp = client.post( URL_TOKEN, {'grant_type': 'authorization_code', 'scope': 'scope1', 'code': 'invalid', 'redirect_uri': 'http://localhost:8000/abc/', 'client_id': client_1.identifier, 'client_secret': client_1.password}) assert resp.status_code == 400 assert resp.content_json['error'] == 'invalid_grant' # Wrong URI. resp = client.post( URL_TOKEN, {'grant_type': 'authorization_code', 'scope': 'scope1', 'code': code_1.code, 'redirect_uri': 'http://wrong-url.com/', 'client_id': client_1.identifier, 'client_secret': client_1.password}) assert resp.status_code == 400 assert resp.content_json['error'] == 'invalid_grant' # Valid call for a token. resp = client.post( URL_TOKEN, {'grant_type': 'authorization_code', 'scope': 'scope1', 'code': code_1.code, 'redirect_uri': redirect_1.uri, 'client_id': client_1.identifier, 'client_secret': client_1.password}) assert resp.status_code == 200 assert 'access_token' in resp.content_json assert 'refresh_token' in resp.content_json assert 'token_type' in resp.content_json # An additional call for code issues token and code invalidation. resp = client.post( URL_TOKEN, {'grant_type': 'authorization_code', 'scope': 'scope1', 'code': '1234567', 'redirect_uri': 'http://localhost:8000/abc/', 'client_id': client_1.identifier, 'client_secret': client_1.password}) assert resp.status_code == 400 assert resp.content_json['error'] == 'invalid_grant'
def endpoint_authorize(request): """ SPEC: The authorization endpoint is used to interact with the resource owner and obtain an authorization grant. SPEC: The authorization server MUST support TLS... The authorization server MUST support the use of the HTTP "GET" method [RFC2616] for the authorization endpoint, and MAY support the use of the "POST" method as well. """ # SPEC: Since requests to the authorization endpoint result in user authentication # and the transmission of clear-text credentials (in the HTTP response), # the authorization server MUST require the use of a transport-layer # security mechanism when sending requests to the authorization endpoint. if not request.is_secure() and not settings.DEBUG: # Insecure connections are only available in debug mode. return ep_auth_response_error_page( request, _('OAuth requires secure connection to be established.'), 403) if request.POST.get('auth_decision') is None: # User has made no decision on auth confirmation yet. input_params = filter_input_params(request.REQUEST) response_type = input_params.get('response_type') client_id = input_params.get('client_id') redirect_uri = input_params.get('redirect_uri') redirect_uri_final = redirect_uri if client_id is None: # Fail fast without a DB hit. return ep_auth_response_error_page( request, _('Client ID must be supplied.')) if response_type not in REGISTRY_EP_AUTH_RESPONSE_TYPE: return ep_auth_response_error_page( request, _('Unknown response type requested. Expected: %s.') % ', '.join(REGISTRY_EP_AUTH_RESPONSE_TYPE)) try: client = Client.objects.get(identifier=client_id) except ObjectDoesNotExist: LOGGER.error( 'Invalid client ID supplied. Value "%s" was sent from IP "%s".' % (client_id, get_remote_ip(request))) return ep_auth_response_error_page( request, _('Invalid client ID is supplied.')) # TODO There should be at least one redirection URI associated with a client. URI validity should be checked while such an association is made. registered_uris = [ url[0] for url in client.redirectionendpoint_set.values_list('uri') ] # Check redirection URI validity. if redirect_uri is None: # redirect_uri is optional and was not supplied. if len(registered_uris) == 1: # There is only one URI associated with client, so we use it. redirect_uri_final = registered_uris[0] else: # Several URIs are registered with the client, decision is ambiguous. LOGGER.error( 'Redirect URI is no supplied client with ID "%s". Request from IP "%s".' % (client.id, get_remote_ip(request))) return ep_auth_response_error_page( request, _('Redirect URI should be supplied for given client.')) # SPEC: The authorization server SHOULD NOT redirect the user-agent to unregistered or untrusted URIs # to prevent the authorization endpoint from being used as an open redirector. if redirect_uri_final not in registered_uris: LOGGER.error( 'An attempt to use an untrusted URI "%s" for client with ID "%s". Request from IP "%s".' % (redirect_uri_final, client.id, get_remote_ip(request))) return ep_auth_response_error_page( request, _('Redirection URI supplied is not associated with given client.' )) # Access token scope requested, scopes_to_apply = resolve_scopes_to_apply(input_params.get('scope'), client) request.session['oauth_client_id'] = client.id request.session['oauth_response_type'] = response_type request.session['oauth_redirect_uri'] = redirect_uri_final request.session['oauth_scopes_ids'] = [s.id for s in scopes_to_apply] request.session['oauth_state'] = input_params.get('state') dict_data = { 'client': client, 'scopes_obj': scopes_to_apply, 'oauthost_title': _('Authorization Request') } return render(request, OAUTHOST_TEMPLATE_AUTHORIZE, dict_data) # ======================================================================================== # User has made his choice using auth form. redirect_uri = request.session.get('oauth_redirect_uri') response_type = request.session.get('oauth_response_type') params_as_uri_fragment = (response_type == 'token') if request.POST.get('confirmed') is None: # User has declined authorization. ep_auth_clear_session_data(request) return ep_auth_response_error(redirect_uri, params_as_uri_fragment, 'access_denied', 'Authorization is canceled by user') # User confirmed authorization using a web-form. client = Client.objects.get(pk=request.session.get('oauth_client_id')) scopes_to_apply = Scope.objects.filter( id__in=request.session.get('oauth_scopes_ids')).all() output_params = {} auth_obj = None # Used for "Authorization code" Grant Type. if response_type == 'code': # Generating Authorization Code. auth_obj = AuthorizationCode(client=client, user=request.user, uri=redirect_uri) auth_obj.save() output_params['code'] = auth_obj.code # Used as "Implicit" Grant Type. if response_type == 'token': expires_in = client.token_lifetime expires_at = None if expires_in is not None: output_params['expires_in'] = expires_in expires_at = datetime.fromtimestamp(int(time() + expires_in)) # Generating Token. auth_obj = Token(client=client, user=request.user, expires_at=expires_at) auth_obj.save() output_params['access_token'] = auth_obj.access_token output_params['token_type'] = auth_obj.access_token_type if auth_obj is not None: # Link scopes to auth object. for scope in scopes_to_apply: auth_obj.scopes.add(scope) state = request.session.get('state') if state is not None: output_params['state'] = state ep_auth_clear_session_data(request) # SPEC: Developers should note that some HTTP client implementations do not # support the inclusion of a fragment component in the HTTP "Location" # response header field. Such client will require using other methods # for redirecting the client than a 3xx redirection response. if not client.hash_sign_supported: data_dict = { 'action_uri': ep_auth_build_redirect_uri(redirect_uri, output_params, params_as_uri_fragment) } return render(request, OAUTHOST_TEMPLATE_AUTHORIZE_PROCEED, data_dict) return ep_auth_response(redirect_uri, output_params, params_as_uri_fragment)
def test_grant_authorization_code(self): # Secure connection check settings.DEBUG = False resp = self.client.get(URL_TOKEN, {}) self.assertEqual(resp.status_code, 403) settings.DEBUG = True resp = self.client.post(URL_TOKEN, {'grant_type': 'a'}) self.assertEqual(resp.status_code, 400) self.assertEqual(resp.content_json['error'], 'unsupported_grant_type') user_1 = User(username='******') user_1.set_password('12345') user_1.save() client_1 = Client(user=user_1, title='OClient') client_1.save() redirect_1 = RedirectionEndpoint(client=client_1, uri='http://redirect-test.com') redirect_1.save() code_1 = AuthorizationCode(user=user_1, client=client_1, uri=redirect_1.uri) code_1.save() # Missing client authentication data. resp = self.client.post(URL_TOKEN, {'grant_type': 'authorization_code'}) self.assertEqual(resp.status_code, 401) self.assertEqual(resp.content_json['error'], 'invalid_client') # Missing all required params. resp = self.client.post(URL_TOKEN, {'grant_type': 'authorization_code', 'client_id': client_1.identifier, 'client_secret': client_1.password}) self.assertEqual(resp.status_code, 400) self.assertEqual(resp.content_json['error'], 'invalid_request') # Missing redirect URI. resp = self.client.post(URL_TOKEN, {'grant_type': 'authorization_code', 'code': 'wrong_code', 'client_id': client_1.identifier, 'client_secret': client_1.password}) self.assertEqual(resp.status_code, 400) self.assertEqual(resp.content_json['error'], 'invalid_request') # Missing code. resp = self.client.post(URL_TOKEN, {'grant_type': 'authorization_code', 'redirect_uri': 'http://wrong-url.com', 'client_id': client_1.identifier, 'client_secret': client_1.password}) self.assertEqual(resp.status_code, 400) self.assertEqual(resp.content_json['error'], 'invalid_request') # Wrong code. resp = self.client.post(URL_TOKEN, {'grant_type': 'authorization_code', 'code': 'invalid', 'redirect_uri': 'http://localhost:8000/abc/', 'client_id': client_1.identifier, 'client_secret': client_1.password}) self.assertEqual(resp.status_code, 400) self.assertEqual(resp.content_json['error'], 'invalid_grant') # Wrong URI. resp = self.client.post(URL_TOKEN, {'grant_type': 'authorization_code', 'code': code_1.code, 'redirect_uri': 'http://wrong-url.com/', 'client_id': client_1.identifier, 'client_secret': client_1.password}) self.assertEqual(resp.status_code, 400) self.assertEqual(resp.content_json['error'], 'invalid_grant') # Valid call for a token. resp = self.client.post(URL_TOKEN, {'grant_type': 'authorization_code', 'code': code_1.code, 'redirect_uri': redirect_1.uri, 'client_id': client_1.identifier, 'client_secret': client_1.password}) self.assertEqual(resp.status_code, 200) self.assertTrue('access_token' in resp.content_json) self.assertTrue('refresh_token' in resp.content_json) self.assertTrue('token_type' in resp.content_json) # An additional call for code issues token and code invalidation. resp = self.client.post(URL_TOKEN, {'grant_type': 'authorization_code', 'code': '1234567', 'redirect_uri': 'http://localhost:8000/abc/', 'client_id': client_1.identifier, 'client_secret': client_1.password}) self.assertEqual(resp.status_code, 400) self.assertEqual(resp.content_json['error'], 'invalid_grant')
def endpoint_authorize(request): """ SPEC: The authorization endpoint is used to interact with the resource owner and obtain an authorization grant. SPEC: The authorization server MUST support TLS... The authorization server MUST support the use of the HTTP "GET" method [RFC2616] for the authorization endpoint, and MAY support the use of the "POST" method as well. """ # SPEC: Since requests to the authorization endpoint result in user authentication # and the transmission of clear-text credentials (in the HTTP response), # the authorization server MUST require the use of a transport-layer # security mechanism when sending requests to the authorization endpoint. if not request.is_secure() and not settings.DEBUG: # Insecure connections are only available in debug mode. return ep_auth_response_error_page(request, _('OAuth requires secure connection to be established.'), 403) if request.POST.get('auth_decision') is None: # User has made no decision on auth confirmation yet. input_params = filter_input_params(request.REQUEST) response_type = input_params.get('response_type') client_id = input_params.get('client_id') redirect_uri = input_params.get('redirect_uri') redirect_uri_final = redirect_uri if client_id is None: # Fail fast without a DB hit. return ep_auth_response_error_page(request, _('Client ID must be supplied.')) if response_type not in REGISTRY_EP_AUTH_RESPONSE_TYPE: return ep_auth_response_error_page(request, _('Unknown response type requested. Expected: %s.') % ', '.join(REGISTRY_EP_AUTH_RESPONSE_TYPE)) try: client = Client.objects.get(identifier=client_id) except ObjectDoesNotExist: LOGGER.error('Invalid client ID supplied. Value "%s" was sent from IP "%s".' % (client_id, get_remote_ip(request))) return ep_auth_response_error_page(request, _('Invalid client ID is supplied.')) # TODO There should be at least one redirection URI associated with a client. URI validity should be checked while such an association is made. registered_uris = [url[0] for url in client.redirectionendpoint_set.values_list('uri')] # Check redirection URI validity. if redirect_uri is None: # redirect_uri is optional and was not supplied. if len(registered_uris) == 1: # There is only one URI associated with client, so we use it. redirect_uri_final = registered_uris[0] else: # Several URIs are registered with the client, decision is ambiguous. LOGGER.error('Redirect URI is no supplied client with ID "%s". Request from IP "%s".' % (client.id, get_remote_ip(request))) return ep_auth_response_error_page(request, _('Redirect URI should be supplied for given client.')) # SPEC: The authorization server SHOULD NOT redirect the user-agent to unregistered or untrusted URIs # to prevent the authorization endpoint from being used as an open redirector. if redirect_uri_final not in registered_uris: LOGGER.error('An attempt to use an untrusted URI "%s" for client with ID "%s". Request from IP "%s".' % (redirect_uri_final, client.id, get_remote_ip(request))) return ep_auth_response_error_page(request, _('Redirection URI supplied is not associated with given client.')) # Access token scope requested, scopes_to_apply = resolve_scopes_to_apply(input_params.get('scope'), client) request.session['oauth_client_id'] = client.id request.session['oauth_response_type'] = response_type request.session['oauth_redirect_uri'] = redirect_uri_final request.session['oauth_scopes_ids'] = [s.id for s in scopes_to_apply] request.session['oauth_state'] = input_params.get('state') dict_data = { 'client': client, 'scopes_obj': scopes_to_apply, 'oauthost_title': _('Authorization Request') } return render(request, OAUTHOST_TEMPLATE_AUTHORIZE, dict_data) # ======================================================================================== # User has made his choice using auth form. redirect_uri = request.session.get('oauth_redirect_uri') response_type = request.session.get('oauth_response_type') params_as_uri_fragment = (response_type == 'token') if request.POST.get('confirmed') is None: # User has declined authorization. ep_auth_clear_session_data(request) return ep_auth_response_error(redirect_uri, params_as_uri_fragment, 'access_denied', 'Authorization is canceled by user') # User confirmed authorization using a web-form. client = Client.objects.get(pk=request.session.get('oauth_client_id')) scopes_to_apply = Scope.objects.filter(id__in=request.session.get('oauth_scopes_ids')).all() output_params = {} auth_obj = None # Used for "Authorization code" Grant Type. if response_type == 'code': # Generating Authorization Code. auth_obj = AuthorizationCode(client=client, user=request.user, uri=redirect_uri) auth_obj.save() output_params['code'] = auth_obj.code # Used as "Implicit" Grant Type. if response_type == 'token': expires_in = client.token_lifetime expires_at = None if expires_in is not None: output_params['expires_in'] = expires_in expires_at = datetime.fromtimestamp(int(time() + expires_in)) # Generating Token. auth_obj = Token(client=client, user=request.user, expires_at=expires_at) auth_obj.save() output_params['access_token'] = auth_obj.access_token output_params['token_type'] = auth_obj.access_token_type if auth_obj is not None: # Link scopes to auth object. for scope in scopes_to_apply: auth_obj.scopes.add(scope) state = request.session.get('state') if state is not None: output_params['state'] = state ep_auth_clear_session_data(request) # SPEC: Developers should note that some HTTP client implementations do not # support the inclusion of a fragment component in the HTTP "Location" # response header field. Such client will require using other methods # for redirecting the client than a 3xx redirection response. if not client.hash_sign_supported: data_dict = {'action_uri': ep_auth_build_redirect_uri(redirect_uri, output_params, params_as_uri_fragment)} return render(request, OAUTHOST_TEMPLATE_AUTHORIZE_PROCEED, data_dict) return ep_auth_response(redirect_uri, output_params, params_as_uri_fragment)