Coverage for webapp/authentication.py: 97%
34 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-15 22:43 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-15 22:43 +0000
1import os
3from urllib.parse import urlparse
5from pymacaroons import Macaroon
6from webapp.api import sso
8LOGIN_URL = os.getenv("LOGIN_URL", "https://login.ubuntu.com")
10PERMISSIONS = [
11 "edit_account",
12 "package_access",
13 "package_manage",
14 "package_metrics",
15 "package_register",
16 "package_release",
17 "package_update",
18 "package_upload_request",
19 "store_admin",
20]
22# keys for session data that should be cleared on auth refresh
23SESSION_AUTH_KEYS = [
24 "macaroons",
25 "macaroon_root",
26 "macaroon_discharge",
27 "publisher",
28 "developer_token",
29 "exchanged_developer_token",
30 "csrf_token",
31]
33# keys for session data that should NOT be cleared on auth refresh
34SESSION_INTEGRATION_KEYS = [
35 "github_auth_secret",
36]
38SESSION_DATA_KEYS = SESSION_AUTH_KEYS + SESSION_INTEGRATION_KEYS
41def get_authorization_header(root, discharge):
42 """
43 Bind root and discharge macaroons and return the authorization header.
44 """
46 bound = Macaroon.deserialize(root).prepare_for_request(
47 Macaroon.deserialize(discharge)
48 )
50 return "macaroon root={}, discharge={}".format(root, bound.serialize())
53def get_publishergw_authorization_header(developer_token):
54 return {"Authorization ": f"Macaroon {developer_token}"}
57def is_authenticated(session):
58 """
59 Checks if the user is authenticated from the session
60 Returns True if the user is authenticated
61 """
62 return (
63 "publisher" in session
64 and "macaroon_discharge" in session
65 and "macaroon_root" in session
66 ) or ("publisher" in session and "macaroons" in session)
69def empty_session(session):
70 """
71 Empty the session, used to logout.
72 """
73 for key in SESSION_DATA_KEYS:
74 session.pop(key, None)
77def reset_auth_session(session):
78 """
79 Clear Snapcraft auth keys while preserving integration tokens
80 (e.g. github_auth_secret). Used when re-authentication is needed
81 but third-party tokens are still valid.
82 """
83 for key in SESSION_AUTH_KEYS:
84 session.pop(key, None)
87def get_caveat_id(root):
88 """
89 Returns the caveat_id generated by the SSO
90 """
91 location = urlparse(LOGIN_URL).hostname
92 (caveat,) = [
93 c
94 for c in Macaroon.deserialize(root).third_party_caveats()
95 if c.location == location
96 ]
98 return caveat.caveat_id
101def request_macaroon():
102 """
103 Request a macaroon from dashboard.
104 Returns the macaroon.
105 """
106 response = sso.post_macaroon({"permissions": PERMISSIONS})
108 return response["macaroon"]
111def get_refreshed_discharge(discharge):
112 """
113 Get a refresh macaroon if the macaroon is not valid anymore.
114 Returns the new discharge macaroon.
115 """
116 response = sso.get_refreshed_discharge({"discharge_macaroon": discharge})
118 return response["discharge_macaroon"]
121def is_macaroon_expired(headers):
122 """
123 Returns True if the macaroon needs to be refreshed from
124 the header response.
125 """
126 return headers.get("WWW-Authenticate") == ("Macaroon needs_refresh=1")