Coverage for webapp/authentication.py: 92%
50 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-25 22:09 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-25 22:09 +0000
1import os
3from urllib.parse import urlparse
5from canonicalwebteam.store_api import dashboard as store_api_dashboard
6from canonicalwebteam.store_api import publishergw as store_api_publishergw
7from pymacaroons import Macaroon
8from webapp.api import sso
10LOGIN_URL = os.getenv("LOGIN_URL", "https://login.ubuntu.com")
12PERMISSIONS = [
13 "edit_account",
14 "package_access",
15 "package_manage",
16 "package_metrics",
17 "package_register",
18 "package_release",
19 "package_update",
20 "package_upload_request",
21 "store_admin",
22]
24# keys for session data that should be cleared on auth refresh
25SESSION_AUTH_KEYS = [
26 "macaroons",
27 "macaroon_exchanged",
28 "macaroon_root",
29 "macaroon_discharge",
30 "publisher",
31 "developer_token",
32 "exchanged_developer_token",
33 "csrf_token",
34 "pending_snap_unregister",
35]
37# keys for session data that should NOT be cleared on auth refresh
38SESSION_INTEGRATION_KEYS = [
39 "github_auth_secret",
40]
42SESSION_DATA_KEYS = SESSION_AUTH_KEYS + SESSION_INTEGRATION_KEYS
45def get_authorization_header(macaroon):
46 """
47 Return the authorization header value for an exchanged macaroon.
48 """
49 return f"Macaroon {macaroon}"
52def get_session_authorization_headers(session):
53 """
54 Return the correct authorization headers for the current session.
55 """
56 if "macaroon_exchanged" in session:
57 return {
58 "Authorization": get_authorization_header(
59 session["macaroon_exchanged"]
60 )
61 }
63 if "macaroon_root" in session and "macaroon_discharge" in session:
64 root = session["macaroon_root"]
65 discharge = session["macaroon_discharge"]
67 bound = Macaroon.deserialize(root).prepare_for_request(
68 Macaroon.deserialize(discharge)
69 )
71 return {
72 "Authorization": (
73 f"macaroon root={root}, discharge={bound.serialize()}"
74 )
75 }
77 if "macaroons" in session:
78 return {"Macaroons": session["macaroons"]}
80 return {"Macaroons": ""}
83def get_publishergw_authorization_header(developer_token):
84 return {"Authorization ": f"Macaroon {developer_token}"}
87def is_authenticated(session):
88 """
89 Checks if the user is authenticated from the session
90 Returns True if the user is authenticated
91 """
92 return (
93 ("publisher" in session and "macaroon_exchanged" in session)
94 or (
95 "publisher" in session
96 and "macaroon_discharge" in session
97 and "macaroon_root" in session
98 )
99 or ("publisher" in session and "macaroons" in session)
100 )
103def empty_session(session):
104 """
105 Empty the session, used to logout.
106 """
107 for key in SESSION_DATA_KEYS:
108 session.pop(key, None)
111def reset_auth_session(session):
112 """
113 Clear Snapcraft auth keys while preserving integration tokens
114 (e.g. github_auth_secret). Used when re-authentication is needed
115 but third-party tokens are still valid.
116 """
117 for key in SESSION_AUTH_KEYS:
118 session.pop(key, None)
121def get_caveat_id(root):
122 """
123 Returns the caveat_id generated by the SSO
124 """
125 location = urlparse(LOGIN_URL).hostname
126 (caveat,) = [
127 c
128 for c in Macaroon.deserialize(root).third_party_caveats()
129 if c.location == location
130 ]
132 return caveat.caveat_id
135def request_macaroon():
136 """
137 Request a macaroon from dashboard.
138 Returns the macaroon.
139 """
140 response = sso.post_macaroon({"permissions": PERMISSIONS})
142 return response["macaroon"]
145def get_refreshed_discharge(discharge):
146 """
147 Get a refresh macaroon if the macaroon is not valid anymore.
148 Returns the new discharge macaroon.
149 """
150 response = sso.get_refreshed_discharge({"discharge_macaroon": discharge})
152 return response["discharge_macaroon"]
155def is_macaroon_expired(headers):
156 """
157 Returns True if the macaroon needs to be refreshed from
158 the header response.
159 """
160 return headers.get("WWW-Authenticate") == ("Macaroon needs_refresh=1")
163def _store_api_authorization_header(_self, session):
164 return get_session_authorization_headers(session)
167store_api_dashboard.Dashboard._get_authorization_header = (
168 _store_api_authorization_header
169)
170store_api_publishergw.dashboard_authorization_header = (
171 get_session_authorization_headers
172)