Coverage for heritrace / routes / auth.py: 100%

61 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-21 12:56 +0000

1# SPDX-FileCopyrightText: 2024-2025 Arcangelo Massari <arcangelo.massari@unibo.it> 

2# 

3# SPDX-License-Identifier: ISC 

4 

5import os 

6from datetime import timedelta 

7 

8from flask import (Blueprint, current_app, flash, redirect, request, session, 

9 url_for) 

10from flask_babel import gettext 

11from flask_login import current_user, login_user, logout_user 

12from heritrace.apis.orcid import extract_orcid_id, is_orcid_url 

13from heritrace.models import User 

14from requests_oauthlib import OAuth2Session 

15 

16auth_bp = Blueprint("auth", __name__) 

17 

18 

19@auth_bp.route("/login") 

20def login(): 

21 if current_user.is_authenticated: 

22 return redirect(url_for("main.catalogue")) 

23 

24 if os.environ.get("FLASK_ENV") == "demo": 

25 user_id_from_env = os.environ.get("USER_ID", "demo_user") 

26 demo_uri = f"http://example.org/demo/{user_id_from_env}" 

27 user_name = f"Demo User ({user_id_from_env})" 

28 

29 user = User(id=demo_uri, name=user_name, orcid=demo_uri) 

30 session["user_name"] = user_name 

31 session.permanent = True 

32 current_app.permanent_session_lifetime = timedelta(days=30) 

33 login_user(user) 

34 

35 flash(gettext("Welcome! You've been automatically logged in to the demo"), "info") 

36 return redirect(url_for("main.catalogue")) 

37 

38 callback_url = url_for("auth.callback", _external=True, _scheme="https") 

39 orcid = OAuth2Session( 

40 current_app.config["ORCID_CLIENT_ID"], 

41 redirect_uri=callback_url, 

42 scope=[current_app.config["ORCID_SCOPE"], "openid"], 

43 ) 

44 authorization_url, state = orcid.authorization_url( 

45 current_app.config["ORCID_AUTHORIZE_URL"], 

46 prompt="login", # Forza il re-login 

47 nonce=os.urandom(16).hex(), # Aggiungiamo un nonce per sicurezza 

48 ) 

49 session["oauth_state"] = state 

50 return redirect(authorization_url) 

51 

52 

53@auth_bp.route("/callback") 

54def callback(): 

55 if request.url.startswith("http://"): 

56 secure_url = request.url.replace("http://", "https://", 1) 

57 else: 

58 secure_url = request.url 

59 

60 orcid = OAuth2Session( 

61 current_app.config["ORCID_CLIENT_ID"], state=session["oauth_state"] 

62 ) 

63 try: 

64 token = orcid.fetch_token( 

65 current_app.config["ORCID_TOKEN_URL"], 

66 client_secret=current_app.config["ORCID_CLIENT_SECRET"], 

67 authorization_response=secure_url, 

68 ) 

69 except Exception as e: 

70 flash( 

71 gettext("An error occurred during authentication. Please try again"), 

72 "danger", 

73 ) 

74 return redirect(url_for("auth.login")) 

75 orcid_id = token["orcid"] 

76 

77 safelist = current_app.config["ORCID_SAFELIST"] 

78 if safelist: 

79 normalized_safelist = { 

80 extract_orcid_id(item) if is_orcid_url(item) else item for item in safelist 

81 } 

82 if orcid_id not in normalized_safelist: 

83 flash( 

84 gettext("Your ORCID is not authorized to access this application"), 

85 "danger", 

86 ) 

87 return redirect(url_for("auth.login")) 

88 

89 session["user_name"] = token["name"] 

90 user = User(id=orcid_id, name=token["name"], orcid=orcid_id) 

91 session.permanent = True 

92 current_app.permanent_session_lifetime = timedelta(days=30) 

93 login_user(user) 

94 flash(gettext("Welcome back %(name)s!", name=current_user.name), "success") 

95 return redirect(url_for("main.catalogue")) 

96 

97 

98@auth_bp.route("/logout") 

99def logout(): 

100 if not current_user.is_authenticated: 

101 return "", 401 

102 logout_user() 

103 flash(gettext("You have been logged out"), "info") 

104 return redirect(url_for("main.index"))