Coverage for webapp/authentication.py: 84%
50 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-08 12:33 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-08 12:33 +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]
36# keys for session data that should NOT be cleared on auth refresh
37SESSION_INTEGRATION_KEYS = [
38 "github_auth_secret",
39]
41SESSION_DATA_KEYS = SESSION_AUTH_KEYS + SESSION_INTEGRATION_KEYS
44def get_authorization_header(macaroon):
45 """
46 Return the authorization header value for an exchanged macaroon.
47 """
48 return f"Macaroon {macaroon}"
51def get_session_authorization_headers(session):
52 """
53 Return the correct authorization headers for the current session.
54 """
55 if "macaroon_exchanged" in session:
56 return {
57 "Authorization": get_authorization_header(
58 session["macaroon_exchanged"]
59 )
60 }
62 if "macaroon_root" in session and "macaroon_discharge" in session:
63 root = session["macaroon_root"]
64 discharge = session["macaroon_discharge"]
66 bound = Macaroon.deserialize(root).prepare_for_request(
67 Macaroon.deserialize(discharge)
68 )
70 return {
71 "Authorization": (
72 f"macaroon root={root}, discharge={bound.serialize()}"
73 )
74 }
76 if "macaroons" in session:
77 return {"Macaroons": session["macaroons"]}
79 return {"Macaroons": ""}
82def get_publishergw_authorization_header(developer_token):
83 return {"Authorization ": f"Macaroon {developer_token}"}
86def is_authenticated(session):
87 """
88 Checks if the user is authenticated from the session
89 Returns True if the user is authenticated
90 """
91 return (
92 ("publisher" in session and "macaroon_exchanged" in session)
93 or (
94 "publisher" in session
95 and "macaroon_discharge" in session
96 and "macaroon_root" in session
97 )
98 or ("publisher" in session and "macaroons" in session)
99 )
102def empty_session(session):
103 """
104 Empty the session, used to logout.
105 """
106 for key in SESSION_DATA_KEYS:
107 session.pop(key, None)
110def reset_auth_session(session):
111 """
112 Clear Snapcraft auth keys while preserving integration tokens
113 (e.g. github_auth_secret). Used when re-authentication is needed
114 but third-party tokens are still valid.
115 """
116 for key in SESSION_AUTH_KEYS:
117 session.pop(key, None)
120def get_caveat_id(root):
121 """
122 Returns the caveat_id generated by the SSO
123 """
124 location = urlparse(LOGIN_URL).hostname
125 (caveat,) = [
126 c
127 for c in Macaroon.deserialize(root).third_party_caveats()
128 if c.location == location
129 ]
131 return caveat.caveat_id
134def request_macaroon():
135 """
136 Request a macaroon from dashboard.
137 Returns the macaroon.
138 """
139 response = sso.post_macaroon({"permissions": PERMISSIONS})
141 return response["macaroon"]
144def get_refreshed_discharge(discharge):
145 """
146 Get a refresh macaroon if the macaroon is not valid anymore.
147 Returns the new discharge macaroon.
148 """
149 response = sso.get_refreshed_discharge({"discharge_macaroon": discharge})
151 return response["discharge_macaroon"]
154def is_macaroon_expired(headers):
155 """
156 Returns True if the macaroon needs to be refreshed from
157 the header response.
158 """
159 return headers.get("WWW-Authenticate") == ("Macaroon needs_refresh=1")
162def _store_api_authorization_header(_self, session):
163 return get_session_authorization_headers(session)
166store_api_dashboard.Dashboard._get_authorization_header = (
167 _store_api_authorization_header
168)
169store_api_publishergw.dashboard_authorization_header = (
170 get_session_authorization_headers
171)