add some tests for authorize page
- non-authenticated user, - non supported flow - authorization page displayed correctly - code flow without openid in scope - code flow with openid in scope
This commit is contained in:
parent
541cf80b77
commit
ba15837b01
|
@ -10,7 +10,7 @@ from app.oauth_models import Scope
|
|||
|
||||
|
||||
@oauth_bp.route("/token", methods=["POST"])
|
||||
def get_access_token():
|
||||
def token():
|
||||
"""
|
||||
Calls by client to exchange the access token given the authorization code.
|
||||
The client authentications using Basic Authentication.
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
import base64
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
from flask import url_for
|
||||
|
||||
from app.extensions import db
|
||||
from app.jose_utils import verify_id_token
|
||||
from app.models import Client, User
|
||||
from app.oauth.views.authorize import (
|
||||
get_host_name_and_scheme,
|
||||
generate_access_token,
|
||||
construct_url,
|
||||
)
|
||||
from tests.utils import login
|
||||
|
||||
|
||||
def test_get_host_name_and_scheme():
|
||||
|
@ -24,3 +33,261 @@ def test_generate_access_token(flask_client):
|
|||
def test_construct_url():
|
||||
url = construct_url("http://ab.cd", {"x": "1 2"})
|
||||
assert url == "http://ab.cd?x=1%202"
|
||||
|
||||
|
||||
def test_authorize_page_non_login_user(flask_client):
|
||||
"""make sure to display login page for non-authenticated user"""
|
||||
user = User.create("test@test.com", "test user")
|
||||
client = Client.create_new("test client", user.id)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
r = flask_client.get(
|
||||
url_for(
|
||||
"oauth.authorize",
|
||||
client_id=client.oauth_client_id,
|
||||
state="teststate",
|
||||
redirect_uri="http://localhost",
|
||||
response_type="code",
|
||||
)
|
||||
)
|
||||
|
||||
html = r.get_data(as_text=True)
|
||||
assert r.status_code == 200
|
||||
assert "In order to accept the request, you need to login or sign up" in html
|
||||
|
||||
|
||||
def test_authorize_page_login_user_non_supported_flow(flask_client):
|
||||
"""return 400 if the flow is not supported"""
|
||||
user = login(flask_client)
|
||||
client = Client.create_new("test client", user.id)
|
||||
db.session.commit()
|
||||
|
||||
# Not provide any flow
|
||||
r = flask_client.get(
|
||||
url_for(
|
||||
"oauth.authorize",
|
||||
client_id=client.oauth_client_id,
|
||||
state="teststate",
|
||||
redirect_uri="http://localhost",
|
||||
# not provide response_type param here
|
||||
)
|
||||
)
|
||||
|
||||
# Provide a not supported flow
|
||||
html = r.get_data(as_text=True)
|
||||
assert r.status_code == 400
|
||||
assert "SimpleLogin only support the following OIDC flows" in html
|
||||
|
||||
r = flask_client.get(
|
||||
url_for(
|
||||
"oauth.authorize",
|
||||
client_id=client.oauth_client_id,
|
||||
state="teststate",
|
||||
redirect_uri="http://localhost",
|
||||
# SL does not support this flow combination
|
||||
response_type="code token id_token",
|
||||
)
|
||||
)
|
||||
|
||||
html = r.get_data(as_text=True)
|
||||
assert r.status_code == 400
|
||||
assert "SimpleLogin only support the following OIDC flows" in html
|
||||
|
||||
|
||||
def test_authorize_page_login_user(flask_client):
|
||||
"""make sure to display authorization page for authenticated user"""
|
||||
user = login(flask_client)
|
||||
client = Client.create_new("test client", user.id)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
r = flask_client.get(
|
||||
url_for(
|
||||
"oauth.authorize",
|
||||
client_id=client.oauth_client_id,
|
||||
state="teststate",
|
||||
redirect_uri="http://localhost",
|
||||
response_type="code",
|
||||
)
|
||||
)
|
||||
|
||||
html = r.get_data(as_text=True)
|
||||
assert r.status_code == 200
|
||||
assert "You can customize the info sent to this app" in html
|
||||
assert "a@b.c (Personal Email)" in html
|
||||
|
||||
|
||||
def test_authorize_code_flow_no_openid_scope(flask_client):
|
||||
"""make sure the authorize redirects user to correct page for the *Code Flow*
|
||||
and when the *openid* scope is not present
|
||||
, ie when response_type=code, openid not in scope
|
||||
"""
|
||||
|
||||
user = login(flask_client)
|
||||
client = Client.create_new("test client", user.id)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# user allows client on the authorization page
|
||||
r = flask_client.post(
|
||||
url_for(
|
||||
"oauth.authorize",
|
||||
client_id=client.oauth_client_id,
|
||||
state="teststate",
|
||||
redirect_uri="http://localhost",
|
||||
response_type="code",
|
||||
),
|
||||
data={"button": "allow", "suggested-email": "x@y.z", "suggested-name": "AB CD"},
|
||||
# user will be redirected to client page, do not allow redirection here
|
||||
# to assert the redirect url
|
||||
# follow_redirects=True,
|
||||
)
|
||||
|
||||
assert r.status_code == 302 # user gets redirected back to client page
|
||||
|
||||
# r.location will have this form http://localhost?state=teststate&code=knuyjepwvg
|
||||
o = urlparse(r.location)
|
||||
assert o.netloc == "localhost"
|
||||
assert not o.fragment
|
||||
|
||||
# parse the query, should return something like
|
||||
# {'state': ['teststate'], 'code': ['knuyjepwvg']}
|
||||
queries = parse_qs(o.query)
|
||||
|
||||
assert queries["state"] == ["teststate"]
|
||||
assert len(queries["code"]) == 1
|
||||
|
||||
# Exchange the code to get access_token
|
||||
valid_credentials = base64.b64encode(
|
||||
f"{client.oauth_client_id}:{client.oauth_client_secret}".encode()
|
||||
).decode("utf-8")
|
||||
|
||||
r = flask_client.post(
|
||||
url_for("oauth.token"),
|
||||
headers={"Authorization": "Basic " + valid_credentials},
|
||||
data={"grant_type": "authorization_code", "code": queries["code"][0]},
|
||||
)
|
||||
|
||||
# r.json should have this format
|
||||
# {
|
||||
# 'access_token': 'avmhluhonsouhcwwailydwvhankspptgidoggcbu',
|
||||
# 'expires_in': 3600,
|
||||
# 'scope': '',
|
||||
# 'token_type': 'bearer',
|
||||
# 'user': {
|
||||
# 'avatar_url': None,
|
||||
# 'client': 'test client',
|
||||
# 'email': 'x@y.z',
|
||||
# 'email_verified': True,
|
||||
# 'id': 1,
|
||||
# 'name': 'AB CD'
|
||||
# }
|
||||
# }
|
||||
assert r.status_code == 200
|
||||
assert r.json["access_token"]
|
||||
assert r.json["expires_in"] == 3600
|
||||
assert r.json["scope"] == ""
|
||||
assert r.json["token_type"] == "bearer"
|
||||
|
||||
assert r.json["user"] == {
|
||||
"avatar_url": None,
|
||||
"client": "test client",
|
||||
"email": "x@y.z",
|
||||
"email_verified": True,
|
||||
"id": 1,
|
||||
"name": "AB CD",
|
||||
}
|
||||
|
||||
|
||||
def test_authorize_code_flow_with_openid_scope(flask_client):
|
||||
"""make sure the authorize redirects user to correct page for the *Code Flow*
|
||||
and when the *openid* scope is present
|
||||
, ie when response_type=code, openid in scope
|
||||
|
||||
The authorize endpoint should stay the same: return the *code*.
|
||||
The token endpoint however should now return id_token in addition to the access_token
|
||||
"""
|
||||
|
||||
user = login(flask_client)
|
||||
client = Client.create_new("test client", user.id)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# user allows client on the authorization page
|
||||
r = flask_client.post(
|
||||
url_for(
|
||||
"oauth.authorize",
|
||||
client_id=client.oauth_client_id,
|
||||
state="teststate",
|
||||
redirect_uri="http://localhost",
|
||||
response_type="code",
|
||||
scope="openid", # openid is in scope
|
||||
),
|
||||
data={"button": "allow", "suggested-email": "x@y.z", "suggested-name": "AB CD"},
|
||||
# user will be redirected to client page, do not allow redirection here
|
||||
# to assert the redirect url
|
||||
# follow_redirects=True,
|
||||
)
|
||||
|
||||
assert r.status_code == 302 # user gets redirected back to client page
|
||||
|
||||
# r.location will have this form http://localhost?state=teststate&code=knuyjepwvg
|
||||
o = urlparse(r.location)
|
||||
assert o.netloc == "localhost"
|
||||
assert not o.fragment
|
||||
|
||||
# parse the query, should return something like
|
||||
# {'state': ['teststate'], 'code': ['knuyjepwvg']}
|
||||
queries = parse_qs(o.query)
|
||||
|
||||
assert queries["state"] == ["teststate"]
|
||||
assert len(queries["code"]) == 1
|
||||
|
||||
# Exchange the code to get access_token
|
||||
valid_credentials = base64.b64encode(
|
||||
f"{client.oauth_client_id}:{client.oauth_client_secret}".encode()
|
||||
).decode("utf-8")
|
||||
|
||||
r = flask_client.post(
|
||||
url_for("oauth.token"),
|
||||
headers={"Authorization": "Basic " + valid_credentials},
|
||||
data={"grant_type": "authorization_code", "code": queries["code"][0]},
|
||||
)
|
||||
|
||||
# r.json should have this format
|
||||
# {
|
||||
# 'access_token': 'avmhluhonsouhcwwailydwvhankspptgidoggcbu',
|
||||
# 'expires_in': 3600,
|
||||
# 'scope': '',
|
||||
# 'token_type': 'bearer',
|
||||
# 'user': {
|
||||
# 'avatar_url': None,
|
||||
# 'client': 'test client',
|
||||
# 'email': 'x@y.z',
|
||||
# 'email_verified': True,
|
||||
# 'id': 1,
|
||||
# 'name': 'AB CD'
|
||||
# }
|
||||
# }
|
||||
print(r.json)
|
||||
assert r.status_code == 200
|
||||
assert r.json["access_token"]
|
||||
assert r.json["expires_in"] == 3600
|
||||
assert r.json["scope"] == ""
|
||||
assert r.json["token_type"] == "bearer"
|
||||
|
||||
assert r.json["user"] == {
|
||||
"avatar_url": None,
|
||||
"client": "test client",
|
||||
"email": "x@y.z",
|
||||
"email_verified": True,
|
||||
"id": 1,
|
||||
"name": "AB CD",
|
||||
}
|
||||
|
||||
# id_token must be returned
|
||||
assert r.json["id_token"]
|
||||
|
||||
# id_token must be a valid, correctly signed JWT
|
||||
assert verify_id_token(r.json["id_token"])
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
from flask import url_for
|
||||
|
||||
from app.extensions import db
|
||||
from app.models import User
|
||||
|
||||
|
||||
def login(flask_client) -> User:
|
||||
# create user, user is activated
|
||||
user = User.create(
|
||||
email="a@b.c", password="password", name="Test User", activated=True
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
r = flask_client.post(
|
||||
url_for("auth.login"),
|
||||
data={"email": "a@b.c", "password": "password"},
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert r.status_code == 200
|
||||
assert b"/auth/logout" in r.data
|
||||
|
||||
return user
|
Loading…
Reference in New Issue