Coverage for heritrace / routes / auth.py: 100%
63 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-07-02 10:16 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-07-02 10:16 +0000
1# SPDX-FileCopyrightText: 2024-2025 Arcangelo Massari <arcangelo.massari@unibo.it>
2#
3# SPDX-License-Identifier: ISC
5import os
6from datetime import timedelta
8from flask import Blueprint, current_app, flash, redirect, request, session, url_for
9from flask_babel import gettext
10from flask_login import current_user, login_user, logout_user
11from requests.exceptions import RequestException
12from requests_oauthlib import OAuth2Session
13from werkzeug.wrappers import Response as WerkzeugResponse
15from heritrace.apis.orcid import extract_orcid_id, is_orcid_url
16from heritrace.models import User
18auth_bp = Blueprint("auth", __name__)
21@auth_bp.route("/login")
22def login() -> WerkzeugResponse:
23 if current_user.is_authenticated:
24 return redirect(url_for("main.catalogue"))
26 if os.environ.get("FLASK_ENV") == "demo":
27 user_id_from_env = os.environ.get("USER_ID", "demo_user")
28 demo_uri = f"http://example.org/demo/{user_id_from_env}"
29 user_name = f"Demo User ({user_id_from_env})"
31 user = User(user_id=demo_uri, name=user_name, orcid=demo_uri)
32 session["user_name"] = user_name
33 session.permanent = True
34 current_app.permanent_session_lifetime = timedelta(days=30)
35 login_user(user)
37 flash(
38 gettext("Welcome! You've been automatically logged in to the demo"), "info"
39 )
40 return redirect(url_for("main.catalogue"))
42 callback_url = url_for("auth.callback", _external=True, _scheme="https")
43 orcid = OAuth2Session(
44 current_app.config["ORCID_CLIENT_ID"],
45 redirect_uri=callback_url,
46 scope=["/authenticate", "openid"],
47 )
48 authorization_url, state = orcid.authorization_url(
49 "https://orcid.org/oauth/authorize",
50 prompt="login", # Forza il re-login
51 nonce=os.urandom(16).hex(), # Aggiungiamo un nonce per sicurezza
52 )
53 session["oauth_state"] = state
54 return redirect(authorization_url)
57@auth_bp.route("/callback")
58def callback() -> WerkzeugResponse:
59 if request.url.startswith("http://"):
60 secure_url = request.url.replace("http://", "https://", 1)
61 else:
62 secure_url = request.url
64 orcid = OAuth2Session(
65 current_app.config["ORCID_CLIENT_ID"], state=session["oauth_state"]
66 )
67 try:
68 token = orcid.fetch_token(
69 "https://orcid.org/oauth/token",
70 client_secret=current_app.config["ORCID_CLIENT_SECRET"],
71 authorization_response=secure_url,
72 )
73 except (RequestException, ValueError, KeyError):
74 flash(
75 gettext("An error occurred during authentication. Please try again"),
76 "danger",
77 )
78 return redirect(url_for("auth.login"))
79 orcid_id = str(token["orcid"])
81 safelist = current_app.config["ORCID_SAFELIST"]
82 if safelist:
83 normalized_safelist = {
84 extract_orcid_id(item) if is_orcid_url(item) else item for item in safelist
85 }
86 if orcid_id not in normalized_safelist:
87 flash(
88 gettext("Your ORCID is not authorized to access this application"),
89 "danger",
90 )
91 return redirect(url_for("auth.login"))
93 session["user_name"] = token["name"]
94 user = User(user_id=orcid_id, name=str(token["name"]), orcid=orcid_id)
95 session.permanent = True
96 current_app.permanent_session_lifetime = timedelta(days=30)
97 login_user(user)
98 flash(gettext("Welcome back %(name)s!", name=current_user.name), "success")
99 return redirect(url_for("main.catalogue"))
102@auth_bp.route("/logout")
103def logout() -> tuple[str, int] | WerkzeugResponse:
104 if not current_user.is_authenticated:
105 return "", 401
106 logout_user()
107 flash(gettext("You have been logged out"), "info")
108 return redirect(url_for("main.index"))