def testGetCurrent_AfterGetForEmailWithNonexistentGoogleAccount(self): self.mox.stubs.Set(users, '_EmailToGaeUserId', {}.get) # Introduce a previously unseen e-mail address. self.assertEquals('1', users.GetForEmail('*****@*****.**').id) # A sign-in with that e-mail address should associate the Google Account. with test_utils.EnvContext(USER_ID='123456789', USER_EMAIL='*****@*****.**'): self.assertEquals('1', users.GetCurrent().id) # Subsequent sign-ins with the same Google Account ID should also hook up. with test_utils.EnvContext(USER_ID='123456789', USER_EMAIL='*****@*****.**'): self.assertEquals('1', users.GetCurrent().id)
def testGetCurrent_GoogleAccountMapping(self): with test_utils.EnvContext(USER_ID='123456789', USER_EMAIL='*****@*****.**'): user = users.GetCurrent() # should allocate the first uid, '1' self.assertEquals('1', user.id) with test_utils.EnvContext(USER_ID='666666', USER_EMAIL='*****@*****.**'): user = users.GetCurrent() # should allocate the next uid, '2' self.assertEquals('2', user.id) with test_utils.EnvContext(USER_ID='123456789', USER_EMAIL='*****@*****.**'): user = users.GetCurrent() # should match by USER_ID self.assertEquals('1', user.id)
def testGetCurrent_UpdateEmailAndDomain(self): with test_utils.EnvContext(USER_ID='123456789', USER_EMAIL='*****@*****.**', USER_ORGANIZATION='alpha.test'): users.GetCurrent() # should allocate the first uid, '1' user = users.Get('1') self.assertEquals('alpha.test', user.ga_domain) self.assertEquals('*****@*****.**', user.email) with test_utils.EnvContext(USER_ID='123456789', USER_EMAIL='*****@*****.**', USER_ORGANIZATION='beta.test'): users.GetCurrent() # should update the existing UserModel user = users.Get('1') self.assertEquals('beta.test', user.ga_domain) self.assertEquals('*****@*****.**', user.email)
def RenderTemplate(self, template_name, context): """Renders a template from the templates/ directory. Args: template_name: A string, the filename of the template to render. context: An optional dictionary of template variables. A few variables are automatically added to this context: - {{root}} is the root_path of the app - {{user}} is the signed-in user - {{login_url}} is a URL to a sign-in page - {{logout_url}} is a URL that signs the user out - {{navbar}} contains variables used by the navigation sidebar Returns: A string, the rendered template. """ path = os.path.join(os.path.dirname(__file__), 'templates', template_name) root = config.Get('root_path') or '' user = users.GetCurrent() context = dict(context, root=root, user=user, xsrf_tag=self.xsrf_tag, login_url=users.GetLoginUrl(self.request.url), logout_url=users.GetLogoutUrl(root + '/.maps'), navbar=self._GetNavbarContext(user)) return template.render(path, context)
def CheckAccess(self): """If login_access_list is set, accept only the specified logins.""" login_access_list = config.Get('login_access_list') if login_access_list is not None: user = users.GetCurrent() if not user: raise RedirectToUrl(users.GetLoginUrl(self.request.url)) if user.email not in login_access_list: raise perms.AuthorizationError(user, None, None)
def testGetCurrent_NormalLogin(self): # Try an effective user determined by the Google Account login. with test_utils.EnvContext(USER_ID='123456789', USER_EMAIL='*****@*****.**', USER_ORGANIZATION='alpha.test'): user = users.GetCurrent() # should allocate the first uid, '1' self.assertEquals('1', user.id) user = users.Get('1') self.assertEquals('alpha.test', user.ga_domain) self.assertEquals('*****@*****.**', user.email)
def testGetCurrent_ImpersonationNotAllowed(self): # Verify that the crisismap_login cookie doesn't work for ordinary users. with test_utils.EnvContext( SERVER_SOFTWARE='Google App Engine/1.7.6', USER_ID='123456789', USER_EMAIL='*****@*****.**', USER_ORGANIZATION='alpha.test', HTTP_COOKIE='crisismap_login=t1000:sky.net:[email protected]'): user = users.GetCurrent() self.assertEquals('1', user.id) # cookie should be ignored self.assertEquals('alpha.test', user.ga_domain) self.assertEquals('*****@*****.**', user.email)
def testGetCurrent_ImpersonationInDev(self): # Verify that the crisismap_login cookie works in development. with test_utils.EnvContext( SERVER_SOFTWARE='Development/1.0', USER_ID='123456789', USER_EMAIL='*****@*****.**', USER_ORGANIZATION='alpha.test', HTTP_COOKIE='crisismap_login=t1000:sky.net:[email protected]'): user = users.GetCurrent() self.assertEquals('t1000', user.id) # cookie should be used self.assertEquals('sky.net', user.ga_domain) self.assertEquals('*****@*****.**', user.email)
def testGetCurrent_ImpersonationInProd(self): # Verify that the crisismap_login cookie works for admins in prod. with test_utils.EnvContext( SERVER_SOFTWARE='Google App Engine/1.7.6', USER_ID='123456789', USER_EMAIL='*****@*****.**', USER_ORGANIZATION='alpha.test', USER_IS_ADMIN='1', HTTP_COOKIE='crisismap_login=t1000:sky.net:[email protected]'): user = users.GetCurrent() self.assertEquals('t1000', user.id) # cookie should be used self.assertEquals('sky.net', user.ga_domain) self.assertEquals('*****@*****.**', user.email)
def CheckAccess(role, target=None, user=None, policy=None): """Checks whether the given user has the specified access role. Args: role: A Role constant identifying the desired access role. target: The object to which access is desired. If 'role' is in MAP_ROLES, this should be a Map object; if 'role' is in DOMAIN_ROLES, this should be a domain name (a string); otherwise, this argument is unused. user: (optional) A users.User object. If not specified, access permissions are checked for the currently signed-in user. policy: The access policy to apply. Returns: True if the user has the specified access permission. Raises: ValueError: The specified role is not a valid member of Role. TypeError: The target has the wrong type for the given role. """ policy = policy or AccessPolicy() user = user or users.GetCurrent() # Roles that are unrelated to a target. if role == Role.ADMIN: return policy.HasRoleAdmin(user) # Roles with a domain as the target. if role in DOMAIN_ROLES and not isinstance(target, basestring): raise TypeError('For role %r, target should be a string' % role) if role == Role.CATALOG_EDITOR: return policy.HasRoleCatalogEditor(user, target) if role == Role.MAP_CREATOR: return policy.HasRoleMapCreator(user, target) if role == Role.DOMAIN_REVIEWER: return policy.HasRoleDomainReviewer(user, target) if role == Role.DOMAIN_ADMIN: return policy.HasRoleDomainAdmin(user, target) # Roles with a Map as the target if role in MAP_ROLES and target.__class__.__name__ not in [ 'Map', 'EmptyMap' ]: raise TypeError('For role %r, target should be a Map' % role) if role == Role.MAP_OWNER: return policy.HasRoleMapOwner(user, target) if role == Role.MAP_REVIEWER: return policy.HasRoleMapReviewer(user, target) if role == Role.MAP_EDITOR: return policy.HasRoleMapEditor(user, target) if role == Role.MAP_VIEWER: return policy.HasRoleMapViewer(user, target) raise ValueError('Invalid role %r' % role)
def GetCurrentUserUrl(self): """Gets a URL identifying the current user. If the user is logged in, the URL will contain the user ID. Otherwise, we'll use a randomly generated cookie to make a semi-stable user URL. Returns: A URL (under this app's root URL) that identifies the current user. """ user = users.GetCurrent() if user: return self.GetUrlForUser(user) return self.GetUrlForAnonymousUser()
def testGetCurrent_AfterGetForEmailWithExistingGoogleAccount(self): self.mox.stubs.Set(users, '_EmailToGaeUserId', { '*****@*****.**': '123456789', '*****@*****.**': '123456789' }.get) # Introduce an e-mail address that's new to us but has a Google Account. # GetForEmail should associate the address with the Google Account. self.assertEquals('1', users.GetForEmail('*****@*****.**').id) # A sign-in with that Google Account ID should get the same UserModel, # even if the e-mail address is different. with test_utils.EnvContext(USER_ID='123456789', USER_EMAIL='*****@*****.**'): self.assertEquals('1', users.GetCurrent().id) # Any other e-mail address associated with the same Google Account should # yield the same UserModel. self.assertEquals('1', users.GetForEmail('*****@*****.**').id)
def SendPermissionChangeEmail(self, recipient_email, map_object, role, message): """Sends recipient_email an email with info of map and permission level.""" email = users.GetCurrent().email subject = map_object.title url = (self.request.host_url + self.request.root_path + '/.maps/' + map_object.id) body = """ I've invited you to collaborate on the map "%s". You can access the map at: %s You have %s access; please use this invitation within 30 days. %s""" % (map_object.title, url, SHARING_ROLES[role], message) mail.send_mail(email, recipient_email, subject, body)
def AssertAccess(role, target=None, user=None, policy=None): """Requires that the given user has the specified access role. Args: role: A Role constant identifying the desired access role. target: The object to which access is desired. If 'role' is in MAP_ROLES, this should be a Map object; if 'role' is in DOMAIN_ROLES, this should be a domain name (a string); otherwise, this argument is unused. user: (optional) A users.User object. If not specified, access permissions are checked for the currently signed-in user. policy: The access policy to apply. Raises: AuthorizationError: If the user lacks the given access permission. """ user = user or users.GetCurrent() # ensure user is set in error message if not CheckAccess(role, target=target, user=user, policy=policy): raise AuthorizationError(user, role, target)
def RecordEvent(event, domain_name=None, map_id=None, map_version_key=None, catalog_entry_key=None, acceptable_purpose=None, acceptable_org=None, org_name=None, uid=None): """Stores an event log entry.""" if not uid: user = users.GetCurrent() uid = user and user.id or None try: EventLog(time=datetime.datetime.utcnow(), uid=uid, event=event, domain_name=domain_name, map_id=map_id, map_version_key=map_version_key, catalog_entry_key=catalog_entry_key, acceptable_purpose=acceptable_purpose, acceptable_org=acceptable_org, org_name=org_name).put() except Exception, e: # pylint: disable=broad-except logging.exception(e)
def AssertCatalogEntryOwner(entry, user=None): user = user or users.GetCurrent() if user.id != entry.creator_uid: raise NotCatalogEntryOwnerError(user, entry)
def GetConfig(request, map_object=None, catalog_entry=None, xsrf_token=''): dev_mode = request.get('dev') and users.IsDeveloper() map_picker_items = GetMapPickerItems( catalog_entry and catalog_entry.domain or config.Get('primary_domain'), request.root_path) # Fill the cm_config dictionary. root = request.root_path xsrf_qs = '?xsrf_token=' + xsrf_token # needed for all POST URLs result = { 'dev_mode': dev_mode, 'langs': base_handler.ALL_LANGUAGES, # Each endpoint that the JS client code uses gets an entry in config. 'js_root': root + '/.js', 'json_proxy_url': root + '/.jsonp', 'kmlify_url': request.host_url + root + '/.kmlify', 'login_url': users.GetLoginUrl(request.url), 'logout_url': users.GetLogoutUrl(request.url), 'map_picker_items': map_picker_items, 'protect_url': root + '/.protect', 'report_query_url': root + '/.api/reports', 'report_post_url': root + '/.api/reports' + xsrf_qs, 'vote_post_url': root + '/.api/votes' + xsrf_qs, 'static_content_url': root + '/.static', 'user_email': users.GetCurrent() and users.GetCurrent().email, 'wms_configure_url': root + '/.wms/configure', 'wms_tiles_url': root + '/.wms/tiles' } # Add settings from the selected client config, if any. result.update(GetClientConfig(request.get('client'), request.headers.get('referer'), dev_mode)) # Add the MapRoot data and other map-specific information. if catalog_entry: # published map map_root = result['map_root'] = catalog_entry.map_root result['label'] = catalog_entry.label result['publisher_name'] = catalog_entry.publisher_name key = catalog_entry.map_version_key elif map_object: # draft map map_root = result['map_root'] = map_object.map_root result['map_list_url'] = root + '/.maps' result['diff_url'] = root + '/.diff/' + map_object.id + xsrf_qs result['save_url'] = root + '/.api/maps/' + map_object.id + xsrf_qs result['share_url'] = root + '/.share/' + map_object.id + xsrf_qs result['api_maps_url'] = root + '/.api/maps' result['legend_url'] = root + '/.legend' result['wms_query_url'] = root + '/.wms/query' result['enable_editing'] = map_object.CheckAccess(perms.Role.MAP_EDITOR) result['draft_mode'] = True key = map_object.current_version_key # Parameters that depend on the MapRoot, for both published and draft maps. ui_region = request.get('gl') if map_object or catalog_entry: result['lang'] = base_handler.SelectLanguageForRequest(request, map_root) ui_region = map_root.get('region', ui_region) cache_key, sources = metadata.CacheSourceAddresses(key, result['map_root']) result['metadata'] = {s: METADATA_CACHE.Get(s) for s in sources} result['metadata_url'] = root + '/.metadata?ck=' + cache_key metadata.ActivateSources(sources) # Construct the URL for the Maps JavaScript API. api_url_params = { 'sensor': 'false', 'libraries': 'places,search,visualization,weather', 'client': GetMapsApiClientId(request.host), 'language': request.lang } if ui_region: api_url_params['region'] = ui_region result['maps_api_url'] = (MAPS_API_BASE_URL + '?' + urllib.urlencode(api_url_params)) maproot_url = request.get('maproot_url', '') if dev_mode or maproot_url.startswith(request.root_url + '/'): # It's always okay to fetch MapRoot JSON from a URL if it's from this app. # In developer mode only, allow MapRoot JSON from arbitrary URLs. result['maproot_url'] = maproot_url if dev_mode: # In developer mode only, allow query params to override the result. # Developers can also specify map_root directly as a query param. for name in ( ClientConfig.properties().keys() + ['map_root', 'use_tab_panel']): value = request.get(name) if value: result[name] = json.loads(value) return result
def SetupUser(context): """Ensures that the User for a login context exists in the datastore.""" with context: return users.GetCurrent() # implicitly updates the datastore
def HandleRequest(self, **kwargs): """A wrapper around the Get or Post method defined in the handler class.""" try: method = getattr(self, self.request.method.capitalize(), None) root_path = config.Get('root_path') or '' user = users.GetCurrent() if not method: raise Error(405, '%s method not allowed.' % self.request.method) # Enforce login restrictions. self.CheckAccess() # Set self.auth according to the API key in the request, if specified. self.auth = GetAuthForRequest(self.request) # Require/allow domain name and user sign-in based on whether the method # takes arguments named 'domain' and 'user'. args, _, _, defaults = inspect.getargspec(method) required_args = args[:len(args) - len(defaults or [])] if 'domain' in kwargs and 'domain' not in args: raise Error(404, 'Not found.') if 'domain' in required_args and 'domain' not in kwargs: raise Error(400, 'Domain not specified.') if 'user' in args: kwargs['user'] = user if 'user' in required_args and not user: return self.redirect(users.GetLoginUrl(self.request.url)) # Prepare an XSRF token if the user is signed in. if user: self.xsrf_token = GenerateXsrfToken(user.id) self.xsrf_tag = ( '<input type="hidden" name="xsrf_token" value="%s">' % self.xsrf_token) # Require a valid XSRF token for all authenticated POST requests. if user and self.request.method == 'POST': xsrf_token = self.request.get('xsrf_token', '') if not ValidateXsrfToken(user.id, xsrf_token): logging.warn('Bad xsrf_token %r for uid %r', xsrf_token, user.id) # The window might have been idle for a day; go somewhere reasonable. return self.redirect(root_path + '/.maps') # Fill in some useful request variables. self.request.lang = SelectLanguage( self.request.get('hl'), self.request.headers.get('accept-language')) self.request.root_path = root_path self.request.root_url = self.request.host_url + root_path # To prevent clickjacking attacks, disable framing by default. if not self.embeddable: self.response.headers['X-Frame-Options'] = 'DENY' # Call the handler, making nice pages for errors derived from Error. method(**kwargs) except RedirectToUrl as exception: return self.redirect(exception.url) except perms.AuthorizationError as exception: self.response.set_status(403, message=exception.message) self.response.out.write( self.RenderTemplate( 'unauthorized.html', { 'exception': exception, 'login_url': users.GetLoginUrl(self.request.url) })) except perms.NotPublishableError as exception: self.response.set_status(403, message=exception.message) self.response.out.write( self.RenderTemplate(self.error_template, {'exception': exception})) except perms.NotCatalogEntryOwnerError as exception: # TODO(kpy): Either add a template for this type of error, or use an # error representation that can be handled by one common error template. self.response.set_status(403, message=exception.message) self.response.out.write( self.RenderTemplate( self.error_template, { 'exception': utils.Struct( message='That publication label is owned ' 'by someone else; you can\'t replace or delete it.' ) })) except ApiError as exception: self.response.set_status(exception.status, message=exception.message) self.response.headers['Content-Type'] = 'text/plain' self.response.out.write(exception.message + '\n') except Error as exception: self.response.set_status(exception.status, message=exception.message) self.response.out.write( self.RenderTemplate(self.error_template, {'exception': exception}))