2020-09-17 10:01:21 +00:00
|
|
|
import datetime
|
2021-12-20 22:57:27 +00:00
|
|
|
|
2020-09-25 09:26:41 +00:00
|
|
|
from authlib.integrations.flask_oauth2 import current_token
|
2020-08-28 14:07:39 +00:00
|
|
|
from authlib.jose import jwk
|
2020-08-17 15:49:49 +00:00
|
|
|
from authlib.oauth2 import OAuth2Error
|
2021-12-20 22:57:27 +00:00
|
|
|
from flask import abort
|
|
|
|
from flask import Blueprint
|
|
|
|
from flask import current_app
|
|
|
|
from flask import flash
|
|
|
|
from flask import jsonify
|
|
|
|
from flask import redirect
|
|
|
|
from flask import request
|
|
|
|
from flask import session
|
|
|
|
from flask_babel import gettext
|
|
|
|
from flask_babel import lazy_gettext as _
|
2021-10-28 13:24:34 +00:00
|
|
|
from flask_themer import render_template
|
2021-12-20 22:57:27 +00:00
|
|
|
|
2022-01-11 18:49:06 +00:00
|
|
|
from ..flaskutils import current_user
|
|
|
|
from ..forms import FullLoginForm
|
|
|
|
from ..models import User
|
2021-12-20 22:57:27 +00:00
|
|
|
from .models import Client
|
|
|
|
from .models import Consent
|
|
|
|
from .oauth2utils import authorization
|
|
|
|
from .oauth2utils import DEFAULT_JWT_ALG
|
|
|
|
from .oauth2utils import DEFAULT_JWT_KTY
|
|
|
|
from .oauth2utils import generate_user_info
|
|
|
|
from .oauth2utils import IntrospectionEndpoint
|
|
|
|
from .oauth2utils import require_oauth
|
|
|
|
from .oauth2utils import RevocationEndpoint
|
2020-08-17 15:49:49 +00:00
|
|
|
|
|
|
|
|
2022-01-11 18:49:06 +00:00
|
|
|
bp = Blueprint("oauth", __name__, url_prefix="/oauth")
|
2020-08-17 15:49:49 +00:00
|
|
|
|
2020-10-29 14:28:19 +00:00
|
|
|
CLAIMS = {
|
|
|
|
"profile": (
|
|
|
|
"id card outline",
|
|
|
|
_("Personnal information about yourself, such as your name or your gender."),
|
|
|
|
),
|
|
|
|
"email": ("at", _("Your email address.")),
|
|
|
|
"address": ("envelope open outline", _("Your postal address.")),
|
|
|
|
"phone": ("phone", _("Your phone number.")),
|
2021-06-03 15:24:36 +00:00
|
|
|
"groups": ("users", _("Groups you are belonging to")),
|
2020-10-29 14:28:19 +00:00
|
|
|
}
|
|
|
|
|
2020-08-17 15:49:49 +00:00
|
|
|
|
|
|
|
@bp.route("/authorize", methods=["GET", "POST"])
|
|
|
|
def authorize():
|
2021-09-28 10:06:41 +00:00
|
|
|
current_app.logger.debug(
|
|
|
|
"authorization endpoint request:\nGET: %s\nPOST: %s",
|
|
|
|
request.args.to_dict(flat=False),
|
|
|
|
request.form.to_dict(flat=False),
|
|
|
|
)
|
|
|
|
|
2020-10-26 18:09:38 +00:00
|
|
|
if "client_id" not in request.args:
|
|
|
|
abort(400)
|
|
|
|
|
|
|
|
client = Client.get(request.args["client_id"])
|
2020-10-26 18:15:53 +00:00
|
|
|
if not client:
|
|
|
|
abort(400)
|
|
|
|
|
|
|
|
user = current_user()
|
2020-09-17 08:00:39 +00:00
|
|
|
scopes = request.args.get("scope", "").split(" ")
|
|
|
|
|
|
|
|
# LOGIN
|
2020-08-17 15:49:49 +00:00
|
|
|
|
|
|
|
if not user:
|
2020-09-17 08:00:39 +00:00
|
|
|
if request.args.get("prompt") == "none":
|
|
|
|
return jsonify({"error": "login_required"})
|
|
|
|
|
2021-01-23 21:30:43 +00:00
|
|
|
form = FullLoginForm(request.form or None)
|
2020-08-17 15:49:49 +00:00
|
|
|
if request.method == "GET":
|
2020-08-20 09:32:33 +00:00
|
|
|
return render_template("login.html", form=form, menu=False)
|
2020-08-17 15:49:49 +00:00
|
|
|
|
2020-08-21 08:23:39 +00:00
|
|
|
if not form.validate() or not User.authenticate(
|
|
|
|
form.login.data, form.password.data, True
|
|
|
|
):
|
2020-08-17 15:49:49 +00:00
|
|
|
flash(gettext("Login failed, please check your information"), "error")
|
2020-08-20 09:32:33 +00:00
|
|
|
return render_template("login.html", form=form, menu=False)
|
2020-08-17 15:49:49 +00:00
|
|
|
|
|
|
|
return redirect(request.url)
|
|
|
|
|
2021-12-06 23:07:32 +00:00
|
|
|
if not user.can_use_oidc:
|
|
|
|
abort(400)
|
|
|
|
|
2020-09-17 08:00:39 +00:00
|
|
|
# CONSENT
|
|
|
|
|
2021-10-03 11:46:52 +00:00
|
|
|
consents = Consent.filter(
|
2022-01-11 16:57:58 +00:00
|
|
|
client=client.dn,
|
|
|
|
subject=user.dn,
|
2021-10-03 11:46:52 +00:00
|
|
|
)
|
2022-01-11 16:57:58 +00:00
|
|
|
consents = [c for c in consents if not c.revokation_date]
|
2020-09-17 08:00:39 +00:00
|
|
|
consent = consents[0] if consents else None
|
|
|
|
|
2020-08-17 15:49:49 +00:00
|
|
|
if request.method == "GET":
|
2021-10-20 10:05:08 +00:00
|
|
|
if client.preconsent or (
|
2022-01-11 16:57:58 +00:00
|
|
|
consent and all(scope in set(consent.scope) for scope in scopes)
|
2021-10-20 10:05:08 +00:00
|
|
|
):
|
2020-09-17 08:00:39 +00:00
|
|
|
return authorization.create_authorization_response(grant_user=user.dn)
|
|
|
|
|
|
|
|
elif request.args.get("prompt") == "none":
|
2021-09-28 11:45:47 +00:00
|
|
|
response = {"error": "consent_required"}
|
|
|
|
current_app.logger.debug("authorization endpoint response: %s", response)
|
|
|
|
return jsonify(response)
|
2020-09-17 08:00:39 +00:00
|
|
|
|
2020-08-17 15:49:49 +00:00
|
|
|
try:
|
2022-04-10 14:00:51 +00:00
|
|
|
grant = authorization.get_consent_grant(end_user=user)
|
2020-08-17 15:49:49 +00:00
|
|
|
except OAuth2Error as error:
|
2021-09-28 11:45:47 +00:00
|
|
|
response = dict(error.get_body())
|
|
|
|
current_app.logger.debug("authorization endpoint response: %s", response)
|
|
|
|
return jsonify(response)
|
2020-08-17 15:49:49 +00:00
|
|
|
|
2020-08-20 09:32:33 +00:00
|
|
|
return render_template(
|
2022-01-11 18:49:06 +00:00
|
|
|
"oidc/user/authorize.html",
|
2020-10-29 14:28:19 +00:00
|
|
|
user=user,
|
|
|
|
grant=grant,
|
|
|
|
client=client,
|
|
|
|
claims=CLAIMS,
|
|
|
|
menu=False,
|
2020-11-09 18:01:41 +00:00
|
|
|
ignored_claims=["openid"],
|
2020-08-20 09:32:33 +00:00
|
|
|
)
|
2020-08-17 15:49:49 +00:00
|
|
|
|
2020-10-28 16:57:27 +00:00
|
|
|
if request.method == "POST":
|
|
|
|
if request.form["answer"] == "logout":
|
|
|
|
del session["user_dn"]
|
|
|
|
flash(gettext("You have been successfully logged out."), "success")
|
|
|
|
return redirect(request.url)
|
|
|
|
|
|
|
|
if request.form["answer"] == "deny":
|
|
|
|
grant_user = None
|
|
|
|
|
|
|
|
if request.form["answer"] == "accept":
|
|
|
|
grant_user = user.dn
|
2020-08-17 15:49:49 +00:00
|
|
|
|
2020-10-28 16:57:27 +00:00
|
|
|
if consent:
|
2022-01-11 16:57:58 +00:00
|
|
|
consent.scope = list(set(scopes + consents[0].scope))
|
2020-10-28 16:57:27 +00:00
|
|
|
else:
|
2020-08-17 15:49:49 +00:00
|
|
|
|
2020-10-28 16:57:27 +00:00
|
|
|
consent = Consent(
|
2022-01-11 16:57:58 +00:00
|
|
|
client=client.dn,
|
|
|
|
subject=user.dn,
|
|
|
|
scope=scopes,
|
|
|
|
issue_date=datetime.datetime.now(),
|
2020-10-28 16:57:27 +00:00
|
|
|
)
|
2021-10-27 07:31:24 +00:00
|
|
|
consent.save()
|
2020-08-17 15:49:49 +00:00
|
|
|
|
2021-09-28 10:06:41 +00:00
|
|
|
response = authorization.create_authorization_response(grant_user=grant_user)
|
|
|
|
current_app.logger.debug(
|
|
|
|
"authorization endpoint response: %s", response.location
|
|
|
|
)
|
|
|
|
return response
|
2020-08-17 15:49:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
@bp.route("/token", methods=["POST"])
|
|
|
|
def issue_token():
|
2021-09-28 10:06:41 +00:00
|
|
|
current_app.logger.debug(
|
|
|
|
"token endpoint request: POST: %s", request.form.to_dict(flat=False)
|
|
|
|
)
|
|
|
|
response = authorization.create_token_response()
|
2021-09-28 11:45:47 +00:00
|
|
|
current_app.logger.debug("token endpoint response: %s", response.json)
|
2021-09-28 10:06:41 +00:00
|
|
|
return response
|
2020-08-24 12:44:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
@bp.route("/introspect", methods=["POST"])
|
|
|
|
def introspect_token():
|
2021-09-28 10:06:41 +00:00
|
|
|
current_app.logger.debug(
|
|
|
|
"introspection endpoint request: POST: %s", request.form.to_dict(flat=False)
|
|
|
|
)
|
|
|
|
response = authorization.create_endpoint_response(
|
|
|
|
IntrospectionEndpoint.ENDPOINT_NAME
|
|
|
|
)
|
2021-10-03 11:46:52 +00:00
|
|
|
current_app.logger.debug("introspection endpoint response: %s", response.json)
|
2021-09-28 10:06:41 +00:00
|
|
|
return response
|
2020-08-24 13:56:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
@bp.route("/revoke", methods=["POST"])
|
|
|
|
def revoke_token():
|
2021-09-28 10:06:41 +00:00
|
|
|
current_app.logger.debug(
|
|
|
|
"revokation endpoint request: POST: %s", request.form.to_dict(flat=False)
|
|
|
|
)
|
|
|
|
response = authorization.create_endpoint_response(RevocationEndpoint.ENDPOINT_NAME)
|
2021-10-03 11:46:52 +00:00
|
|
|
current_app.logger.debug("revokation endpoint response: %s", response.json)
|
2021-09-28 10:06:41 +00:00
|
|
|
return response
|
2020-08-26 09:54:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
@bp.route("/jwks.json")
|
|
|
|
def jwks():
|
2020-08-28 14:07:39 +00:00
|
|
|
with open(current_app.config["JWT"]["PUBLIC_KEY"]) as fd:
|
|
|
|
pubkey = fd.read()
|
|
|
|
|
2021-12-03 17:37:25 +00:00
|
|
|
obj = jwk.dumps(pubkey, current_app.config["JWT"].get("KTY", DEFAULT_JWT_KTY))
|
2020-08-28 14:07:39 +00:00
|
|
|
return jsonify(
|
|
|
|
{
|
|
|
|
"keys": [
|
|
|
|
{
|
|
|
|
"kid": None,
|
|
|
|
"use": "sig",
|
2021-12-03 17:37:25 +00:00
|
|
|
"alg": current_app.config["JWT"].get("ALG", DEFAULT_JWT_ALG),
|
2020-08-28 14:07:39 +00:00
|
|
|
**obj,
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
2020-08-26 09:54:35 +00:00
|
|
|
)
|
2020-09-25 09:26:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
@bp.route("/userinfo")
|
|
|
|
@require_oauth("profile")
|
|
|
|
def userinfo():
|
2021-10-20 10:05:08 +00:00
|
|
|
current_app.logger.debug("userinfo endpoint request: %s", request.args)
|
2022-01-11 16:57:58 +00:00
|
|
|
response = generate_user_info(current_token.subject, current_token.scope[0])
|
2021-09-28 10:06:41 +00:00
|
|
|
current_app.logger.debug("userinfo endpoint response: %s", response)
|
2021-09-28 11:45:47 +00:00
|
|
|
return jsonify(response)
|