Coverage for webapp/login/views.py: 94%
65 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-30 22:06 +0000
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-30 22:06 +0000
1import os
3import flask
4from canonicalwebteam.store_api.dashboard import Dashboard
5from canonicalwebteam.store_api.publishergw import PublisherGW
6from canonicalwebteam.store_api.devicegw import DeviceGW
8from django_openid_auth.teams import TeamsRequest, TeamsResponse
9from flask_openid import OpenID
11from webapp import authentication
12from webapp.helpers import api_publisher_session, api_session
13from webapp.api.exceptions import ApiResponseError
14from webapp.extensions import csrf
15from webapp.login.macaroon import MacaroonRequest, MacaroonResponse
16from webapp.publisher.snaps import logic
18login = flask.Blueprint(
19 "login", __name__, template_folder="/templates", static_folder="/static"
20)
22LOGIN_URL = os.getenv("LOGIN_URL", "https://login.ubuntu.com")
23ENVIRONMENT = os.getenv("ENVIRONMENT", "devel")
26# getter for ENVIRONMENT variable
27# this allows the value to be mocked in tests
28def get_environment():
29 return ENVIRONMENT
32LP_CANONICAL_TEAM = "canonical"
34open_id = OpenID(
35 store_factory=lambda: None,
36 safe_roots=[],
37 extension_responses=[MacaroonResponse, TeamsResponse],
38)
40dashboard = Dashboard(api_session)
41publisher_gateway = PublisherGW(api_publisher_session)
42device_gateway = DeviceGW("snap", api_session)
45@login.route("/login", methods=["GET", "POST"])
46@csrf.exempt
47@open_id.loginhandler
48def login_handler():
49 if authentication.is_authenticated(flask.session):
50 return flask.redirect(open_id.get_next_url())
52 try:
53 root = authentication.request_macaroon()
54 except ApiResponseError as api_response_error:
55 if api_response_error.status_code == 401:
56 return flask.redirect(flask.url_for(".logout"))
57 else:
58 return flask.abort(502, str(api_response_error))
60 openid_macaroon = MacaroonRequest(
61 caveat_id=authentication.get_caveat_id(root)
62 )
63 flask.session["macaroon_root"] = root
65 lp_teams = TeamsRequest(query_membership=[LP_CANONICAL_TEAM])
67 return open_id.try_login(
68 LOGIN_URL,
69 ask_for=["email", "nickname", "image"],
70 ask_for_optional=["fullname"],
71 extensions=[openid_macaroon, lp_teams],
72 )
75@open_id.after_login
76def after_login(resp):
77 flask.session["macaroon_discharge"] = resp.extensions["macaroon"].discharge
79 if not resp.nickname:
80 return flask.redirect(LOGIN_URL)
82 account = dashboard.get_account(flask.session)
83 validation_sets = dashboard.get_validation_sets(flask.session)
85 if account:
86 is_canonical = LP_CANONICAL_TEAM in resp.extensions["lp"].is_member
88 # in environments other than production, for testing purposes,
89 # we detect if the user is Canonical by checking
90 # if the email ends with @canonical.com
91 if (not is_canonical) and get_environment() != "production":
92 is_canonical = account["email"] and account["email"].endswith(
93 "@canonical.com"
94 )
96 flask.session["publisher"] = {
97 "identity_url": resp.identity_url,
98 "nickname": account["username"],
99 "fullname": account["displayname"],
100 "image": resp.image,
101 "email": account["email"],
102 "is_canonical": is_canonical,
103 }
105 if logic.get_stores(
106 account["stores"], roles=["admin", "review", "view"]
107 ):
108 flask.session["publisher"]["has_stores"] = (
109 len(dashboard.get_stores(flask.session)) > 0
110 )
112 flask.session["publisher"]["has_validation_sets"] = (
113 validation_sets is not None
114 and len(validation_sets["assertions"]) > 0
115 )
116 else:
117 flask.session["publisher"] = {
118 "identity_url": resp.identity_url,
119 "nickname": resp.nickname,
120 "fullname": resp.fullname,
121 "image": resp.image,
122 "email": resp.email,
123 }
125 response = flask.make_response(
126 flask.redirect(
127 open_id.get_next_url(),
128 302,
129 ),
130 )
131 # this is a temporary cookies to be taken out later
132 response.set_cookie("login_migrated", "true")
133 return response
136@login.route("/login-beta", methods=["GET"])
137def login_beta():
138 return flask.redirect(flask.url_for(".login_handler"))
141@login.route("/logout")
142def logout():
143 authentication.empty_session(flask.session)
145 return flask.redirect("/")