forked from Github-Mirrors/canaille
Add groups claim and scope
This commit is contained in:
parent
294b86a698
commit
f1ac9e140a
14 changed files with 90 additions and 20 deletions
|
@ -18,7 +18,7 @@
|
|||
"https://mydomain.tld/oauth/register",
|
||||
"scopes_supported":
|
||||
["openid", "profile", "email", "address",
|
||||
"phone"],
|
||||
"phone", "groups"],
|
||||
"response_types_supported":
|
||||
["code", "token", "id_token", "code token",
|
||||
"code id_token", "token id_token"],
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
"https://mydomain.tld/oauth/register",
|
||||
"scopes_supported":
|
||||
["openid", "profile", "email", "address",
|
||||
"phone"],
|
||||
"phone", "groups"],
|
||||
"response_types_supported":
|
||||
["code", "token", "id_token", "code token",
|
||||
"code id_token", "token id_token"],
|
||||
|
|
|
@ -27,6 +27,7 @@ CLAIMS = {
|
|||
"email": ("at", _("Your email address.")),
|
||||
"address": ("envelope open outline", _("Your postal address.")),
|
||||
"phone": ("phone", _("Your phone number.")),
|
||||
"groups": ("users", _("Groups you are belonging to")),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -61,6 +61,8 @@ def generate_user_info(user, scope):
|
|||
fields += ["address"]
|
||||
if "phone" in scope:
|
||||
fields += ["phone_number", "phone_number_verified"]
|
||||
if "groups" in scope:
|
||||
fields += ["groups"]
|
||||
|
||||
data = {}
|
||||
for field in fields:
|
||||
|
@ -69,6 +71,9 @@ def generate_user_info(user, scope):
|
|||
data[field] = user.__getattr__(ldap_field_match)
|
||||
if isinstance(data[field], list):
|
||||
data[field] = data[field][0]
|
||||
if field == "groups":
|
||||
group_name_attr = current_app.config["LDAP"]["GROUP_NAME_ATTRIBUTE"]
|
||||
data[field] = [getattr(g, group_name_attr)[0] for g in user.groups]
|
||||
|
||||
return UserInfo(**data)
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ def create_app():
|
|||
server_metadata_url=get_well_known_url(
|
||||
app.config["OAUTH_AUTH_SERVER"], external=True
|
||||
),
|
||||
client_kwargs={"scope": "openid profile email"},
|
||||
client_kwargs={"scope": "openid profile email groups"},
|
||||
)
|
||||
|
||||
@app.route("/")
|
||||
|
|
|
@ -53,7 +53,10 @@
|
|||
<h2 class="ui header">{{ name }}</h2>
|
||||
<div>
|
||||
{% if user %}
|
||||
Welcome {{ user.name }}
|
||||
<p>Welcome {{ user.name }}</p>
|
||||
{% if user.groups %}
|
||||
<p>You're a member of the following groups: {{ user.groups }}</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
Welcome, please <a href="{{ url_for('login') }}">log-in</a>.
|
||||
{% endif %}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"http://localhost:5000/oauth/register",
|
||||
"scopes_supported":
|
||||
["openid", "profile", "email", "address",
|
||||
"phone"],
|
||||
"phone", "groups"],
|
||||
"response_types_supported":
|
||||
["code", "token", "id_token", "code token",
|
||||
"code id_token", "token id_token"],
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
"http://localhost:5000/oauth/register",
|
||||
"scopes_supported":
|
||||
["openid", "profile", "email", "address",
|
||||
"phone"],
|
||||
"phone", "groups"],
|
||||
"response_types_supported":
|
||||
["code", "token", "id_token", "code token",
|
||||
"code id_token", "token id_token"],
|
||||
|
|
|
@ -94,6 +94,7 @@ oauthGrantType: refresh_token
|
|||
oauthScope: openid
|
||||
oauthScope: profile
|
||||
oauthScope: email
|
||||
oauthScope: groups
|
||||
oauthResponseType: code
|
||||
oauthResponseType: id_token
|
||||
oauthTokenEndpointAuthMethod: client_secret_basic
|
||||
|
@ -111,6 +112,7 @@ oauthGrantType: refresh_token
|
|||
oauthScope: openid
|
||||
oauthScope: profile
|
||||
oauthScope: email
|
||||
oauthScope: groups
|
||||
oauthResponseType: code
|
||||
oauthResponseType: id_token
|
||||
oauthTokenEndpointAuthMethod: client_secret_basic
|
||||
|
|
|
@ -215,7 +215,7 @@ def client(app, slapd_connection):
|
|||
"refresh_token",
|
||||
],
|
||||
oauthResponseType=["code", "token", "id_token"],
|
||||
oauthScope=["openid", "profile"],
|
||||
oauthScope=["openid", "profile", "groups"],
|
||||
oauthTermsOfServiceURI="https://mydomain.tld/tos",
|
||||
oauthPolicyURI="https://mydomain.tld/policy",
|
||||
oauthJWKURI="https://mydomain.tld/jwk",
|
||||
|
@ -361,7 +361,10 @@ def foo_group(app, user, slapd_connection):
|
|||
g.save(slapd_connection)
|
||||
with app.app_context():
|
||||
user.load_groups(conn=slapd_connection)
|
||||
return g
|
||||
yield g
|
||||
user._groups = []
|
||||
g.delete(conn=slapd_connection)
|
||||
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -375,7 +378,9 @@ def bar_group(app, admin, slapd_connection):
|
|||
g.save(slapd_connection)
|
||||
with app.app_context():
|
||||
admin.load_groups(conn=slapd_connection)
|
||||
return g
|
||||
yield g
|
||||
admin._groups = []
|
||||
g.delete(conn=slapd_connection)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
@ -47,7 +47,7 @@ def test_authorization_code_flow(testclient, slapd_connection, logged_user, clie
|
|||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
status=200,
|
||||
)
|
||||
assert {"name": "John Doe", "family_name": "Doe", "sub": "user"} == res.json
|
||||
assert {"name": "John Doe", "family_name": "Doe", "sub": "user", "groups": []} == res.json
|
||||
|
||||
|
||||
def test_logout_login(testclient, slapd_connection, logged_user, client):
|
||||
|
@ -105,7 +105,7 @@ def test_logout_login(testclient, slapd_connection, logged_user, client):
|
|||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
status=200,
|
||||
)
|
||||
assert {"name": "John Doe", "family_name": "Doe", "sub": "user"} == res.json
|
||||
assert {"name": "John Doe", "family_name": "Doe", "sub": "user", "groups": []} == res.json
|
||||
|
||||
|
||||
def test_refresh_token(testclient, slapd_connection, logged_user, client):
|
||||
|
@ -164,7 +164,7 @@ def test_refresh_token(testclient, slapd_connection, logged_user, client):
|
|||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
status=200,
|
||||
)
|
||||
assert {"name": "John Doe", "family_name": "Doe", "sub": "user"} == res.json
|
||||
assert {"name": "John Doe", "family_name": "Doe", "sub": "user", "groups": []} == res.json
|
||||
|
||||
|
||||
def test_code_challenge(testclient, slapd_connection, logged_user, client):
|
||||
|
@ -218,7 +218,7 @@ def test_code_challenge(testclient, slapd_connection, logged_user, client):
|
|||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
status=200,
|
||||
)
|
||||
assert {"name": "John Doe", "family_name": "Doe", "sub": "user"} == res.json
|
||||
assert {"name": "John Doe", "family_name": "Doe", "sub": "user", "groups": []} == res.json
|
||||
|
||||
client.oauthTokenEndpointAuthMethod = "client_secret_basic"
|
||||
client.save(slapd_connection)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from authlib.jose import jwt
|
||||
from urllib.parse import urlsplit, parse_qs
|
||||
from canaille.models import AuthorizationCode, Token
|
||||
from canaille.models import AuthorizationCode, Token, User
|
||||
|
||||
|
||||
def test_oauth_hybrid(testclient, slapd_connection, user, client):
|
||||
User.attr_type_by_name(slapd_connection)
|
||||
res = testclient.get(
|
||||
"/oauth/authorize",
|
||||
params=dict(
|
||||
|
@ -41,7 +42,7 @@ def test_oauth_hybrid(testclient, slapd_connection, user, client):
|
|||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
status=200,
|
||||
)
|
||||
assert {"name": "John Doe", "family_name": "Doe", "sub": "user"} == res.json
|
||||
assert {"name": "John Doe", "family_name": "Doe", "sub": "user", "groups": []} == res.json
|
||||
|
||||
|
||||
def test_oidc_hybrid(testclient, slapd_connection, logged_user, client, keypair):
|
||||
|
@ -80,4 +81,4 @@ def test_oidc_hybrid(testclient, slapd_connection, logged_user, client, keypair)
|
|||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
status=200,
|
||||
)
|
||||
assert {"name": "John Doe", "family_name": "Doe", "sub": "user"} == res.json
|
||||
assert {"name": "John Doe", "family_name": "Doe", "sub": "user", "groups": []} == res.json
|
||||
|
|
|
@ -40,7 +40,7 @@ def test_oauth_implicit(testclient, slapd_connection, user, client):
|
|||
"/oauth/userinfo", headers={"Authorization": f"Bearer {access_token}"}
|
||||
)
|
||||
assert "application/json" == res.content_type
|
||||
assert {"name": "John Doe", "sub": "user", "family_name": "Doe"} == res.json
|
||||
assert {"name": "John Doe", "sub": "user", "family_name": "Doe", "groups": []} == res.json
|
||||
|
||||
client.oauthGrantType = ["code"]
|
||||
client.oauthTokenEndpointAuthMethod = "client_secret_basic"
|
||||
|
@ -92,7 +92,60 @@ def test_oidc_implicit(testclient, keypair, slapd_connection, user, client):
|
|||
status=200,
|
||||
)
|
||||
assert "application/json" == res.content_type
|
||||
assert {"name": "John Doe", "sub": "user", "family_name": "Doe"} == res.json
|
||||
assert {"name": "John Doe", "sub": "user", "family_name": "Doe", "groups": []} == res.json
|
||||
|
||||
client.oauthGrantType = ["code"]
|
||||
client.oauthTokenEndpointAuthMethod = "client_secret_basic"
|
||||
client.save(slapd_connection)
|
||||
|
||||
|
||||
def test_oidc_implicit_with_group(testclient, keypair, slapd_connection, user, client, foo_group):
|
||||
client.oauthGrantType = ["token id_token"]
|
||||
client.oauthTokenEndpointAuthMethod = "none"
|
||||
|
||||
client.save(slapd_connection)
|
||||
|
||||
res = testclient.get(
|
||||
"/oauth/authorize",
|
||||
params=dict(
|
||||
response_type="id_token token",
|
||||
client_id=client.oauthClientID,
|
||||
scope="openid profile groups",
|
||||
nonce="somenonce",
|
||||
),
|
||||
)
|
||||
assert "text/html" == res.content_type
|
||||
|
||||
res.form["login"] = "user"
|
||||
res.form["password"] = "correct horse battery staple"
|
||||
res = res.form.submit(status=302)
|
||||
|
||||
res = res.follow(status=200)
|
||||
assert "text/html" == res.content_type, res.json
|
||||
|
||||
res = res.form.submit(name="answer", value="accept", status=302)
|
||||
|
||||
assert res.location.startswith(client.oauthRedirectURIs[0])
|
||||
params = parse_qs(urlsplit(res.location).fragment)
|
||||
|
||||
access_token = params["access_token"][0]
|
||||
token = Token.get(access_token, conn=slapd_connection)
|
||||
assert token is not None
|
||||
|
||||
id_token = params["id_token"][0]
|
||||
claims = jwt.decode(id_token, keypair[1])
|
||||
assert user.uid[0] == claims["sub"]
|
||||
assert user.cn[0] == claims["name"]
|
||||
assert [client.oauthClientID] == claims["aud"]
|
||||
assert ["foo"] == claims["groups"]
|
||||
|
||||
res = testclient.get(
|
||||
"/oauth/userinfo",
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
status=200,
|
||||
)
|
||||
assert "application/json" == res.content_type
|
||||
assert {"name": "John Doe", "sub": "user", "family_name": "Doe", "groups": ["foo"]} == res.json
|
||||
|
||||
client.oauthGrantType = ["code"]
|
||||
client.oauthTokenEndpointAuthMethod = "client_secret_basic"
|
||||
|
|
|
@ -15,7 +15,7 @@ def test_password_flow(testclient, slapd_connection, user, client):
|
|||
status=200,
|
||||
)
|
||||
|
||||
assert res.json["scope"] == "openid profile"
|
||||
assert res.json["scope"] == "openid profile groups"
|
||||
assert res.json["token_type"] == "Bearer"
|
||||
access_token = res.json["access_token"]
|
||||
|
||||
|
@ -27,4 +27,4 @@ def test_password_flow(testclient, slapd_connection, user, client):
|
|||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
status=200,
|
||||
)
|
||||
assert {"name": "John Doe", "sub": "user", "family_name": "Doe"} == res.json
|
||||
assert {"name": "John Doe", "sub": "user", "family_name": "Doe", "groups": []} == res.json
|
||||
|
|
Loading…
Reference in a new issue