Token introspection

This commit is contained in:
Éloi Rivard 2020-08-24 14:44:32 +02:00
parent 3ae32a8797
commit 8880c92226
5 changed files with 76 additions and 6 deletions

View file

@ -91,7 +91,7 @@ def app(slapd_server):
"JWT": { "JWT": {
"KEY": "secret-key", "KEY": "secret-key",
"ALG": "HS256", "ALG": "HS256",
"ISS": "http://mydomain.tld", "ISS": "https://mydomain.tld",
"EXP": 3600, "EXP": 3600,
"MAPPING": { "MAPPING": {
"SUB": "uid", "SUB": "uid",
@ -155,6 +155,22 @@ def user(app, slapd_connection):
return u return u
@pytest.fixture
def token(slapd_connection, client, user):
t = Token(
oauthAccessToken=gen_salt(48),
oauthClientID=client.oauthClientID,
oauthSubject=user.dn,
oauthTokenType=None,
oauthRefreshToken=gen_salt(48),
oauthScope="openid profile",
oauthIssueDate=datetime.datetime.now().strftime("%Y%m%d%H%M%SZ"),
oauthTokenLifetime=str(3600),
)
t.save(slapd_connection)
return t
@pytest.fixture @pytest.fixture
def logged_user(user, testclient): def logged_user(user, testclient):
with testclient.session_transaction() as sess: with testclient.session_transaction() as sess:

View file

@ -141,7 +141,6 @@ def test_refresh_token(testclient, slapd_connection, logged_user, client):
token = Token.get(access_token, conn=slapd_connection) token = Token.get(access_token, conn=slapd_connection)
assert token is not None assert token is not None
print("------------------------------------")
res = testclient.post( res = testclient.post(
"/oauth/token", "/oauth/token",
params=dict( params=dict(

View file

@ -165,6 +165,10 @@ class AuthorizationCode(LDAPObjectHelper, AuthorizationCodeMixin):
) )
return (auth_time - datetime.datetime(1970, 1, 1)).total_seconds() return (auth_time - datetime.datetime(1970, 1, 1)).total_seconds()
@property
def code_challenge(self):
return self.oauthCodeChallenge
class Token(LDAPObjectHelper, TokenMixin): class Token(LDAPObjectHelper, TokenMixin):
objectClass = ["oauthToken"] objectClass = ["oauthToken"]
@ -180,6 +184,10 @@ class Token(LDAPObjectHelper, TokenMixin):
def get_expires_in(self): def get_expires_in(self):
return int(self.oauthTokenLifetime) return int(self.oauthTokenLifetime)
def get_issued_at(self):
issue_date = datetime.datetime.strptime(self.oauthIssueDate, "%Y%m%d%H%M%SZ")
return (issue_date - datetime.datetime(1970, 1, 1)).total_seconds()
def get_expires_at(self): def get_expires_at(self):
issue_date = datetime.datetime.strptime(self.oauthIssueDate, "%Y%m%d%H%M%SZ") issue_date = datetime.datetime.strptime(self.oauthIssueDate, "%Y%m%d%H%M%SZ")
issue_timestamp = (issue_date - datetime.datetime(1970, 1, 1)).total_seconds() issue_timestamp = (issue_date - datetime.datetime(1970, 1, 1)).total_seconds()
@ -193,3 +201,7 @@ class Token(LDAPObjectHelper, TokenMixin):
+ datetime.timedelta(seconds=int(self.oauthTokenLifetime)) + datetime.timedelta(seconds=int(self.oauthTokenLifetime))
>= datetime.datetime.now() >= datetime.datetime.now()
) )
@property
def revoked(self):
return False

View file

@ -3,7 +3,7 @@ from flask import Blueprint, request, session, redirect
from flask import render_template, jsonify, flash from flask import render_template, jsonify, flash
from flask_babel import gettext from flask_babel import gettext
from .models import User, Client from .models import User, Client
from .oauth2utils import authorization from .oauth2utils import authorization, IntrospectionEndpoint
from .forms import LoginForm from .forms import LoginForm
from .flaskutils import current_user from .flaskutils import current_user
@ -56,3 +56,8 @@ def authorize():
@bp.route("/token", methods=["POST"]) @bp.route("/token", methods=["POST"])
def issue_token(): def issue_token():
return authorization.create_token_response() return authorization.create_token_response()
@bp.route("/introspect", methods=["POST"])
def introspect_token():
return authorization.create_endpoint_response(IntrospectionEndpoint.ENDPOINT_NAME)

View file

@ -8,6 +8,8 @@ from authlib.oauth2.rfc6749.grants import (
ClientCredentialsGrant, ClientCredentialsGrant,
) )
from authlib.oauth2.rfc6750 import BearerTokenValidator as _BearerTokenValidator from authlib.oauth2.rfc6750 import BearerTokenValidator as _BearerTokenValidator
from authlib.oauth2.rfc7636 import CodeChallenge
from authlib.oauth2.rfc7662 import IntrospectionEndpoint as _IntrospectionEndpoint
from authlib.oidc.core.grants import ( from authlib.oidc.core.grants import (
OpenIDCode as _OpenIDCode, OpenIDCode as _OpenIDCode,
OpenIDImplicitGrant as _OpenIDImplicitGrant, OpenIDImplicitGrant as _OpenIDImplicitGrant,
@ -81,6 +83,8 @@ def save_authorization_code(code, request):
oauthNonce=nonce, oauthNonce=nonce,
oauthAuthorizationDate=now.strftime("%Y%m%d%H%M%SZ"), oauthAuthorizationDate=now.strftime("%Y%m%d%H%M%SZ"),
oauthAuthorizationLifetime=str(84000), oauthAuthorizationLifetime=str(84000),
oauthCodeChallenge=request.data.get("code_challenge"),
oauthCodeChallengeMethod=request.data.get("code_challenge_method"),
) )
code.save() code.save()
return code.oauthCode return code.oauthCode
@ -178,9 +182,8 @@ def save_token(token, request):
oauthTokenLifetime=str(token["expires_in"]), oauthTokenLifetime=str(token["expires_in"]),
oauthScope=token["scope"], oauthScope=token["scope"],
oauthClientID=request.client.oauthClientID, oauthClientID=request.client.oauthClientID,
oauthRefreshToken=token.get("refresh_token"),
) )
if "refresh_token" in token:
t.oauthRefreshToken = token["refresh_token"]
t.save() t.save()
@ -195,6 +198,38 @@ class BearerTokenValidator(_BearerTokenValidator):
return False return False
class IntrospectionEndpoint(_IntrospectionEndpoint):
def query_token(self, token, token_type_hint, client):
if token_type_hint == "access_token":
tok = Token.filter(oauthAccessToken=token)
elif token_type_hint == "refresh_token":
tok = Token.filter(oauthRefreshToken=token)
else:
tok = Token.filter(oauthAccessToken=token)
if not tok:
tok = Token.filter(oauthRefreshToken=token)
if tok:
tok = tok[0]
if tok.oauthClientID == client.oauthClientID:
return tok
# if has_introspect_permission(client):
# return tok
def introspect_token(self, token):
return {
"active": True,
"client_id": token.oauthClientID,
"token_type": token.oauthTokenType,
"username": User.get(token.oauthSubject).name,
"scope": token.get_scope(),
"sub": token.oauthSubject,
"aud": token.oauthClientID,
"iss": current_app.config["JWT"]["ISS"],
"exp": token.get_expires_at(),
"iat": token.get_issued_at(),
}
authorization = AuthorizationServer() authorization = AuthorizationServer()
require_oauth = ResourceProtector() require_oauth = ResourceProtector()
@ -208,9 +243,12 @@ def config_oauth(app):
authorization.register_grant(ClientCredentialsGrant) authorization.register_grant(ClientCredentialsGrant)
authorization.register_grant( authorization.register_grant(
AuthorizationCodeGrant, [OpenIDCode(require_nonce=True)] AuthorizationCodeGrant,
[OpenIDCode(require_nonce=True), CodeChallenge(required=False)],
) )
authorization.register_grant(OpenIDImplicitGrant) authorization.register_grant(OpenIDImplicitGrant)
authorization.register_grant(OpenIDHybridGrant) authorization.register_grant(OpenIDHybridGrant)
require_oauth.register_token_validator(BearerTokenValidator()) require_oauth.register_token_validator(BearerTokenValidator())
authorization.register_endpoint(IntrospectionEndpoint)