def test_protected_route_overruled_error(): """ Configuring a protected route that would be overruled by a route in FORCED_ANONYMOUS_ROUTES should lead to a ProtectedRouteConflict """ testsettings = TESTSETTINGS.copy() testsettings['PROTECTED'] = [('/foo/protected', ['*'], ['scope1'])] testsettings['FORCED_ANONYMOUS_ROUTES'] = ('/foo', ) with pytest.raises(config.ProtectedRouteConflictError): reload_settings(testsettings) authorization_middleware(None)
def test_protected_resource_syntax_error(): invalid_entries = [ ('foo', ), ('/foo', ), ('/foo', ['*']), ] for entry in invalid_entries: testsettings = TESTSETTINGS.copy() protected = [] protected.append(entry) testsettings['PROTECTED'] = protected with pytest.raises(config.ProtectedRecourceSyntaxError): reload_settings(testsettings) authorization_middleware(None)
def test_protected_resource_read_write_distinction(tokendata_scope1, tokendata_scope2): testsettings = TESTSETTINGS.copy() testsettings['PROTECTED'] = [ ('/read_write_distinction', ['GET', 'HEAD'], ['scope1']), ('/read_write_distinction', ['PATCH', 'PUT', 'POST', 'DELETE'], ['scope2']) ] reload_settings(testsettings) middleware = authorization_middleware(lambda r: ok_response) request = create_request(tokendata_scope1, '4', 'Bearer', '/read_write_distinction', 'GET') response = middleware(request) assert response.status_code == 200 request = create_request(tokendata_scope1, '4', 'Bearer', '/read_write_distinction', 'POST') response = middleware(request) assert response.status_code == 401 assert 'insufficient_scope' in response['WWW-Authenticate'] request = create_request(tokendata_scope2, '4', 'Bearer', '/read_write_distinction', 'POST') response = middleware(request) assert response.status_code == 200
def test_min_scope_multiple_sufficient(tokendata_two_scopes): """ Two scopes required, both of them in token """ testsettings = TESTSETTINGS.copy() testsettings['MIN_SCOPE'] = ("scope1", "scope2") reload_settings(testsettings) middleware = authorization_middleware(lambda r: ok_response) request = create_request(tokendata_two_scopes, "4") response = middleware(request) assert response.status_code == 200
def test_min_scope_as_string_insufficient(tokendata_scope1): """ MIN_SCOPE configured as string instead of tuple """ testsettings = TESTSETTINGS.copy() testsettings['MIN_SCOPE'] = "scope1" reload_settings(testsettings) middleware = authorization_middleware(lambda r: ok_response) request = create_request_no_auth_header() response = middleware(request) assert response.status_code == 401
def test_min_scope_sufficient(tokendata_scope1): """ scope1 is required, scope1 is in token """ testsettings = TESTSETTINGS.copy() testsettings['MIN_SCOPE'] = ("scope1", ) reload_settings(testsettings) middleware = authorization_middleware(lambda r: ok_response) request = create_request(tokendata_scope1, "4") response = middleware(request) assert response.status_code == 200
def test_options_works_while_min_scope(): testsettings = TESTSETTINGS.copy() testsettings['MIN_SCOPE'] = ("scope", ) reload_settings(testsettings) middleware = authorization_middleware(lambda r: ok_response) empty_request = types.SimpleNamespace(META={}, path='/', method='OPTIONS') response = middleware(empty_request) assert response.status_code == 200 with pytest.raises(Exception): response.is_authorized_for("scope1")
def test_min_scope_multiple_insufficient(tokendata_scope1): """ Two scopes required, only one of them in token """ testsettings = TESTSETTINGS.copy() testsettings['MIN_SCOPE'] = ("scope1", "scope2") reload_settings(testsettings) middleware = authorization_middleware(lambda r: ok_response) request = create_request(tokendata_scope1, "4") response = middleware(request) assert response.status_code == 401 assert 'insufficient_scope' in response['WWW-Authenticate']
def test_min_scope_insufficient(): """ scope1 is required, request with no token """ testsettings = TESTSETTINGS.copy() testsettings['MIN_SCOPE'] = ("scope1", ) reload_settings(testsettings) middleware = authorization_middleware(lambda r: ok_response) request = create_request_no_auth_header() response = middleware(request) assert response.status_code == 401 assert 'insufficient_scope' in response['WWW-Authenticate']
def test_jwks_from_url(requests_mock, tokendata_two_scopes): """ Verify that loading keyset from url works, by checking that is_authorized_for method correctly evaluates that user has the scopes mentioned in the token data """ jwks_url = "https://get.your.jwks.here/protocol/openid-connect/certs" requests_mock.get(jwks_url, text=json.dumps(JWKS1)) reload_settings({'JWKS': None, 'JWKS_URL': jwks_url}) middleware = authorization_middleware(lambda r: ok_response) request = create_request(tokendata_two_scopes, "4") middleware(request) assert request.is_authorized_for("scope1", "scope2")
def test_forced_anonymous_routes(): testsettings = TESTSETTINGS.copy() testsettings['FORCED_ANONYMOUS_ROUTES'] = ('/status', ) testsettings['MIN_SCOPE'] = ("scope1", ) reload_settings(testsettings) empty_request = types.SimpleNamespace(META={}, path='/status/lala', method='GET') middleware = authorization_middleware(lambda r: ok_response) response = middleware(empty_request) assert response.status_code == 200 with pytest.raises(Exception): response.is_authorized_for("scope1")
def test_unknown_kid(tokendata_two_scopes): """ Verify that a token signed with an unknown key results in an "invalid_token" response """ # Create a request with a token signed with a key from JWKS2 reload_settings({ 'JWKS': json.dumps(JWKS2), }) request = create_request(tokendata_two_scopes, "6") # Instantiate the middleware with JWKS1 reload_settings({ 'JWKS': json.dumps(JWKS1), }) middleware = authorization_middleware(lambda r: ok_response) response = middleware(request) assert response.status_code == 401 assert 'WWW-Authenticate' in response assert 'invalid_token' in response['WWW-Authenticate']
def test_reload_jwks_from_url(requests_mock, tokendata_two_scopes): """ It is possible that the IdP rotates the keys. In that case the new keyset needs to be fetched from the JWKS url to be able to verify signed tokens. """ jwks_url = "https://get.your.jwks.here/protocol/openid-connect/certs" # Create a request with a token signed with a key from JWKS2 requests_mock.get(jwks_url, text=json.dumps(JWKS2)) reload_settings({'JWKS': None, 'JWKS_URL': jwks_url}) assert requests_mock.call_count == 1 request = create_request(tokendata_two_scopes, "6") # Instantiate the middleware with JWKS1 requests_mock.get(jwks_url, text=json.dumps(JWKS1)) reload_settings({ 'JWKS': None, 'JWKS_URL': jwks_url, 'MIN_INTERVAL_KEYSET_UPDATE': 0 # Set update interval to 0 secs for the test }) assert requests_mock.call_count == 2 middleware = authorization_middleware(lambda r: ok_response) """ Process a request with the middleware. The middleware should now: - refetch the keyset from jwks_url - receive and load JWKS1 - still not recognize the kid - respond with an invalid_token response """ response = middleware(request) assert requests_mock.call_count == 3 assert response.status_code == 401 assert 'WWW-Authenticate' in response assert 'invalid_token' in response['WWW-Authenticate'] """ Mock requests so jwks_url returns JWKS2 and do the same request again. The middleware should now: - refetch the keyset from jwks_url again - receive and load JWKS2 - successfully verify the signature of the token """ requests_mock.get(jwks_url, text=json.dumps(JWKS2)) middleware(request) assert requests_mock.call_count == 4 assert request.is_authorized_for("scope1", "scope2")
def test_protected_resources_all_methods(tokendata_scope1, tokendata_two_scopes): testsettings = TESTSETTINGS.copy() testsettings['PROTECTED'] = [ ('/one_scope_required', ['*'], ['scope1']), ('/two_scopes_required', ['*'], ['scope1', 'scope2']), ] reload_settings(testsettings) middleware = authorization_middleware(lambda r: ok_response) # a token with scope1 gives access via all methods # to the one_scope_required route for method in ('GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'): request = create_request(tokendata_scope1, '4', 'Bearer', '/one_scope_required', method) response = middleware(request) assert request.is_authorized_for('scope1') assert response.status_code == 200 # a token with only scope1 does not give access to two_scopes_required route request = create_request(tokendata_scope1, '4', 'Bearer', '/two_scopes_required', 'GET') response = middleware(request) assert response.status_code == 401 assert 'insufficient_scope' in response['WWW-Authenticate'] # a token with scope1 and scope2 gives access to two_scopes_required route request = create_request(tokendata_two_scopes, '4', 'Bearer', '/two_scopes_required', 'GET') response = middleware(request) assert response.status_code == 200 # OPTIONS method should be allowed without auth header, even with methods: * request = create_request_no_auth_header('one_scope_required', 'OPTIONS') response = middleware(request) assert response.status_code == 200
def middleware(): reload_settings(TESTSETTINGS) return authorization_middleware(lambda r: ok_response)
def test_empty_scopes_error(): testsettings = TESTSETTINGS.copy() testsettings['PROTECTED'] = [('/foo/protected', ['*'], [])] with pytest.raises(config.NoRequiredScopesError): reload_settings(testsettings) authorization_middleware(None)
def test_bad_jwks(): with pytest.raises(config.AuthzConfigurationError): reload_settings({'JWKS': 'iamnotajwks'}) authorization_middleware(None)
def test_unknown_config_param(): testsettings = TESTSETTINGS.copy() testsettings['lalaland'] = 'oscar' with pytest.raises(config.AuthzConfigurationError): reload_settings(testsettings) authorization_middleware(None)
def test_missing_conf(): with pytest.raises(config.AuthzConfigurationError): authorization_middleware(None)