def test_response_mode_query(self): self.prepare_data() rv = self.client.post('/oauth/authorize', data={ 'client_id': 'hybrid-client', 'response_type': 'code id_token token', 'response_mode': 'query', 'state': 'bar', 'nonce': 'abc', 'scope': 'openid profile', 'redirect_uri': 'https://a.b', 'user_id': '1', }) self.assertIn('code=', rv.location) self.assertIn('id_token=', rv.location) self.assertIn('access_token=', rv.location) params = dict(url_decode(urlparse.urlparse(rv.location).query)) self.assertEqual(params['state'], 'bar')
def code_from_authorize_response(response): """ Get the code out from a response from ``/oauth2/authorize``. Args: response (pytest_flask.plugin.JSONResponse): response to get code from Return: str: the code """ location = response.headers['Location'] try: return dict(url_decode(urlparse.urlparse(location).query))['code'] except KeyError: raise ValueError( 'response did not contain a code; got headers:\n{}' .format(response.headers) )
def test_public_authorize_token(self): self.app.config.update({'OAUTH2_REFRESH_TOKEN_GENERATOR': True}) self.prepare_data(False, token_endpoint_auth_method='none') rv = self.client.post(self.authorize_url, data={'user_id': '1'}) self.assertIn('code=', rv.location) params = dict(url_decode(urlparse.urlparse(rv.location).query)) code = params['code'] rv = self.client.post('/oauth/token', data={ 'grant_type': 'authorization_code', 'code': code, 'client_id': 'code-client', }) resp = json.loads(rv.data) self.assertIn('access_token', resp) self.assertNotIn('refresh_token', resp)
async def _refresh_token(self, url, refresh_token=None, body='', headers=None, auth=USE_CLIENT_DEFAULT, **kwargs): resp = await self.post( url, data=dict(url_decode(body)), headers=headers, auth=auth, **kwargs) for hook in self.compliance_hook['refresh_token_response']: resp = hook(resp) resp.raise_for_status() token = self.parse_response_token(resp.json()) if 'refresh_token' not in token: self.token['refresh_token'] = refresh_token if self.update_token: await self.update_token(self.token, refresh_token=refresh_token) return self.token
def test_plain_code_challenge_failed(self): self.prepare_data() url = self.authorize_url + '&code_challenge=foo' rv = self.client.post(url, data={'user_id': '1'}) self.assertIn('code=', rv.headers['location']) params = dict( url_decode(urlparse.urlparse(rv.headers['location']).query)) code = params['code'] rv = self.client.post('/oauth/token', data={ 'grant_type': 'authorization_code', 'code': code, 'code_verifier': generate_token(48), 'client_id': 'code-client', }) resp = rv.json() self.assertIn('failed', resp['error_description'])
def test_plain_code_challenge_success(self): self.prepare_data() code_verifier = generate_token(48) url = self.authorize_url + '&code_challenge=' + code_verifier rv = self.client.post(url, data={'user_id': '1'}) self.assertIn('code=', rv.location) params = dict(url_decode(urlparse.urlparse(rv.location).query)) code = params['code'] rv = self.client.post('/oauth/token', data={ 'grant_type': 'authorization_code', 'code': code, 'code_verifier': code_verifier, 'client_id': 'code-client', }) resp = json.loads(rv.data) self.assertIn('access_token', resp)
def test_trusted_client_missing_code_verifier(self): self.prepare_data('client_secret_basic') url = self.authorize_url + '&code_challenge=foo' rv = self.client.post(url, data={'user_id': '1'}) self.assertIn('code=', rv.headers['location']) params = dict( url_decode(urlparse.urlparse(rv.headers['location']).query)) code = params['code'] headers = self.create_basic_header('code-client', 'code-secret') rv = self.client.post('/oauth/token', data={ 'grant_type': 'authorization_code', 'code': code, }, headers=headers) resp = rv.json() self.assertIn('Missing', resp['error_description'])
async def _fetch_token(self, url, body='', headers=None, auth=USE_CLIENT_DEFAULT, method='POST', **kwargs): if method.upper() == 'POST': resp = await self.post( url, data=dict(url_decode(body)), headers=headers, auth=auth, **kwargs) else: if '?' in url: url = '&'.join([url, body]) else: url = '?'.join([url, body]) resp = await self.get(url, headers=headers, auth=auth, **kwargs) for hook in self.compliance_hook['access_token_response']: resp = hook(resp) resp.raise_for_status() return self.parse_response_token(resp.json())
def test_trusted_client_without_code_challenge(self): self.prepare_data('client_secret_basic') rv = self.client.get(self.authorize_url) self.assertEqual(rv.data, b'ok') rv = self.client.post(self.authorize_url, data={'user_id': '1'}) self.assertIn('code=', rv.location) params = dict(url_decode(urlparse.urlparse(rv.location).query)) code = params['code'] headers = self.create_basic_header('code-client', 'code-secret') rv = self.client.post('/oauth/token', data={ 'grant_type': 'authorization_code', 'code': code, }, headers=headers) resp = json.loads(rv.data) self.assertIn('access_token', resp)
def parse_response_token(self, status_code, text): if status_code >= 400: message = "Token request failed with code {}, response was '{}'." self.handle_error('fetch_token_denied', message) try: text = text.strip() if text.startswith('{'): token = json.loads(text) else: token = dict(url_decode(text)) except (TypeError, ValueError) as e: error = ("Unable to decode token from token response. " "This is commonly caused by an unsuccessful request where" " a non urlencoded error message is returned. " "The decoding error was %s" "" % e) raise ValueError(error) return token
def test_not_implemented_code_challenge_method(self): self.prepare_data() url = self.authorize_url + '&code_challenge=foo' url += '&code_challenge_method=S128' rv = self.client.post(url, data={'user_id': '1'}) self.assertIn('code=', rv.location) params = dict(url_decode(urlparse.urlparse(rv.location).query)) code = params['code'] self.assertRaises(RuntimeError, self.client.post, '/oauth/token', data={ 'grant_type': 'authorization_code', 'code': code, 'code_verifier': 'foo', 'client_id': 'code-client', })
def test_authorize_id_token(self): self.prepare_data() rv = self.client.post( "/oauth/authorize", data={ "response_type": "id_token", "client_id": "implicit-client", "scope": "openid profile", "state": "bar", "nonce": "abc", "redirect_uri": "https://a.b/c", "user_id": "1", }, ) self.assertIn("id_token=", rv.headers["location"]) self.assertIn("state=bar", rv.headers["location"]) params = dict( url_decode(urlparse.urlparse(rv.headers["location"]).fragment)) self.validate_claims(params["id_token"], params)
def test_token_generator(self): m = 'tests.flask.test_oauth2.oauth2_server:token_generator' self.app.config.update({'OAUTH2_ACCESS_TOKEN_GENERATOR': m}) self.prepare_data(False, token_endpoint_auth_method='none') rv = self.client.post(self.authorize_url, data={'user_id': '1'}) self.assertIn('code=', rv.location) params = dict(url_decode(urlparse.urlparse(rv.location).query)) code = params['code'] rv = self.client.post('/oauth/token', data={ 'grant_type': 'authorization_code', 'code': code, 'client_id': 'code-client', }) resp = json.loads(rv.data) self.assertIn('access_token', resp) self.assertIn('c-authorization_code.1.', resp['access_token'])
def test_s256_code_challenge_success_client_secret_post(self, client, oauth): code_verifier = generate_token(48) code_challenge = create_s256_code_challenge(code_verifier) client.login() response = client.post(url_for( 'oauth.authorize', response_type='code', client_id=oauth.client_id, code_challenge=code_challenge, code_challenge_method='S256' ), { 'scope': 'default', 'accept': '', }) assert 'code=' in response.location params = dict(url_decode(urlparse.urlparse(response.location).query)) code = params['code'] response = client.post(url_for('oauth.token'), { 'grant_type': 'authorization_code', 'code': code, 'code_verifier': code_verifier, 'client_id': oauth.client_id, 'client_secret': oauth.secret }) assert200(response) assert response.content_type == 'application/json' assert 'access_token' in response.json token = response.json['access_token'] response = client.post(url_for('api.fake'), headers={ 'Authorization': ' '.join(['Bearer', token]) }) assert200(response) assert response.content_type == 'application/json' assert response.json == {'success': True}
def get_token_response(self): server = self.create_server() data = {'response_type': 'code', 'client_id': 'client'} request = self.factory.post('/authorize', data=data) grant_user = User.objects.get(username='******') resp = server.create_authorization_response(request, grant_user=grant_user) self.assertEqual(resp.status_code, 302) params = dict(url_decode(urlparse.urlparse(resp['Location']).query)) code = params['code'] request = self.factory.post( '/oauth/token', data={'grant_type': 'authorization_code', 'code': code}, HTTP_AUTHORIZATION=self.create_basic_auth('client', 'secret'), ) resp = server.create_token_response(request) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) return data
async def _refresh_token(self, url, refresh_token=None, body='', headers=None, auth=None, **kwargs): async with self.session.post(url, data=dict(url_decode(body)), headers=headers, auth=auth, **kwargs) as resp: token = await self._parse_token(resp, 'refresh_token_response') if 'refresh_token' not in token: self.token['refresh_token'] = refresh_token if callable(self.token_updater): await self.token_updater(self.token) return self.token
def test_authorize_token_no_refresh_token(self): self.app.config.update({"OAUTH2_REFRESH_TOKEN_GENERATOR": True}) self.prepare_data(False, token_endpoint_auth_method="none") rv = self.client.post(self.authorize_url, data={"user_id": "1"}) self.assertIn("code=", rv.headers["location"]) params = dict( url_decode(urlparse.urlparse(rv.headers["location"]).query)) code = params["code"] rv = self.client.post( "/oauth/token", data={ "grant_type": "authorization_code", "code": code, "client_id": "code-client", }, ) resp = rv.json() self.assertIn("access_token", resp) self.assertNotIn("refresh_token", resp)
def __init__(self, method, uri, body=None, headers=None): InsecureTransportError.check(uri) self.method = method self.uri = uri self.body = body self.headers = headers or {} self.query = urlparse.urlparse(uri).query self.query_params = url_decode(self.query) self.body_params = extract_params(body) or [] params = {} if self.query_params: params.update(dict(self.query_params)) if self.body_params: params.update(dict(self.body_params)) self.data = params self.grant_user = None self.credential = None self.client = None
def test_client_secret_post(self): self.app.config.update({'OAUTH2_REFRESH_TOKEN_GENERATOR': True}) self.prepare_data(token_endpoint_auth_method='client_secret_post') url = self.authorize_url + '&state=bar' rv = self.client.post(url, data={'user_id': '1'}) self.assertIn('code=', rv.location) params = dict(url_decode(urlparse.urlparse(rv.location).query)) self.assertEqual(params['state'], 'bar') code = params['code'] rv = self.client.post('/oauth/token', data={ 'grant_type': 'authorization_code', 'client_id': 'code-client', 'client_secret': 'code-secret', 'code': code, }) resp = json.loads(rv.data) self.assertIn('access_token', resp) self.assertIn('refresh_token', resp)
def test_invalid_redirect_uri(self): self.prepare_data() uri = self.authorize_url + '&redirect_uri=https%3A%2F%2Fa.c' rv = self.client.post(uri, data={'user_id': '1'}) self.assertIn('error=invalid_request', rv.location) uri = self.authorize_url + '&redirect_uri=https%3A%2F%2Fa.b' rv = self.client.post(uri, data={'user_id': '1'}) self.assertIn('code=', rv.location) params = dict(url_decode(urlparse.urlparse(rv.location).query)) code = params['code'] headers = self.create_basic_header('code-client', 'code-secret') rv = self.client.post('/oauth/token', data={ 'grant_type': 'authorization_code', 'code': code, }, headers=headers) resp = json.loads(rv.data) self.assertEqual(resp['error'], 'invalid_request')
def test_code_id_token(self): self.prepare_data() rv = self.client.post( "/oauth/authorize", data={ "client_id": "hybrid-client", "response_type": "code id_token", "state": "bar", "nonce": "abc", "scope": "openid profile", "redirect_uri": "https://a.b", "user_id": "1", }, ) self.assertIn("code=", rv.headers["location"]) self.assertIn("id_token=", rv.headers["location"]) self.assertNotIn("access_token=", rv.headers["location"]) params = dict( url_decode(urlparse.urlparse(rv.headers["location"]).fragment)) self.assertEqual(params["state"], "bar") params["nonce"] = "abc" params["client_id"] = "hybrid-client" self.validate_claims(params["id_token"], params) code = params["code"] headers = self.create_basic_header("hybrid-client", "hybrid-secret") rv = self.client.post( "/oauth/token", data={ "grant_type": "authorization_code", "redirect_uri": "https://a.b", "code": code, }, headers=headers, ) resp = rv.json() self.assertIn("access_token", resp) self.assertIn("id_token", resp)
def test_authorize_token(self): # generate refresh token self.prepare_data() rv = self.client.post('/oauth/authorize', data={ 'response_type': 'code', 'client_id': 'code-client', 'state': 'bar', 'scope': 'openid profile', 'redirect_uri': 'https://a.b', 'user_id': '1' }) self.assertIn('code=', rv.headers['location']) params = dict( url_decode(urlparse.urlparse(rv.headers['location']).query)) self.assertEqual(params['state'], 'bar') code = params['code'] headers = self.create_basic_header('code-client', 'code-secret') rv = self.client.post('/oauth/token', data={ 'grant_type': 'authorization_code', 'redirect_uri': 'https://a.b', 'code': code, }, headers=headers) resp = rv.json() self.assertIn('access_token', resp) self.assertIn('id_token', resp) jwt = JsonWebToken() claims = jwt.decode(resp['id_token'], self.get_validate_key(), claims_cls=CodeIDToken, claims_options={'iss': { 'value': 'Authlib' }}) claims.validate()
def test_authorize_token(self): # generate refresh token self.app.config.update({'OAUTH2_REFRESH_TOKEN_GENERATOR': True}) self.prepare_data() url = self.authorize_url + '&state=bar' rv = self.client.post(url, data={'user_id': '1'}) self.assertIn('code=', rv.location) params = dict(url_decode(urlparse.urlparse(rv.location).query)) self.assertEqual(params['state'], 'bar') code = params['code'] headers = self.create_basic_header('code-client', 'code-secret') rv = self.client.post('/oauth/token', data={ 'grant_type': 'authorization_code', 'code': code, }, headers=headers) resp = json.loads(rv.data) self.assertIn('access_token', resp) self.assertIn('refresh_token', resp)
def test_token_generator(self): m = "tests.fastapi.test_oauth2.oauth2_server:token_generator" self.app.config.update({"OAUTH2_ACCESS_TOKEN_GENERATOR": m}) self.prepare_data(False, token_endpoint_auth_method="none") rv = self.client.post(self.authorize_url, data={"user_id": "1"}) self.assertIn("code=", rv.headers["location"]) params = dict( url_decode(urlparse.urlparse(rv.headers["location"]).query)) code = params["code"] rv = self.client.post( "/oauth/token", data={ "grant_type": "authorization_code", "code": code, "client_id": "code-client", }, ) resp = rv.json() self.assertIn("access_token", resp) self.assertIn("c-authorization_code.1.", resp["access_token"])
def test_login(self): """Test the login redirect""" self.registry.oidc.fedora.client_id = "test-client-id" with mock.patch('requests.sessions.Session.send', side_effect=fake_send()): resp = self.app.get('/oidc/login', status=302) location = urlparse(resp.location) assert location.scheme == "https" assert location.netloc == "id.stg.fedoraproject.org" assert location.path == "/openidc/Authorization" query_data = dict(url_decode(location.query)) assert query_data["response_type"] == "code" assert query_data["client_id"] == "test-client-id" assert query_data["redirect_uri"] == "http://localhost/oidc/authorize" assert set(query_data["scope"].split(" ")) == set([ 'openid', 'email', 'profile', 'https://id.fedoraproject.org/scope/groups', 'https://id.fedoraproject.org/scope/agreements', ])
def test_response_mode_query(self): self.prepare_data() rv = self.client.post( "/oauth/authorize", data={ "client_id": "hybrid-client", "response_type": "code id_token token", "response_mode": "query", "state": "bar", "nonce": "abc", "scope": "openid profile", "redirect_uri": "https://a.b", "user_id": "1", }, ) self.assertIn("code=", rv.headers["location"]) self.assertIn("id_token=", rv.headers["location"]) self.assertIn("access_token=", rv.headers["location"]) params = dict( url_decode(urlparse.urlparse(rv.headers["location"]).query)) self.assertEqual(params["state"], "bar")
def test_s256_code_challenge_success(self): self.prepare_data() code_verifier = generate_token(48) code_challenge = create_s256_code_challenge(code_verifier) url = self.authorize_url + '&code_challenge=' + code_challenge url += '&code_challenge_method=S256' rv = self.client.post(url, data={'user_id': '1'}) self.assertIn('code=', rv.headers['location']) params = dict( url_decode(urlparse.urlparse(rv.headers['location']).query)) code = params['code'] rv = self.client.post('/oauth/token', data={ 'grant_type': 'authorization_code', 'code': code, 'code_verifier': code_verifier, 'client_id': 'code-client', }) resp = rv.json() self.assertIn('access_token', resp)
def test_oauth2_authorize_code_challenge(self): app = Flask(__name__) app.secret_key = '!' oauth = OAuth(app) client = oauth.register( 'dev', client_id='dev', api_base_url='https://i.b/api', access_token_url='https://i.b/token', authorize_url='https://i.b/authorize', client_kwargs={'code_challenge_method': 'S256'}, ) with app.test_request_context(): resp = client.authorize_redirect('https://b.com/bar') self.assertEqual(resp.status_code, 302) url = resp.headers.get('Location') self.assertIn('code_challenge=', url) self.assertIn('code_challenge_method=S256', url) state = dict(url_decode(urlparse.urlparse(url).query))['state'] self.assertIsNotNone(state) data = session[f'_state_dev_{state}'] verifier = data['data']['code_verifier'] self.assertIsNotNone(verifier) def fake_send(sess, req, **kwargs): self.assertIn(f'code_verifier={verifier}', req.body) return mock_send_value(get_bearer_token()) path = '/?code=a&state={}'.format(state) with app.test_request_context(path=path): # session is cleared in tests session[f'_state_dev_{state}'] = data with mock.patch('requests.sessions.Session.send', fake_send): token = client.authorize_access_token() self.assertEqual(token['access_token'], 'a')
def test_public_authorize_token(self): self.app.config.update({'OAUTH2_REFRESH_TOKEN_GENERATOR': True}) self.prepare_data(False) client = Client.query.filter_by(client_id='code-client').first() client.client_secret = '' db.session.add(client) db.session.commit() rv = self.client.post(self.authorize_url, data={'user_id': '1'}) self.assertIn('code=', rv.location) params = dict(url_decode(urlparse.urlparse(rv.location).query)) code = params['code'] rv = self.client.post('/oauth/token', data={ 'grant_type': 'authorization_code', 'code': code, 'client_id': 'code-client', }) resp = json.loads(rv.data) self.assertIn('access_token', resp) self.assertNotIn('refresh_token', resp)
def _fetch_token(self, url, **kwargs): resp = self.post(url, **kwargs) if resp.status_code >= 400: error = "Token request failed with code {}, response was '{}'." message = error.format(resp.status_code, resp.text) raise FetchTokenDeniedError(description=message) try: text = resp.text.strip() if text.startswith('{'): token = json.loads(text) else: token = dict(url_decode(text)) except (TypeError, ValueError) as e: error = ("Unable to decode token from token response. " "This is commonly caused by an unsuccessful request where" " a non urlencoded error message is returned. " "The decoding error was %s" "" % e) raise ValueError(error) self.token = token return token