Merge branch 'issue-92-jwt-access-token' into 'master'

AuthorizationCode and Token have a new id parameter

See merge request yaal/canaille!37
This commit is contained in:
Éloi Rivard 2022-02-17 13:45:00 +00:00
commit 5ac6dff790
17 changed files with 91 additions and 43 deletions

View file

@ -266,9 +266,9 @@ class LDAPObject:
self.changes = {}
@classmethod
def get(cls, dn=None, filter=None, conn=None):
def get(cls, dn=None, filter=None, conn=None, **kwargs):
try:
return cls.filter(dn, filter, conn)[0]
return cls.filter(dn, filter, conn, **kwargs)[0]
except (IndexError, ldap.NO_SUCH_OBJECT):
return None

View file

@ -77,8 +77,9 @@ class Client(LDAPObject, ClientMixin):
class AuthorizationCode(LDAPObject, AuthorizationCodeMixin):
object_class = ["oauthAuthorizationCode"]
base = "ou=authorizations,ou=oauth"
id = "oauthCode"
id = "oauthAuthorizationCodeID"
attribute_table = {
"authorization_code_id": "oauthAuthorizationCodeID",
"description": "description",
"code": "oauthCode",
"client": "oauthClient",
@ -116,8 +117,9 @@ class AuthorizationCode(LDAPObject, AuthorizationCodeMixin):
class Token(LDAPObject, TokenMixin):
object_class = ["oauthToken"]
base = "ou=tokens,ou=oauth"
id = "oauthAccessToken"
id = "oauthTokenID"
attribute_table = {
"token_id": "oauthTokenID",
"access_token": "oauthAccessToken",
"description": "description",
"client": "oauthClient",

View file

@ -107,6 +107,7 @@ def save_authorization_code(code, request):
nonce = request.data.get("nonce")
now = datetime.datetime.now()
code = AuthorizationCode(
authorization_code_id=gen_salt(48),
code=code,
subject=request.user,
client=request.client.dn,
@ -222,6 +223,7 @@ def query_client(client_id):
def save_token(token, request):
now = datetime.datetime.now()
t = Token(
token_id=gen_salt(48),
type=token["token_type"],
access_token=token["access_token"],
issue_date=now,
@ -237,7 +239,7 @@ def save_token(token, request):
class BearerTokenValidator(_BearerTokenValidator):
def authenticate_token(self, token_string):
return Token.get(token_string)
return Token.get(access_token=token_string)
def request_invalid(self, request):
return False

View file

@ -23,7 +23,7 @@ def index(user):
@bp.route("/<token_id>", methods=["GET", "POST"])
@permissions_needed("manage_oidc")
def view(user, token_id):
token = Token.get(token_id)
token = Token.get(token_id=token_id)
token_client = Client.get(token.client)
token_user = User.get(dn=token.subject)
token_audience = [Client.get(aud) for aud in token.audience]

View file

@ -22,7 +22,7 @@
</thead>
{% for authorization in authorizations %}
<tr>
<td><a href="{{ url_for('oidc.authorizations.view', authorization_id=authorization.code) }}">{{ authorization.code }}</a></td>
<td><a href="{{ url_for('oidc.authorizations.view', authorization_id=authorization.authorization_code_id) }}">{{ authorization.code }}</a></td>
<td><a href="{{ url_for('oidc.clients.edit', client_id=authorization.client_id) }}">{{ authorization.client_id }}</a></td>
<td>{{ authorization.subject }}</td>
<td>{{ authorization.issue_date }}</td>

View file

@ -23,7 +23,7 @@
{% for token, client, user in items %}
<tr>
<td>
<a href="{{ url_for('oidc.tokens.view', token_id=token.access_token) }}">
<a href="{{ url_for('oidc.tokens.view', token_id=token.token_id) }}">
{{ token.access_token }}
</a>
</td>

View file

@ -309,6 +309,24 @@ olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.36 NAME 'oauthPreconsent'
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.37 NAME 'oauthTokenID'
DESC 'OAuth 2.0 token identifier'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.38 NAME 'oauthAuthorizationCodeID'
DESC 'OAuth 2.0 authorization code identifier'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
olcObjectClasses: ( 1.3.6.1.4.1.56207.1.2.1 NAME 'oauthClient'
DESC 'OAuth 2.0 Authorization Code'
SUP top
@ -341,8 +359,9 @@ olcObjectClasses: ( 1.3.6.1.4.1.56207.1.2.2 NAME 'oauthAuthorizationCode'
DESC 'OAuth 2.0 Authorization Code'
SUP top
STRUCTURAL
MUST oauthCode
MUST oauthAuthorizationCodeID
MAY ( description $
oauthCode $
oauthClient $
oauthSubject $
oauthRedirectURI $
@ -359,11 +378,12 @@ olcObjectClasses: ( 1.3.6.1.4.1.56207.1.2.3 NAME 'oauthToken'
DESC 'OAuth 2.0 Token'
SUP top
STRUCTURAL
MUST oauthAccessToken
MUST oauthTokenID
MAY ( description $
oauthClient $
oauthSubject $
oauthTokenType $
oauthAccessToken $
oauthRefreshToken $
oauthScope $
oauthIssueDate $

View file

@ -306,6 +306,24 @@ attributetypes ( 1.3.6.1.4.1.56207.1.1.36 NAME 'oauthPreconsent'
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetypes ( 1.3.6.1.4.1.56207.1.1.37 NAME 'oauthTokenID'
DESC 'OAuth 2.0 token identifier'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetypes ( 1.3.6.1.4.1.56207.1.1.38 NAME 'oauthAuthorizationCodeID'
DESC 'OAuth 2.0 authorization code identifier'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
objectclass ( 1.3.6.1.4.1.56207.1.2.1 NAME 'oauthClient'
DESC 'OAuth 2.0 Authorization Code'
SUP top
@ -338,8 +356,9 @@ objectclass ( 1.3.6.1.4.1.56207.1.2.2 NAME 'oauthAuthorizationCode'
DESC 'OAuth 2.0 Authorization Code'
SUP top
STRUCTURAL
MUST oauthCode
MUST oauthAuthorizationCodeID
MAY ( description $
oauthCode $
oauthClient $
oauthSubject $
oauthRedirectURI $
@ -356,11 +375,12 @@ objectclass ( 1.3.6.1.4.1.56207.1.2.3 NAME 'oauthToken'
DESC 'OAuth 2.0 Token'
SUP top
STRUCTURAL
MUST oauthAccessToken
MUST oauthTokenID
MAY ( description $
oauthClient $
oauthSubject $
oauthTokenType $
oauthAccessToken $
oauthRefreshToken $
oauthScope $
oauthIssueDate $

View file

@ -79,6 +79,7 @@ def other_client(app, slapd_connection):
@pytest.fixture
def authorization(app, slapd_connection, user, client):
a = AuthorizationCode(
authorization_code_id=gen_salt(48),
code="my-code",
client=client.dn,
subject=user.dn,
@ -99,6 +100,7 @@ def authorization(app, slapd_connection, user, client):
@pytest.fixture
def token(slapd_connection, client, user):
t = Token(
token_id=gen_salt(48),
access_token=gen_salt(48),
audience=[client.dn],
client=client.dn,

View file

@ -30,7 +30,7 @@ def test_authorization_code_flow(
assert res.location.startswith(client.redirect_uris[0])
params = parse_qs(urlsplit(res.location).query)
code = params["code"][0]
authcode = AuthorizationCode.get(code, conn=slapd_connection)
authcode = AuthorizationCode.get(code=code, conn=slapd_connection)
assert authcode is not None
res = testclient.post(
@ -46,7 +46,7 @@ def test_authorization_code_flow(
)
access_token = res.json["access_token"]
token = Token.get(access_token, conn=slapd_connection)
token = Token.get(access_token=access_token, conn=slapd_connection)
assert token.client == client.dn
assert token.subject == logged_user.dn
@ -92,7 +92,7 @@ def test_authorization_code_flow_preconsented(
assert res.location.startswith(client.redirect_uris[0])
params = parse_qs(urlsplit(res.location).query)
code = params["code"][0]
authcode = AuthorizationCode.get(code, conn=slapd_connection)
authcode = AuthorizationCode.get(code=code, conn=slapd_connection)
assert authcode is not None
res = testclient.post(
@ -108,7 +108,7 @@ def test_authorization_code_flow_preconsented(
)
access_token = res.json["access_token"]
token = Token.get(access_token, conn=slapd_connection)
token = Token.get(access_token=access_token, conn=slapd_connection)
assert token.client == client.dn
assert token.subject == logged_user.dn
@ -164,7 +164,7 @@ def test_logout_login(testclient, slapd_connection, logged_user, client):
assert res.location.startswith(client.redirect_uris[0])
params = parse_qs(urlsplit(res.location).query)
code = params["code"][0]
authcode = AuthorizationCode.get(code, conn=slapd_connection)
authcode = AuthorizationCode.get(code=code, conn=slapd_connection)
assert authcode is not None
res = testclient.post(
@ -180,7 +180,7 @@ def test_logout_login(testclient, slapd_connection, logged_user, client):
)
access_token = res.json["access_token"]
token = Token.get(access_token, conn=slapd_connection)
token = Token.get(access_token=access_token, conn=slapd_connection)
assert token.client == client.dn
assert token.subject == logged_user.dn
@ -217,7 +217,7 @@ def test_refresh_token(testclient, slapd_connection, logged_user, client):
assert res.location.startswith(client.redirect_uris[0])
params = parse_qs(urlsplit(res.location).query)
code = params["code"][0]
authcode = AuthorizationCode.get(code, conn=slapd_connection)
authcode = AuthorizationCode.get(code=code, conn=slapd_connection)
assert authcode is not None
res = testclient.post(
@ -232,7 +232,7 @@ def test_refresh_token(testclient, slapd_connection, logged_user, client):
status=200,
)
access_token = res.json["access_token"]
old_token = Token.get(access_token, conn=slapd_connection)
old_token = Token.get(access_token=access_token, conn=slapd_connection)
assert old_token is not None
assert not old_token.revokation_date
@ -246,7 +246,7 @@ def test_refresh_token(testclient, slapd_connection, logged_user, client):
status=200,
)
access_token = res.json["access_token"]
new_token = Token.get(access_token, conn=slapd_connection)
new_token = Token.get(access_token=access_token, conn=slapd_connection)
assert new_token is not None
old_token.reload(slapd_connection)
assert old_token.revokation_date
@ -289,7 +289,7 @@ def test_code_challenge(testclient, slapd_connection, logged_user, client):
assert res.location.startswith(client.redirect_uris[0])
params = parse_qs(urlsplit(res.location).query)
code = params["code"][0]
authcode = AuthorizationCode.get(code, conn=slapd_connection)
authcode = AuthorizationCode.get(code=code, conn=slapd_connection)
assert authcode is not None
res = testclient.post(
@ -306,7 +306,7 @@ def test_code_challenge(testclient, slapd_connection, logged_user, client):
)
access_token = res.json["access_token"]
token = Token.get(access_token, conn=slapd_connection)
token = Token.get(access_token=access_token, conn=slapd_connection)
assert token.client == client.dn
assert token.subject == logged_user.dn
@ -350,7 +350,7 @@ def test_authorization_code_flow_when_consent_already_given(
assert res.location.startswith(client.redirect_uris[0])
params = parse_qs(urlsplit(res.location).query)
code = params["code"][0]
authcode = AuthorizationCode.get(code, conn=slapd_connection)
authcode = AuthorizationCode.get(code=code, conn=slapd_connection)
assert authcode is not None
consents = Consent.filter(
@ -410,7 +410,7 @@ def test_authorization_code_flow_when_consent_already_given_but_for_a_smaller_sc
assert res.location.startswith(client.redirect_uris[0])
params = parse_qs(urlsplit(res.location).query)
code = params["code"][0]
authcode = AuthorizationCode.get(code, conn=slapd_connection)
authcode = AuthorizationCode.get(code=code, conn=slapd_connection)
assert authcode is not None
consents = Consent.filter(
@ -448,7 +448,7 @@ def test_authorization_code_flow_when_consent_already_given_but_for_a_smaller_sc
assert res.location.startswith(client.redirect_uris[0])
params = parse_qs(urlsplit(res.location).query)
code = params["code"][0]
authcode = AuthorizationCode.get(code, conn=slapd_connection)
authcode = AuthorizationCode.get(code=code, conn=slapd_connection)
assert authcode is not None
consents = Consent.filter(

View file

@ -9,6 +9,7 @@ from werkzeug.security import gen_salt
def test_clean_command(testclient, slapd_connection, client, user):
AuthorizationCode.ldap_object_classes(slapd_connection)
code = AuthorizationCode(
authorization_code_id=gen_salt(48),
code="my-code",
client=client.dn,
subject=user.dn,
@ -26,6 +27,7 @@ def test_clean_command(testclient, slapd_connection, client, user):
Token.ldap_object_classes(slapd_connection)
token = Token(
token_id=gen_salt(48),
access_token="my-token",
client=client.dn,
subject=user.dn,
@ -37,13 +39,13 @@ def test_clean_command(testclient, slapd_connection, client, user):
)
token.save(slapd_connection)
assert AuthorizationCode.get("my-code", conn=slapd_connection)
assert Token.get("my-token", conn=slapd_connection)
assert AuthorizationCode.get(code="my-code", conn=slapd_connection)
assert Token.get(access_token="my-token", conn=slapd_connection)
assert code.is_expired()
assert token.is_expired()
runner = testclient.app.test_cli_runner()
runner.invoke(cli, ["clean"])
assert not AuthorizationCode.get("my-code", conn=slapd_connection)
assert not Token.get("my-token", conn=slapd_connection)
assert not AuthorizationCode.get(code="my-code", conn=slapd_connection)
assert not Token.get(access_token="my-token", conn=slapd_connection)

View file

@ -12,6 +12,6 @@ def test_authorizaton_list(testclient, authorization, logged_admin):
def test_authorizaton_view(testclient, authorization, logged_admin):
res = testclient.get("/admin/authorization/" + authorization.code)
res = testclient.get("/admin/authorization/" + authorization.authorization_code_id)
for attr in authorization.may + authorization.must:
assert attr in res.text

View file

@ -34,11 +34,11 @@ def test_oauth_hybrid(testclient, slapd_connection, user, client):
params = parse_qs(urlsplit(res.location).fragment)
code = params["code"][0]
authcode = AuthorizationCode.get(code, conn=slapd_connection)
authcode = AuthorizationCode.get(code=code, conn=slapd_connection)
assert authcode is not None
access_token = params["access_token"][0]
token = Token.get(access_token, conn=slapd_connection)
token = Token.get(access_token=access_token, conn=slapd_connection)
assert token is not None
res = testclient.get(
@ -74,11 +74,11 @@ def test_oidc_hybrid(
params = parse_qs(urlsplit(res.location).fragment)
code = params["code"][0]
authcode = AuthorizationCode.get(code, conn=slapd_connection)
authcode = AuthorizationCode.get(code=code, conn=slapd_connection)
assert authcode is not None
access_token = params["access_token"][0]
token = Token.get(access_token, conn=slapd_connection)
token = Token.get(access_token=access_token, conn=slapd_connection)
assert token is not None
id_token = params["id_token"][0]

View file

@ -35,7 +35,7 @@ def test_oauth_implicit(testclient, slapd_connection, user, client):
params = parse_qs(urlsplit(res.location).fragment)
access_token = params["access_token"][0]
token = Token.get(access_token, conn=slapd_connection)
token = Token.get(access_token=access_token, conn=slapd_connection)
assert token is not None
res = testclient.get(
@ -86,7 +86,7 @@ def test_oidc_implicit(
params = parse_qs(urlsplit(res.location).fragment)
access_token = params["access_token"][0]
token = Token.get(access_token, conn=slapd_connection)
token = Token.get(access_token=access_token, conn=slapd_connection)
assert token is not None
id_token = params["id_token"][0]
@ -145,7 +145,7 @@ def test_oidc_implicit_with_group(
params = parse_qs(urlsplit(res.location).fragment)
access_token = params["access_token"][0]
token = Token.get(access_token, conn=slapd_connection)
token = Token.get(access_token=access_token, conn=slapd_connection)
assert token is not None
id_token = params["id_token"][0]

View file

@ -20,7 +20,7 @@ def test_password_flow_basic(testclient, slapd_connection, user, client):
assert res.json["token_type"] == "Bearer"
access_token = res.json["access_token"]
token = Token.get(access_token, conn=slapd_connection)
token = Token.get(access_token=access_token, conn=slapd_connection)
assert token is not None
res = testclient.get(
@ -57,7 +57,7 @@ def test_password_flow_post(testclient, slapd_connection, user, client):
assert res.json["token_type"] == "Bearer"
access_token = res.json["access_token"]
token = Token.get(access_token, conn=slapd_connection)
token = Token.get(access_token=access_token, conn=slapd_connection)
assert token is not None
res = testclient.get(

View file

@ -12,5 +12,5 @@ def test_token_list(testclient, token, logged_admin):
def test_token_view(testclient, token, logged_admin):
res = testclient.get("/admin/token/" + token.access_token)
res = testclient.get("/admin/token/" + token.token_id)
assert token.access_token in res.text

View file

@ -59,7 +59,7 @@ def test_full_flow(
assert res.location.startswith(client.redirect_uris[0])
params = parse_qs(urlsplit(res.location).query)
code = params["code"][0]
authcode = AuthorizationCode.get(code, conn=slapd_connection)
authcode = AuthorizationCode.get(code=code, conn=slapd_connection)
assert authcode is not None
res = testclient.post(
@ -75,7 +75,7 @@ def test_full_flow(
)
access_token = res.json["access_token"]
token = Token.get(access_token, conn=slapd_connection)
token = Token.get(access_token=access_token, conn=slapd_connection)
assert token.client == client.dn
assert token.subject == logged_user.dn