Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(223)

Unified Diff: appengine/components/components/auth/ui/ui.py

Issue 2838793002: auth: Split UI pages into admin and non-admin ones. (Closed)
Patch Set: comments Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « appengine/components/components/auth/ui/templates/linking_done.html ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: appengine/components/components/auth/ui/ui.py
diff --git a/appengine/components/components/auth/ui/ui.py b/appengine/components/components/auth/ui/ui.py
index 10f9a6c533127f1202fae6e207a1af2ff0bc15e3..c0b3956a2df13c86bb0832fdfb2dd4af58c96d9c 100644
--- a/appengine/components/components/auth/ui/ui.py
+++ b/appengine/components/components/auth/ui/ui.py
@@ -115,81 +115,38 @@ def redirect_ui_on_replica(method):
return wrapper
-class UIHandler(handler.AuthenticatingHandler):
- """Renders Jinja templates extending base.html or base_minimal.html."""
+################################################################################
+## Admin routes. The use cookies and GAE's "is_current_user_admin" for authn.
+
+
+class AdminPageHandler(handler.AuthenticatingHandler):
+ """Base class for handlers involved in bootstrap processes."""
# TODO(vadimsh): Enable CSP nonce for styles too. We'll need to get rid of
# all 'style=...' attributes first.
csp_use_script_nonce = True
+ @classmethod
+ def get_auth_methods(cls, conf):
+ # This method sets 'is_superuser' bit for GAE-level admins.
+ return [handler.gae_cookie_authentication]
+
def reply(self, path, env=None, status=200):
"""Render template |path| to response using given environment.
- Optional keys from |env| that base.html uses:
- css_file: URL to a file with page specific styles, relative to site root.
- js_file: URL to a file with page specific Javascript code, relative to
- site root. File should define global object named same as a filename,
- i.e. '/auth/static/js/api.js' should define global object 'api' that
- incapsulates functionality implemented in the module.
- navbar_tab_id: id a navbar tab to highlight.
- page_title: title of an HTML page.
-
Args:
path: path to a template, relative to templates/.
env: additional environment dict to use when rendering the template.
status: HTTP status code to return.
"""
- env = (env or {}).copy()
- env.setdefault('css_file', None)
- env.setdefault('js_file', None)
- env.setdefault('navbar_tab_id', None)
- env.setdefault('page_title', 'Untitled')
-
- # This goes to both Jinja2 env and Javascript config object.
- user = self.get_current_user()
- common = {
- 'account_picture': user.picture() if user else None,
- 'auth_service_config_locked': False, # overridden in auth_service
- 'is_admin': api.is_admin(),
- 'login_url': self.create_login_url(self.request.url),
- 'logout_url': self.create_logout_url('/'),
- 'using_gae_auth': self.auth_method == handler.gae_cookie_authentication,
- 'xsrf_token': self.generate_xsrf_token(),
- }
- if _ui_env_callback:
- common.update(_ui_env_callback(self))
-
- # Name of Javascript module with page code.
- js_module_name = None
- if env['js_file']:
- assert env['js_file'].endswith('.js')
- js_module_name = os.path.basename(env['js_file'])[:-3]
-
- # This will be accessible from Javascript as global 'config' variable.
- js_config = {
- 'identity': api.get_current_identity().to_bytes(),
- }
- js_config.update(common)
-
- # Jinja2 environment to use to render a template.
full_env = {
'app_name': _ui_app_name,
- 'app_revision_url': utils.get_app_revision_url(),
- 'app_version': utils.get_app_version(),
- 'config': json.dumps(js_config),
'csp_nonce': self.csp_nonce,
'identity': api.get_current_identity(),
- 'js_module_name': js_module_name,
- 'navbar': [
- (cls.navbar_tab_id, cls.navbar_tab_title, cls.navbar_tab_url)
- for cls in _ui_navbar_tabs
- if cls.is_visible()
- ],
+ 'logout_url': json.dumps(self.create_logout_url('/')), # see base.html
+ 'xsrf_token': self.generate_xsrf_token(),
}
- full_env.update(common)
- full_env.update(env)
-
- # Render it.
+ full_env.update(env or {})
self.response.set_status(status)
self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
self.response.write(template.render(path, full_env))
@@ -200,7 +157,7 @@ class UIHandler(handler.AuthenticatingHandler):
'page_title': 'Access Denied',
'error': error,
}
- self.reply('auth/access_denied.html', env=env, status=401)
+ self.reply('auth/admin/access_denied.html', env=env, status=401)
def authorization_error(self, error):
"""Redirects to login or shows 'Access Denied' page."""
@@ -221,19 +178,10 @@ class UIHandler(handler.AuthenticatingHandler):
'page_title': 'Access Denied',
'error': error,
}
- self.reply('auth/access_denied.html', env=env, status=403)
+ self.reply('auth/admin/access_denied.html', env=env, status=403)
-class MainHandler(UIHandler):
- """Redirects to first navbar tab."""
- @redirect_ui_on_replica
- @api.require(acl.has_access)
- def get(self):
- assert _ui_navbar_tabs
- self.redirect(_ui_navbar_tabs[0].navbar_tab_url)
-
-
-class BootstrapHandler(UIHandler):
+class BootstrapHandler(AdminPageHandler):
"""Creates Administrators group (if necessary) and adds current caller to it.
Requires Appengine level Admin access for its handlers, since Administrators
@@ -242,11 +190,6 @@ class BootstrapHandler(UIHandler):
Used during bootstrap of a new service instance.
"""
- @classmethod
- def get_auth_methods(cls, conf):
- # This method sets 'is_superuser' bit for GAE-level admins.
- return [handler.gae_cookie_authentication]
-
@forbid_ui_on_replica
@api.require(api.is_superuser)
def get(self):
@@ -255,7 +198,7 @@ class BootstrapHandler(UIHandler):
'admin_group': model.ADMIN_GROUP,
'return_url': self.request.get('r') or '',
}
- self.reply('auth/bootstrap.html', env)
+ self.reply('auth/admin/bootstrap.html', env)
@forbid_ui_on_replica
@api.require(api.is_superuser)
@@ -269,10 +212,10 @@ class BootstrapHandler(UIHandler):
'added': added,
'return_url': self.request.get('return_url') or '',
}
- self.reply('auth/bootstrap_done.html', env)
+ self.reply('auth/admin/bootstrap_done.html', env)
-class BootstrapOAuthHandler(UIHandler):
+class BootstrapOAuthHandler(AdminPageHandler):
"""Page to set OAuth2 client ID used by the main web UI.
Requires Appengine level Admin access for its handlers, since without client
@@ -282,11 +225,6 @@ class BootstrapOAuthHandler(UIHandler):
also available after the service is linked to some primary Auth service.
"""
- @classmethod
- def get_auth_methods(cls, conf):
- # This method sets 'is_superuser' bit for GAE-level admins.
- return [handler.gae_cookie_authentication]
-
@api.require(api.is_superuser)
def get(self):
self.show_page(web_client_id=api.get_web_client_id_uncached())
@@ -303,10 +241,10 @@ class BootstrapOAuthHandler(UIHandler):
'web_client_id': web_client_id or '',
'saved': saved,
}
- self.reply('auth/bootstrap_oauth.html', env)
+ self.reply('auth/admin/bootstrap_oauth.html', env)
-class LinkToPrimaryHandler(UIHandler):
+class LinkToPrimaryHandler(AdminPageHandler):
"""A page with confirmation of Primary <-> Replica linking request.
URL to that page is generated by a Primary service.
@@ -321,11 +259,6 @@ class LinkToPrimaryHandler(UIHandler):
self.abort(400)
return
- @classmethod
- def get_auth_methods(cls, conf):
- # This method sets 'is_superuser' bit for GAE-level admins.
- return [handler.gae_cookie_authentication]
-
@forbid_ui_on_replica
@api.require(api.is_superuser)
def get(self):
@@ -336,7 +269,7 @@ class LinkToPrimaryHandler(UIHandler):
'primary_id': ticket.primary_id,
'primary_url': ticket.primary_url,
}
- self.reply('auth/linking.html', env)
+ self.reply('auth/admin/linking.html', env)
@forbid_ui_on_replica
@api.require(api.is_superuser)
@@ -356,7 +289,133 @@ class LinkToPrimaryHandler(UIHandler):
'primary_url': ticket.primary_url,
'success': success,
}
- self.reply('auth/linking_done.html', env)
+ self.reply('auth/admin/linking_done.html', env)
+
+
+################################################################################
+## Web UI routes.
+
+# TODO(vadimsh): Switch them to use OAuth for authentication.
+
+
+class UIHandler(handler.AuthenticatingHandler):
+ """Renders Jinja templates extending base.html."""
+
+ # TODO(vadimsh): Enable CSP nonce for styles too. We'll need to get rid of
+ # all 'style=...' attributes first.
+ csp_use_script_nonce = True
+
+ def reply(self, path, env=None, status=200):
+ """Renders template |path| to the HTTP response using given environment.
+
+ Optional keys from |env| that base.html uses:
+ css_file: URL to a file with page specific styles, relative to site root.
+ js_file: URL to a file with page specific Javascript code, relative to
+ site root. File should define global object named same as a filename,
+ i.e. '/auth/static/js/api.js' should define global object 'api' that
+ incapsulates functionality implemented in the module.
+ navbar_tab_id: id of a navbar tab to highlight.
+ page_title: title of an HTML page.
+
+ Args:
+ path: path to a template, relative to templates/.
+ env: additional environment dict to use when rendering the template.
+ status: HTTP status code to return.
+ """
+ env = (env or {}).copy()
+ env.setdefault('css_file', None)
+ env.setdefault('js_file', None)
+ env.setdefault('navbar_tab_id', None)
+ env.setdefault('page_title', 'Untitled')
+
+ # This goes to both Jinja2 env and Javascript config object.
+ user = self.get_current_user()
+ common = {
+ 'account_picture': user.picture() if user else None,
+ 'auth_service_config_locked': False, # overridden in auth_service
+ 'is_admin': api.is_admin(),
+ 'login_url': self.create_login_url(self.request.url),
+ 'logout_url': self.create_logout_url('/'),
+ 'using_gae_auth': self.auth_method == handler.gae_cookie_authentication,
+ 'xsrf_token': self.generate_xsrf_token(),
+ }
+ if _ui_env_callback:
+ common.update(_ui_env_callback(self))
+
+ # Name of Javascript module with page code.
+ js_module_name = None
+ if env['js_file']:
+ assert env['js_file'].endswith('.js')
+ js_module_name = os.path.basename(env['js_file'])[:-3]
+
+ # This will be accessible from Javascript as global 'config' variable.
+ js_config = {
+ 'identity': api.get_current_identity().to_bytes(),
+ }
+ js_config.update(common)
+
+ # Jinja2 environment to use to render a template.
+ full_env = {
+ 'app_name': _ui_app_name,
+ 'app_revision_url': utils.get_app_revision_url(),
+ 'app_version': utils.get_app_version(),
+ 'config': json.dumps(js_config),
+ 'csp_nonce': self.csp_nonce,
+ 'identity': api.get_current_identity(),
+ 'js_module_name': js_module_name,
+ 'navbar': [
+ (cls.navbar_tab_id, cls.navbar_tab_title, cls.navbar_tab_url)
+ for cls in _ui_navbar_tabs
+ if cls.is_visible()
+ ],
+ }
+ full_env.update(common)
+ full_env.update(env)
+
+ # Render it.
+ self.response.set_status(status)
+ self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
+ self.response.write(template.render(path, full_env))
+
+ def authentication_error(self, error):
+ """Shows 'Access denied' page."""
+ # TODO(vadimsh): This will be deleted once we use Google Sign-In.
+ env = {
+ 'page_title': 'Access Denied',
+ 'error': error,
+ }
+ self.reply('auth/access_denied.html', env=env, status=401)
+
+ def authorization_error(self, error):
+ """Redirects to login or shows 'Access Denied' page."""
+ # TODO(vadimsh): This will be deleted once we use Google Sign-In.
+ # Not authenticated or used IP whitelist for auth -> redirect to login.
+ # Bots doesn't use UI, and users should always use real accounts.
+ ident = api.get_current_identity()
+ if ident.is_anonymous or ident.is_bot:
+ self.redirect(self.create_login_url(self.request.url))
+ return
+
+ # Admin group is empty -> redirect to bootstrap procedure to create it.
+ if model.is_empty_group(model.ADMIN_GROUP):
+ self.redirect_to('bootstrap')
+ return
+
+ # No access.
+ env = {
+ 'page_title': 'Access Denied',
+ 'error': error,
+ }
+ self.reply('auth/access_denied.html', env=env, status=403)
+
+
+class MainHandler(UIHandler):
+ """Redirects to first navbar tab."""
+ @redirect_ui_on_replica
+ @api.require(acl.has_access)
+ def get(self):
+ assert _ui_navbar_tabs
+ self.redirect(_ui_navbar_tabs[0].navbar_tab_url)
class UINavbarTabHandler(UIHandler):
« no previous file with comments | « appengine/components/components/auth/ui/templates/linking_done.html ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698