class AuthorizationView(View): def __init__(self): validator = DjangoValidator() # TODO: this should probably be tunable through settings self._authorization_endpoint = Server(validator) self._error_uri = reverse('oauth2_error') def get(self, request, *args, **kwargs): uri, http_method, body, headers = extract_params(request) redirect_uri = request.GET.get('redirect_uri', None) log.debug('Found redirect uri %s.', redirect_uri) try: scopes, credentials = self._authorization_endpoint.validate_authorization_request( uri, http_method, body, headers) log.debug('Saving credentials to session, %r.', credentials) request.session['oauth2_credentials'] = credentials kwargs['scopes'] = scopes kwargs.update(credentials) actual_view = get_actual_authorization_view(request) log.debug('Invoking actual view method, %r.', actual_view) return actual_view(request, *args, **kwargs) except errors.FatalClientError as e: log.debug('Fatal client error, redirecting to error page.') return HttpResponseRedirect(e.in_uri(self._error_uri)) except errors.OAuth2Error as e: log.debug('Client error, redirecting back to client.') # TODO: remove after federico PR e.redirect_uri = redirect_uri or 'https://localhost' return HttpResponseRedirect(e.in_uri(e.redirect_uri)) @csrf_exempt def post(self, request, *args, **kwargs): uri, http_method, body, headers = extract_params(request) scopes, credentials = get_authorization(request) log.debug('Fetched credentials view, %r.', credentials) credentials.update(request.session.get('oauth2_credentials', {})) log.debug('Fetched credentials from session, %r.', credentials) redirect_uri = credentials.get('redirect_uri') log.debug('Found redirect uri %s.', redirect_uri) try: url, headers, body, status = self._authorization_endpoint.create_authorization_response( uri, http_method, body, headers, scopes, credentials) log.debug('Authorization successful, redirecting to client.') return HttpResponseRedirect(url) except errors.FatalClientError as e: log.debug('Fatal client error, redirecting to error page.') return HttpResponseRedirect(e.in_uri(self._error_uri)) except errors.OAuth2Error as e: log.debug('Client error, redirecting back to client.') return HttpResponseRedirect(e.in_uri(redirect_uri))
class AuthorizationView(View): def __init__(self): validator = DjangoValidator() # TODO: this should probably be tunable through settings self._authorization_endpoint = Server(validator) self._error_uri = reverse('oauth2_error') def get(self, request, *args, **kwargs): uri, http_method, body, headers = extract_params(request) redirect_uri = request.GET.get('redirect_uri', None) log.debug('Found redirect uri %s.', redirect_uri) try: scopes, credentials = self._authorization_endpoint.validate_authorization_request( uri, http_method, body, headers) log.debug('Saving credentials to session, %r.', credentials) request.session['oauth2_credentials'] = credentials kwargs['scopes'] = scopes kwargs.update(credentials) actual_view = get_actual_authorization_view(request) log.debug('Invoking actual view method, %r.', actual_view) return actual_view(request, *args, **kwargs) except errors.FatalClientError as e: log.debug('Fatal client error, redirecting to error page.') return HttpResponseRedirect(e.in_uri(self._error_uri)) except errors.OAuth2Error as e: log.debug('Client error, redirecting back to client.') # TODO: remove after federico PR e.redirect_uri = redirect_uri or 'https://localhost' return HttpResponseRedirect(e.in_uri(e.redirect_uri)) @csrf_exempt def post(self, request, *args, **kwargs): uri, http_method, body, headers = extract_params(request) scopes, credentials = get_authorization(request) log.debug('Fetched credentials view, %r.', credentials) credentials.update(request.session.get('oauth2_credentials', {})) log.debug('Fetched credentials from session, %r.', credentials) redirect_uri = credentials.get('redirect_uri') log.debug('Found redirect uri %s.', redirect_uri) try: url, headers, body, status = self._authorization_endpoint.create_authorization_response( uri, http_method, body, headers, scopes, credentials) log.debug('Authorization successful, redirecting to client.') return HttpResponseRedirect(url) except errors.FatalClientError as e: log.debug('Fatal client error, redirecting to error page.') return HttpResponseRedirect(e.in_uri(self._error_uri)) except errors.OAuth2Error as e: log.debug('Client error, redirecting back to client.') return HttpResponseRedirect(e.in_uri(redirect_uri))
class IamRequestValidator(IRequestValidator): def __init__(self, validator: OauthlibRequestValidator): self._server = Server(validator) def validate_pre_auth_request(self, request: ff.Message): http_request = request.headers.get('http_request') return self._server.validate_authorization_request( f'{http_request["headers"]["Host"]}{http_request["url"]}', http_request['method'], '', http_request['headers']) def validate_post_auth_request(self, request: ff.Message): pass def create_response(self, request: ff.Message): return self._server.create_authorization_response( request.headers.get('uri'), request.headers.get('http_method'), request.to_dict(), request.headers)
class TestScopeHandling(TestCase): DEFAULT_REDIRECT_URI = 'http://i.b./path' def set_scopes(self, scopes): def set_request_scopes(client_id, code, client, request): request.scopes = scopes return True return set_request_scopes def set_user(self, request): request.user = '******' request.client_id = 'bar' request.client = mock.MagicMock() request.client.client_id = 'mocked' return True def set_client(self, request): request.client = mock.MagicMock() request.client.client_id = 'mocked' return True def setUp(self): self.validator = mock.MagicMock(spec=RequestValidator) self.validator.get_default_redirect_uri.return_value = TestScopeHandling.DEFAULT_REDIRECT_URI self.validator.authenticate_client.side_effect = self.set_client self.server = Server(self.validator) self.web = WebApplicationServer(self.validator) self.mobile = MobileApplicationServer(self.validator) self.legacy = LegacyApplicationServer(self.validator) self.backend = BackendApplicationServer(self.validator) def test_scope_extraction(self): scopes = ( ('images', ['images']), ('images+videos', ['images', 'videos']), ('images+videos+openid', ['images', 'videos', 'openid']), ('http%3A%2f%2fa.b%2fvideos', ['http://a.b/videos']), ('http%3A%2f%2fa.b%2fvideos+pics', ['http://a.b/videos', 'pics']), ('pics+http%3A%2f%2fa.b%2fvideos', ['pics', 'http://a.b/videos']), ('http%3A%2f%2fa.b%2fvideos+https%3A%2f%2fc.d%2Fsecret', ['http://a.b/videos', 'https://c.d/secret']), ) uri = 'http://example.com/path?client_id=abc&scope=%s&response_type=%s' for scope, correct_scopes in scopes: scopes, _ = self.web.validate_authorization_request( uri % (scope, 'code')) self.assertItemsEqual(scopes, correct_scopes) scopes, _ = self.mobile.validate_authorization_request( uri % (scope, 'token')) self.assertItemsEqual(scopes, correct_scopes) scopes, _ = self.server.validate_authorization_request( uri % (scope, 'code')) self.assertItemsEqual(scopes, correct_scopes) def test_scope_preservation(self): scope = 'pics+http%3A%2f%2fa.b%2fvideos' decoded_scope = 'pics http://a.b/videos' auth_uri = 'http://example.com/path?client_id=abc&response_type=' token_uri = 'http://example.com/path' # authorization grant for backend_server_type in ['web', 'server']: h, _, s = getattr(self, backend_server_type).create_authorization_response( auth_uri + 'code', scopes=decoded_scope.split(' ')) self.validator.validate_code.side_effect = self.set_scopes(decoded_scope.split(' ')) self.assertEqual(s, 302) self.assertIn('Location', h) code = get_query_credentials(h['Location'])['code'][0] _, body, _ = getattr(self, backend_server_type).create_token_response(token_uri, body='grant_type=authorization_code&code=%s' % code) self.assertEqual(json.loads(body)['scope'], decoded_scope) # implicit grant for backend_server_type in ['mobile', 'server']: h, _, s = getattr(self, backend_server_type).create_authorization_response( auth_uri + 'token', scopes=decoded_scope.split(' ')) self.assertEqual(s, 302) self.assertIn('Location', h) self.assertEqual(get_fragment_credentials(h['Location'])['scope'][0], decoded_scope) # resource owner password credentials grant for backend_server_type in ['legacy', 'server']: body = 'grant_type=password&username=abc&password=secret&scope=%s' _, body, _ = getattr(self, backend_server_type).create_token_response(token_uri, body=body % scope) self.assertEqual(json.loads(body)['scope'], decoded_scope) # client credentials grant for backend_server_type in ['backend', 'server']: body = 'grant_type=client_credentials&scope=%s' self.validator.authenticate_client.side_effect = self.set_user _, body, _ = getattr(self, backend_server_type).create_token_response(token_uri, body=body % scope) self.assertEqual(json.loads(body)['scope'], decoded_scope) def test_scope_changed(self): scope = 'pics+http%3A%2f%2fa.b%2fvideos' scopes = ['images', 'http://a.b/videos'] decoded_scope = 'images http://a.b/videos' auth_uri = 'http://example.com/path?client_id=abc&response_type=' token_uri = 'http://example.com/path' # authorization grant h, _, s = self.web.create_authorization_response( auth_uri + 'code', scopes=scopes) self.assertEqual(s, 302) self.assertIn('Location', h) code = get_query_credentials(h['Location'])['code'][0] self.validator.validate_code.side_effect = self.set_scopes(scopes) _, body, _ = self.web.create_token_response(token_uri, body='grant_type=authorization_code&code=%s' % code) self.assertEqual(json.loads(body)['scope'], decoded_scope) # implicit grant self.validator.validate_scopes.side_effect = self.set_scopes(scopes) h, _, s = self.mobile.create_authorization_response( auth_uri + 'token', scopes=scopes) self.assertEqual(s, 302) self.assertIn('Location', h) self.assertEqual(get_fragment_credentials(h['Location'])['scope'][0], decoded_scope) # resource owner password credentials grant self.validator.validate_scopes.side_effect = self.set_scopes(scopes) body = 'grant_type=password&username=abc&password=secret&scope=%s' _, body, _ = self.legacy.create_token_response(token_uri, body=body % scope) self.assertEqual(json.loads(body)['scope'], decoded_scope) # client credentials grant self.validator.validate_scopes.side_effect = self.set_scopes(scopes) self.validator.authenticate_client.side_effect = self.set_user body = 'grant_type=client_credentials&scope=%s' _, body, _ = self.backend.create_token_response(token_uri, body=body % scope) self.assertEqual(json.loads(body)['scope'], decoded_scope) def test_invalid_scope(self): scope = 'pics+http%3A%2f%2fa.b%2fvideos' auth_uri = 'http://example.com/path?client_id=abc&response_type=' token_uri = 'http://example.com/path' self.validator.validate_scopes.return_value = False # authorization grant h, _, s = self.web.create_authorization_response( auth_uri + 'code', scopes=['invalid']) self.assertEqual(s, 302) self.assertIn('Location', h) error = get_query_credentials(h['Location'])['error'][0] self.assertEqual(error, 'invalid_scope') # implicit grant h, _, s = self.mobile.create_authorization_response( auth_uri + 'token', scopes=['invalid']) self.assertEqual(s, 302) self.assertIn('Location', h) error = get_fragment_credentials(h['Location'])['error'][0] self.assertEqual(error, 'invalid_scope') # resource owner password credentials grant body = 'grant_type=password&username=abc&password=secret&scope=%s' _, body, _ = self.legacy.create_token_response(token_uri, body=body % scope) self.assertEqual(json.loads(body)['error'], 'invalid_scope') # client credentials grant self.validator.authenticate_client.side_effect = self.set_user body = 'grant_type=client_credentials&scope=%s' _, body, _ = self.backend.create_token_response(token_uri, body=body % scope) self.assertEqual(json.loads(body)['error'], 'invalid_scope')
class AuthorizationEndpoint(object): def __init__(self, request): self.request = request self.validator = RequestValidator() self.server = Server(self.validator) @view_config(route_name='oauth2_authorization_endpoint', renderer='templates/application_authorization.pt', permission='add-authorized-app', request_method='GET') def get(self): uri, http_method, body, headers = extract_params(self.request) try: scopes, credentials = self.server.validate_authorization_request( uri, http_method, body, headers, ) app = self.validator.get_client(credentials['client_id']) try: auth_app = Session.query(AuthorizedApplication).filter( AuthorizedApplication.user == self.request.user, AuthorizedApplication.scope == scopes, AuthorizedApplication.redirect_uri == credentials['redirect_uri'], AuthorizedApplication.response_type == credentials['response_type'], AuthorizedApplication.application == app, ).one() except NoResultFound: auth_app = None if auth_app is not None: credentials['user'] = self.request.user server_response = self.server.create_authorization_response( uri, http_method, body, headers, scopes, credentials, ) return create_response(*server_response) else: authorship_information = app.user.email pretty_scopes = self.validator.get_pretty_scopes(scopes) return { 'response_type': credentials['response_type'], 'client_id': credentials['client_id'], 'redirect_uri': credentials['redirect_uri'], 'state': credentials['state'], 'scope': ' '.join(scopes), 'app': app, 'scopes': pretty_scopes, 'authorship_information': authorship_information, } except FatalClientError as e: return response_from_error(e) except OAuth2Error as e: return HTTPFound(e.in_uri(e.redirect_uri)) @view_config(route_name='oauth2_authorization_endpoint', permission='add-authorized-app', request_method='POST') def post(self): uri, http_method, body, headers = extract_params(self.request) redirect_uri = self.request.POST.get('redirect_uri') if 'submit' in self.request.POST: scope = self.request.POST.get('scope', '') scopes = scope.split() credentials = { 'client_id': self.request.POST.get('client_id'), 'redirect_uri': redirect_uri, 'response_type': self.request.POST.get('response_type'), 'state': self.request.POST.get('state'), 'user': self.request.user, } try: server_response = self.server.create_authorization_response( uri, http_method, body, headers, scopes, credentials, ) app = Session.query(Application).filter( Application.id == credentials['client_id'], ).one() try: auth_app = Session.query(AuthorizedApplication).filter( AuthorizedApplication.user == self.request.user, AuthorizedApplication.application == app, ).one() except NoResultFound: auth_app = AuthorizedApplication( user=self.request.user, application=app, ) auth_app.redirect_uri = credentials['redirect_uri'] auth_app.response_type = credentials['response_type'] auth_app.scope = scopes Session.add(auth_app) return create_response(*server_response) except FatalClientError as e: return response_from_error(e) elif 'cancel' in self.request.POST: e = AccessDeniedError() return HTTPFound(e.in_uri(redirect_uri))
class AuthorizationEndpoint(object): def __init__(self, request): self.request = request self.validator = RequestValidator() self.server = Server(self.validator) @view_config(route_name='oauth2_authorization_endpoint', renderer='templates/application_authorization.pt', permission='add-authorized-app', request_method='GET') def get(self): uri, http_method, body, headers = extract_params(self.request) try: scopes, credentials = self.server.validate_authorization_request( uri, http_method, body, headers, ) app = self.validator.get_client(credentials['client_id']) try: auth_app = Session.query(AuthorizedApplication).filter( AuthorizedApplication.user == self.request.user, AuthorizedApplication.scope == scopes, AuthorizedApplication.redirect_uri == credentials['redirect_uri'], AuthorizedApplication.response_type == credentials['response_type'], AuthorizedApplication.application == app, ).one() except NoResultFound: auth_app = None if auth_app is not None: credentials['user'] = self.request.user server_response = self.server.create_authorization_response( uri, http_method, body, headers, scopes, credentials, ) return create_response(*server_response) else: authorship_information = app.user.email pretty_scopes = self.validator.get_pretty_scopes(scopes) return { 'response_type': credentials['response_type'], 'client_id': credentials['client_id'], 'redirect_uri': credentials['redirect_uri'], 'state': credentials['state'], 'scope': ' '.join(scopes), 'app': app, 'scopes': pretty_scopes, 'authorship_information': authorship_information, } except FatalClientError as e: return response_from_error(e) except OAuth2Error as e: return HTTPFound(e.in_uri(e.redirect_uri)) @view_config(route_name='oauth2_authorization_endpoint', permission='add-authorized-app', request_method='POST') def post(self): uri, http_method, body, headers = extract_params(self.request) redirect_uri = self.request.POST.get('redirect_uri') if 'submit' in self.request.POST: scope = self.request.POST.get('scope', '') scopes = scope.split() credentials = { 'client_id': self.request.POST.get('client_id'), 'redirect_uri': redirect_uri, 'response_type': self.request.POST.get('response_type'), 'state': self.request.POST.get('state'), 'user': self.request.user, } try: server_response = self.server.create_authorization_response( uri, http_method, body, headers, scopes, credentials, ) app = Session.query(Application).filter( Application.id == credentials['client_id'], ).one() try: auth_app = Session.query(AuthorizedApplication).filter( AuthorizedApplication.user == self.request.user, AuthorizedApplication.application == app, ).one() except NoResultFound: auth_app = AuthorizedApplication( user=self.request.user, application=app, ) auth_app.redirect_uri = credentials['redirect_uri'] auth_app.response_type = credentials['response_type'] auth_app.scope = scopes Session.add(auth_app) return create_response(*server_response) except FatalClientError as e: return response_from_error(e) elif 'cancel' in self.request.POST: e = AccessDeniedError() return HTTPFound(e.in_uri(redirect_uri))
class TestScopeHandling(TestCase): DEFAULT_REDIRECT_URI = 'http://i.b./path' def set_scopes(self, scopes): def set_request_scopes(client_id, code, client, request): request.scopes = scopes return True return set_request_scopes def set_user(self, request): request.user = '******' request.client_id = 'bar' request.client = mock.MagicMock() request.client.client_id = 'mocked' return True def set_client(self, request): request.client = mock.MagicMock() request.client.client_id = 'mocked' return True def setUp(self): self.validator = mock.MagicMock(spec=RequestValidator) self.validator.get_default_redirect_uri.return_value = TestScopeHandling.DEFAULT_REDIRECT_URI self.validator.authenticate_client.side_effect = self.set_client self.server = Server(self.validator) self.web = WebApplicationServer(self.validator) self.mobile = MobileApplicationServer(self.validator) self.legacy = LegacyApplicationServer(self.validator) self.backend = BackendApplicationServer(self.validator) def test_scope_extraction(self): scopes = ( ('images', ['images']), ('images+videos', ['images', 'videos']), ('images+videos+openid', ['images', 'videos', 'openid']), ('http%3A%2f%2fa.b%2fvideos', ['http://a.b/videos']), ('http%3A%2f%2fa.b%2fvideos+pics', ['http://a.b/videos', 'pics']), ('pics+http%3A%2f%2fa.b%2fvideos', ['pics', 'http://a.b/videos']), ('http%3A%2f%2fa.b%2fvideos+https%3A%2f%2fc.d%2Fsecret', ['http://a.b/videos', 'https://c.d/secret']), ) uri = 'http://example.com/path?client_id=abc&scope=%s&response_type=%s' for scope, correct_scopes in scopes: scopes, _ = self.web.validate_authorization_request( uri % (scope, 'code')) self.assertItemsEqual(scopes, correct_scopes) scopes, _ = self.mobile.validate_authorization_request( uri % (scope, 'token')) self.assertItemsEqual(scopes, correct_scopes) scopes, _ = self.server.validate_authorization_request( uri % (scope, 'code')) self.assertItemsEqual(scopes, correct_scopes) def test_scope_preservation(self): scope = 'pics+http%3A%2f%2fa.b%2fvideos' decoded_scope = 'pics http://a.b/videos' auth_uri = 'http://example.com/path?client_id=abc&response_type=' token_uri = 'http://example.com/path' # authorization grant for backend_server_type in ['web', 'server']: h, _, s = getattr( self, backend_server_type).create_authorization_response( auth_uri + 'code', scopes=decoded_scope.split(' ')) self.validator.validate_code.side_effect = self.set_scopes( decoded_scope.split(' ')) self.assertEqual(s, 302) self.assertIn('Location', h) code = get_query_credentials(h['Location'])['code'][0] _, body, _ = getattr( self, backend_server_type ).create_token_response( token_uri, body= 'client_id=me&redirect_uri=http://back.to/me&grant_type=authorization_code&code=%s' % code) self.assertEqual(json.loads(body)['scope'], decoded_scope) # implicit grant for backend_server_type in ['mobile', 'server']: h, _, s = getattr( self, backend_server_type).create_authorization_response( auth_uri + 'token', scopes=decoded_scope.split(' ')) self.assertEqual(s, 302) self.assertIn('Location', h) self.assertEqual( get_fragment_credentials(h['Location'])['scope'][0], decoded_scope) # resource owner password credentials grant for backend_server_type in ['legacy', 'server']: body = 'grant_type=password&username=abc&password=secret&scope=%s' _, body, _ = getattr(self, backend_server_type).create_token_response( token_uri, body=body % scope) self.assertEqual(json.loads(body)['scope'], decoded_scope) # client credentials grant for backend_server_type in ['backend', 'server']: body = 'grant_type=client_credentials&scope=%s' self.validator.authenticate_client.side_effect = self.set_user _, body, _ = getattr(self, backend_server_type).create_token_response( token_uri, body=body % scope) self.assertEqual(json.loads(body)['scope'], decoded_scope) def test_scope_changed(self): scope = 'pics+http%3A%2f%2fa.b%2fvideos' scopes = ['images', 'http://a.b/videos'] decoded_scope = 'images http://a.b/videos' auth_uri = 'http://example.com/path?client_id=abc&response_type=' token_uri = 'http://example.com/path' # authorization grant h, _, s = self.web.create_authorization_response(auth_uri + 'code', scopes=scopes) self.assertEqual(s, 302) self.assertIn('Location', h) code = get_query_credentials(h['Location'])['code'][0] self.validator.validate_code.side_effect = self.set_scopes(scopes) _, body, _ = self.web.create_token_response( token_uri, body='grant_type=authorization_code&code=%s' % code) self.assertEqual(json.loads(body)['scope'], decoded_scope) # implicit grant self.validator.validate_scopes.side_effect = self.set_scopes(scopes) h, _, s = self.mobile.create_authorization_response(auth_uri + 'token', scopes=scopes) self.assertEqual(s, 302) self.assertIn('Location', h) self.assertEqual( get_fragment_credentials(h['Location'])['scope'][0], decoded_scope) # resource owner password credentials grant self.validator.validate_scopes.side_effect = self.set_scopes(scopes) body = 'grant_type=password&username=abc&password=secret&scope=%s' _, body, _ = self.legacy.create_token_response(token_uri, body=body % scope) self.assertEqual(json.loads(body)['scope'], decoded_scope) # client credentials grant self.validator.validate_scopes.side_effect = self.set_scopes(scopes) self.validator.authenticate_client.side_effect = self.set_user body = 'grant_type=client_credentials&scope=%s' _, body, _ = self.backend.create_token_response(token_uri, body=body % scope) self.assertEqual(json.loads(body)['scope'], decoded_scope) def test_invalid_scope(self): scope = 'pics+http%3A%2f%2fa.b%2fvideos' auth_uri = 'http://example.com/path?client_id=abc&response_type=' token_uri = 'http://example.com/path' self.validator.validate_scopes.return_value = False # authorization grant h, _, s = self.web.create_authorization_response(auth_uri + 'code', scopes=['invalid']) self.assertEqual(s, 302) self.assertIn('Location', h) error = get_query_credentials(h['Location'])['error'][0] self.assertEqual(error, 'invalid_scope') # implicit grant h, _, s = self.mobile.create_authorization_response(auth_uri + 'token', scopes=['invalid']) self.assertEqual(s, 302) self.assertIn('Location', h) error = get_fragment_credentials(h['Location'])['error'][0] self.assertEqual(error, 'invalid_scope') # resource owner password credentials grant body = 'grant_type=password&username=abc&password=secret&scope=%s' _, body, _ = self.legacy.create_token_response(token_uri, body=body % scope) self.assertEqual(json.loads(body)['error'], 'invalid_scope') # client credentials grant self.validator.authenticate_client.side_effect = self.set_user body = 'grant_type=client_credentials&scope=%s' _, body, _ = self.backend.create_token_response(token_uri, body=body % scope) self.assertEqual(json.loads(body)['error'], 'invalid_scope')