forked from Github-Mirrors/canaille
Token introspection
This commit is contained in:
parent
3ae32a8797
commit
8880c92226
5 changed files with 76 additions and 6 deletions
|
@ -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:
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue