def attempt_oauth_initialization(scope): """Attempts to perform GetOAuthUser RPC retrying deadlines. The result it cached in appengine.api.oauth guts. Never raises exceptions, just gives up letting subsequent oauth.* calls fail in a proper way. """ # 4 attempts: ~20 sec (default RPC deadline is 5 sec). attempt = 0 while attempt < 4: attempt += 1 try: oauth.get_client_id(scope) return except apiproxy_errors.DeadlineExceededError as e: logging.warning('DeadlineExceededError: %s', e) continue except oauth.OAuthServiceFailureError as e: logging.warning('oauth.OAuthServiceFailureError (%s): %s', e.__class__.__name__, e) # oauth library "caches" the error code in os.environ and retrying # oauth.get_client_id doesn't do anything. Clear this cache first, see # oauth_api.py, _maybe_call_get_oauth_user in GAE SDK. os.environ.pop('OAUTH_ERROR_CODE', None) continue except oauth.Error as e: # Next call to oauth.get_client_id() will trigger same error and it will # be handled for real. logging.warning('oauth.Error (%s): %s', e.__class__.__name__, e) return
def attempt_oauth_initialization(scope): """Attempts to perform GetOAuthUser RPC retrying deadlines. The result it cached in appengine.api.oauth guts. Never raises exceptions, just gives up letting subsequent oauth.* calls fail in a proper way. """ # 4 attempts: ~20 sec (default RPC deadline is 5 sec). attempt = 0 while attempt < 4: attempt += 1 try: oauth.get_client_id(scope) return except apiproxy_errors.DeadlineExceededError as e: logging.warning('DeadlineExceededError: %s', e) continue except oauth.OAuthServiceFailureError as e: logging.warning( 'oauth.OAuthServiceFailureError (%s): %s', e.__class__.__name__, e) # oauth library "caches" the error code in os.environ and retrying # oauth.get_client_id doesn't do anything. Clear this cache first, see # oauth_api.py, _maybe_call_get_oauth_user in GAE SDK. os.environ.pop('OAUTH_ERROR_CODE', None) continue except oauth.Error as e: # Next call to oauth.get_client_id() will trigger same error and it will # be handled for real. logging.warning('oauth.Error (%s): %s', e.__class__.__name__, e) return
def testMaybeSetVarsWithActualRequestAccessToken(self): dummy_scope = 'scope' dummy_token = 'dummy_token' dummy_email = '*****@*****.**' dummy_client_id = self._SAMPLE_ALLOWED_CLIENT_IDS[0] @api_config.api('TestApi', 'v1', allowed_client_ids=self._SAMPLE_ALLOWED_CLIENT_IDS, scopes=[dummy_scope]) class TestApiScopes(remote.Service): """Describes TestApiScopes.""" # pylint: disable=g-bad-name @api_config.method(message_types.VoidMessage, message_types.VoidMessage) def method(self, request): return request # users_id_token._get_id_token_user and time.time don't need to be stubbed # because the scopes used will not be [EMAIL_SCOPE] hence _get_id_token_user # will never be attempted self.mox.StubOutWithMock(users_id_token, '_is_local_dev') users_id_token._is_local_dev().AndReturn(False) self.mox.StubOutWithMock(oauth, 'get_client_id') oauth.get_client_id(dummy_scope).AndReturn(dummy_client_id) self.mox.ReplayAll() api_instance = TestApiScopes() os.environ['HTTP_AUTHORIZATION'] = 'Bearer ' + dummy_token api_instance.method(message_types.VoidMessage()) self.assertEqual(os.getenv('ENDPOINTS_USE_OAUTH_SCOPE'), dummy_scope) self.mox.VerifyAll()
def testGetClientIdCache(self): self.users_stub.SetOAuthUser(scopes=['https://www.mynet.net/myscope']) client_id = oauth.get_client_id('https://www.mynet.net/myscope') self.assertEqual('123456789.apps.googleusercontent.com', client_id) self.users_stub.SetOAuthUser(client_id='another_client_id') client_id = oauth.get_client_id('https://www.mynet.net/myscope') self.assertEqual('123456789.apps.googleusercontent.com', client_id)
def DoOAuthAuth(is_admin=None, require_level=None): """Verify OAuth was used with a valid account. Args: is_admin: Boolean. When True, checks if the user is an admin. require_level: int, default None, when defined, requires that a session be at level x. Returns: users.User() object. Raises: NotAuthenticated: there is no authenticated user for this request. IsAdminMismatch: the current user is not an administrator. """ # TODO(user): make use of require_level. try: user = oauth.get_current_user(OAUTH_SCOPE) except oauth.OAuthRequestError: raise NotAuthenticated('OAuthRequestError') try: if oauth.get_client_id(OAUTH_SCOPE) != settings.OAUTH_CLIENT_ID: raise NotAuthenticated('mismatched OAUTH_CLIENT_ID') except AttributeError: raise NotAuthenticated('OAUTH_CLIENT_ID not set') email = user.email() if is_admin is not None and not IsAdminUser(email): raise IsAdminMismatch if email in _GetGroupMembers('oauth_users'): return user logging.warning('OAuth user unknown: %s', email) raise NotAuthenticated('DoOAuthAuthUnknownUser')
def Authorize(): try: email = utils.GetEmail() except oauth.OAuthRequestError: raise OAuthError if not email: raise NotLoggedInError try: if not email.endswith('.gserviceaccount.com'): # For non-service account, need to verify that the OAuth client ID # is in our whitelist. client_id = oauth.get_client_id(utils.OAUTH_SCOPES) if client_id not in OAUTH_CLIENT_ID_WHITELIST: logging.error('OAuth client id %s for user %s not in whitelist', client_id, email) email = None raise OAuthError except oauth.OAuthRequestError: # Transient errors when checking the token result should result in HTTP 500, # so catch oauth.OAuthRequestError here, not oauth.Error (which would catch # both fatal and transient errors). raise OAuthError logging.info('OAuth user logged in as: %s', email) if utils.IsInternalUser(): datastore_hooks.SetPrivilegedRequest()
def get_current_rietveld_oauth_user(): """Gets the current OAuth 2.0 user associated with a request. This user must be intending to reach this application, so we check the token info to verify this is the case. Returns: A users.User object that was retrieved from the App Engine OAuth library if the token is valid, otherwise None. """ # TODO(dhermes): Address local environ here as well. try: current_client_id = oauth.get_client_id(EMAIL_SCOPE) except oauth.Error: return accepted_client_id, _, additional_client_ids = SecretKey.get_config() if (accepted_client_id != current_client_id and current_client_id not in additional_client_ids): logging.debug('Client ID %r not intended for this application.', current_client_id) return try: return oauth.get_current_user(EMAIL_SCOPE) except oauth.Error: logging.warning( 'A Client ID was retrieved with no corresponsing user.')
def get_current_rietveld_oauth_user(): """Gets the current OAuth 2.0 user associated with a request. This user must be intending to reach this application, so we check the token info to verify this is the case. Returns: A users.User object that was retrieved from the App Engine OAuth library if the token is valid, otherwise None. """ # TODO(dhermes): Address local environ here as well. try: current_client_id = oauth.get_client_id(EMAIL_SCOPE) except oauth.Error: return accepted_client_id, _, additional_client_ids = SecretKey.get_config() if (accepted_client_id != current_client_id and current_client_id not in additional_client_ids): logging.debug('Client ID %r not intended for this application.', current_client_id) return try: return oauth.get_current_user(EMAIL_SCOPE) except oauth.Error: logging.warning('A Client ID was retrieved with no corresponsing user.')
def get_client_id(): try: if not config.get('DEV_SERVER'): return oauth.get_client_id(SCOPES) except oauth.Error as e: pass return None
def Authorize(): try: user = oauth.get_current_user(OAUTH_SCOPES) except oauth.Error: raise NotLoggedInError if not user: raise NotLoggedInError try: if not user.email().endswith('.gserviceaccount.com'): # For non-service account, need to verify that the OAuth client ID # is in our whitelist. client_id = oauth.get_client_id(OAUTH_SCOPES) if client_id not in OAUTH_CLIENT_ID_WHITELIST: logging.info('OAuth client id %s for user %s not in whitelist', client_id, user.email()) user = None raise OAuthError except oauth.Error: raise OAuthError logging.info('OAuth user logged in as: %s', user.email()) if utils.IsGroupMember(user.email(), 'chromeperf-access'): datastore_hooks.SetPrivilegedRequest()
def _set_bearer_user_vars(allowed_client_ids, scopes): """Validate the oauth bearer token and set endpoints auth user variables. If the bearer token is valid, this sets ENDPOINTS_USE_OAUTH_SCOPE. This provides enough information that our endpoints.get_current_user() function can get the user. Args: allowed_client_ids: List of client IDs that are acceptable. scopes: List of acceptable scopes. """ for scope in scopes: try: client_id = oauth.get_client_id(scope) except oauth.Error: continue if allowed_client_ids and client_id not in allowed_client_ids: logging.warning('Client ID is not allowed: %s', client_id) return os.environ[_ENV_USE_OAUTH_SCOPE] = scope logging.debug('Returning user from matched oauth_user.') return logging.warning('Oauth framework user didn\'t match oauth token user.') return None
def _set_bearer_user_vars(allowed_client_ids, scopes): """Validate the oauth bearer token and set endpoints auth user variables. If the bearer token is valid, this sets ENDPOINTS_USE_OAUTH_SCOPE. This provides enough information that our endpoints.get_current_user() function can get the user. Args: allowed_client_ids: List of client IDs that are acceptable. scopes: List of acceptable scopes. """ for scope in scopes: try: client_id = oauth.get_client_id(scope) except oauth.Error: continue if (list(allowed_client_ids) != SKIP_CLIENT_ID_CHECK and client_id not in allowed_client_ids): logging.warning('Client ID is not allowed: %s', client_id) return os.environ[_ENV_USE_OAUTH_SCOPE] = scope logging.debug('Returning user from matched oauth_user.') return logging.debug('Oauth framework user didn\'t match oauth token user.') return None
def _set_bearer_user_vars(allowed_client_ids, scopes): """Validate the oauth bearer token and set endpoints auth user variables. If the bearer token is valid, this sets ENDPOINTS_USE_OAUTH_SCOPE. This provides enough information that our endpoints.get_current_user() function can get the user. Args: allowed_client_ids: List of client IDs that are acceptable. scopes: List of acceptable scopes. """ for scope in scopes: try: client_id = oauth.get_client_id(scope) except oauth.Error: # This scope failed. Try the next. continue # The client ID must be in allowed_client_ids. If allowed_client_ids is # empty, don't allow any client ID. If allowed_client_ids is set to # SKIP_CLIENT_ID_CHECK, all client IDs will be allowed. if (list(allowed_client_ids) != SKIP_CLIENT_ID_CHECK and client_id not in allowed_client_ids): logging.warning('Client ID is not allowed: %s', client_id) return os.environ[_ENV_USE_OAUTH_SCOPE] = scope logging.debug('Returning user from matched oauth_user.') return logging.debug('Oauth framework user didn\'t match oauth token user.') return None
def Authorize(): try: email = utils.GetEmail() except oauth.OAuthRequestError: raise OAuthError if not email: raise NotLoggedInError try: # TODO(dberris): Migrate to using Cloud IAM and checking roles instead, to # allow for dynamic management of the accounts. if not email.endswith('.gserviceaccount.com'): # For non-service accounts, need to verify that the OAuth client ID # is in our allowlist. client_id = oauth.get_client_id(utils.OAUTH_SCOPES) if client_id not in OAUTH_CLIENT_ID_ALLOWLIST: logging.error('OAuth client id %s for user %s not in allowlist', client_id, email) raise OAuthError except oauth.OAuthRequestError: # Transient errors when checking the token result should result in HTTP 500, # so catch oauth.OAuthRequestError here, not oauth.Error (which would catch # both fatal and transient errors). raise OAuthError logging.info('OAuth user logged in as: %s', email) if utils.IsInternalUser(): datastore_hooks.SetPrivilegedRequest()
def _set_bearer_user_vars(allowed_client_ids, scopes): """Validate the oauth bearer token and set endpoints auth user variables. If the bearer token is valid, this sets ENDPOINTS_USE_OAUTH_SCOPE. This provides enough information that our endpoints.get_current_user() function can get the user. Args: allowed_client_ids: List of client IDs that are acceptable. scopes: List of acceptable scopes. """ all_scopes, sufficient_scopes = _process_scopes(scopes) try: authorized_scopes = oauth.get_authorized_scopes(sorted(all_scopes)) except oauth.Error: _logger.debug('Unable to get authorized scopes.', exc_info=True) return if not _are_scopes_sufficient(authorized_scopes, sufficient_scopes): _logger.debug('Authorized scopes did not satisfy scope requirements.') return client_id = oauth.get_client_id(authorized_scopes) # The client ID must be in allowed_client_ids. If allowed_client_ids is # empty, don't allow any client ID. If allowed_client_ids is set to # SKIP_CLIENT_ID_CHECK, all client IDs will be allowed. if (list(allowed_client_ids) != SKIP_CLIENT_ID_CHECK and client_id not in allowed_client_ids): _logger.warning('Client ID is not allowed: %s', client_id) return os.environ[_ENV_USE_OAUTH_SCOPE] = ' '.join(authorized_scopes) _logger.debug('get_current_user() will return user from matched oauth_user.')
def AssertWhitelistedOrXSRF(self, mc, metadata): """Raise an exception if we don't want to process this request.""" # For local development, we accept any request. # TODO(jrobbins): make this more realistic by requiring a fake XSRF token. if settings.local_mode: return # Check if the user is whitelisted. auth_client_ids, auth_emails = ( client_config_svc.GetClientConfigSvc().GetClientIDEmails()) if mc.auth.user_pb.email in auth_emails: logging.info('User %r is whitelisted to use any client', mc.auth.user_pb.email) return # Check if the client is whitelisted. client_id = None try: client_id = oauth.get_client_id(framework_constants.OAUTH_SCOPE) logging.info('Oauth client ID %s', client_id) except oauth.Error as ex: logging.info('oauth.Error: %s' % ex) if client_id in auth_client_ids: logging.info('Client %r is whitelisted for any user', client_id) return # Otherwise, require an XSRF token generated by our app UI. logging.info( 'Neither email nor client ID is whitelisted, checking XSRF') token = metadata.get(XSRF_TOKEN_HEADER) xsrf.ValidateToken(token, mc.auth.user_id, xsrf.XHR_SERVLET_PATH, timeout=self.xsrf_timeout)
def test_oauth_works_as_expected_in_test(self): oauth_user = oauth.get_current_user(EMAIL_SCOPE) self.assertEqual(oauth_user.email(), TEST_EMAIL) self.assertEqual(oauth_user.auth_domain(), 'gmail.com') self.assertEqual(oauth_user.user_id(), '0') client_id = oauth.get_client_id(EMAIL_SCOPE) self.assertEqual(client_id, CLIENT_ID)
def _get_client_id(tries=3): """Call oauth.get_client_id() and retry if it times out.""" for attempt in xrange(tries): try: return oauth.get_client_id(EMAIL_SCOPE) except apiproxy_errors.DeadlineExceededError: logging.error('get_client_id() timed out on attempt %r', attempt) if attempt == tries - 1: raise
def extract_oauth_caller_identity(extra_client_ids=None): """Extracts and validates Identity of a caller for current request. Uses client_id whitelist fetched from the datastore to validate OAuth client used to build access_token. Also recognizes various types of service accounts and verifies that their client_id is what it should be. Service account's client_id doesn't have to be in client_id whitelist. Used by webapp2 request handlers and Cloud Endpoints API methods. Args: extra_client_ids: optional list of allowed client_ids, in addition to the whitelist in the datastore. Returns: Identity of the caller in case the request was successfully validated. Raises: AuthenticationError in case access_token is missing or invalid. AuthorizationError in case client_id is forbidden. """ # OAuth2 scope a token should have. oauth_scope = 'https://www.googleapis.com/auth/userinfo.email' # Fetch OAuth request state with retries. oauth.* calls use it internally. attempt_oauth_initialization(oauth_scope) # Extract client_id and email from access token. That also validates the token # and raises OAuthRequestError if token is revoked or otherwise not valid. try: client_id = oauth.get_client_id(oauth_scope) except oauth.OAuthRequestError: raise AuthenticationError('Invalid OAuth token') # This call just reads data cached by oauth.get_client_id, and thus should # never fail. email = oauth.get_current_user(oauth_scope).email() # Is client_id in the explicit whitelist? Used with three legged OAuth. Detect # Google service accounts. No need to whitelist client_ids for each of them, # since email address uniquely identifies credentials used. good = ( email.endswith('.gserviceaccount.com') or get_request_auth_db().is_allowed_oauth_client_id(client_id) or client_id in (extra_client_ids or [])) if not good: raise AuthorizationError( 'Unrecognized combination of email (%s) and client_id (%s). ' 'Is client_id whitelisted? Is it unrecognized service account?' % (email, client_id)) try: return model.Identity(model.IDENTITY_USER, email) except ValueError: raise AuthenticationError('Unsupported user email: %s' % email)
def attempt_oauth_initialization(scope): """Attempts to perform GetOAuthUser RPC retrying deadlines. The result it cached in appengine.api.oauth guts. Never raises exceptions, just gives up letting subsequent oauth.* calls fail in a proper way. """ # 4 attempts: ~20 sec (default RPC deadline is 5 sec). attempt = 0 while attempt < 4: attempt += 1 try: oauth.get_client_id(scope) return except apiproxy_errors.DeadlineExceededError as e: logging.warning('DeadlineExceededError: %s', e) continue except oauth.Error as e: # Next call to oauth.get_client_id() will trigger same error and it will # be handled for real. logging.warning('oauth.Error: %s', e) return
def AttemptOauth(self, client_id, allowed_client_ids=None): if allowed_client_ids is None: allowed_client_ids = self._SAMPLE_ALLOWED_CLIENT_IDS self.mox.StubOutWithMock(oauth, 'get_client_id') # We have four cases: # * no client ID is specified, so we raise for every scope. # * the given client ID is in the whitelist or there is no # whitelist, so we'll only be called once. # * we have a client ID not on the whitelist, so we need a # mock call for every scope. if client_id is None: for scope in self._SAMPLE_OAUTH_SCOPES: oauth.get_client_id(scope).AndRaise(oauth.Error) elif (list(allowed_client_ids) == users_id_token.SKIP_CLIENT_ID_CHECK or client_id in allowed_client_ids): scope = self._SAMPLE_OAUTH_SCOPES[0] oauth.get_client_id(scope).AndReturn(client_id) else: for scope in self._SAMPLE_OAUTH_SCOPES: oauth.get_client_id(scope).AndReturn(client_id) self.mox.ReplayAll() users_id_token._set_bearer_user_vars(allowed_client_ids, self._SAMPLE_OAUTH_SCOPES) self.mox.VerifyAll()
def testMultipleScopesSuccess(self): self.users_stub.SetOAuthUser(scopes=['scope1', 'scope2', 'scope3']) authorized_scopes = oauth.get_authorized_scopes( ('scope1', 'scope2', 'scope4')) client_id = oauth.get_client_id(('scope1', 'scope2', 'scope4')) user = oauth.get_current_user(['scope1', 'scope2', 'scope5']) self.assertCountEqual(['scope1', 'scope2'], authorized_scopes) self.assertEqual('123456789.apps.googleusercontent.com', client_id) self.assertEqual('*****@*****.**', user.email()) self.assertEqual('0', user.user_id()) self.assertEqual('gmail.com', user.auth_domain()) self.assertFalse(oauth.is_current_user_admin(('scope1', 'scope2'))) authorized_scopes = oauth.get_authorized_scopes( ['scope1', 'scope2', 'scope4']) client_id = oauth.get_client_id(['scope1', 'scope2', 'scope4']) user = oauth.get_current_user(['scope1', 'scope2', 'scope4']) self.assertCountEqual(['scope1', 'scope2'], authorized_scopes) self.assertEqual('123456789.apps.googleusercontent.com', client_id) self.assertEqual('*****@*****.**', user.email()) self.assertEqual('0', user.user_id()) self.assertEqual('gmail.com', user.auth_domain()) self.assertFalse(oauth.is_current_user_admin(('scope1', 'scope2')))
def _GetApiUser(): """Get the GAE `User` object for the currently authenticated user.""" user = users.get_current_user() if user: return user # Note (http://goo.gl/PCgGNp): "On the local development server, # oauth.get_current_user() always returns a User object with email set # to "*****@*****.**" and user ID set to 0 regardless of whether or # not a valid OAuth request was made. So test for oauth last. try: # Get the db.User that represents the user on whose behalf the # consumer is making this request. user = oauth.get_current_user(base_settings.OAUTH_SCOPE) client_id = oauth.get_client_id(base_settings.OAUTH_SCOPE) if user and client_id and base_settings.OAUTH_CLIENT_ID == client_id: return user except oauth.OAuthRequestError: pass raise AccessDeniedError('All authentication methods failed')
def authenticate_user(): """Quite possible that endpoints.get_current_user() may perform the authentication being performed here Ref: https://cloud.google.com/appengine/docs/python/endpoints/auth -Adding a user check to methods- """ scope = 'https://www.googleapis.com/auth/userinfo.email' logging.info('\noauth.get_current_user(%s)' % repr(scope)) try: user = oauth.get_current_user(scope) allowed_clients = ['407408718192.apps.googleusercontent.com'] # list your client ids here token_audience = oauth.get_client_id(scope) if token_audience not in allowed_clients: raise oauth.OAuthRequestError('audience of token \'%s\' is not in allowed list (%s)' % (token_audience, allowed_clients)) logging.info(' = %s\n' % user) logging.info('- auth_domain = %s\n' % user.auth_domain()) logging.info('- email = %s\n' % user.email()) logging.info('- nickname = %s\n' % user.nickname()) logging.info('- user_id = %s\n' % user.user_id()) except oauth.OAuthRequestError, e: # # self.response.set_status(401) # # self.response.write(' -> %s %s\n' % (e.__class__.__name__, e.message)) # logging.warn(traceback.format_exc()) logging.error(e.message)
from google.appengine.api import oauth scope = 'https://www.googleapis.com/auth/userinfo.email' self.response.write('\noauth.get_current_user(%s)' % repr(scope)) try: user = oauth.get_current_user(scope) allowed clients = ['474832205158-5q0sqta9e932cmftub9aaihgeb2c6hko.apps.googleusercontent.com'] token_audience = oauth.get_client_id(scope) if token_audience not in allowed_clients: raise oauth.OAuthRequestError('audience of token \'%s\' is not in allowed list (%s)' % (token_audience, allowed clients)) self.response.write(' = %s\n' % user) self.response.write(' - auth_domain = %s \n' % user.auth_domain()) self.response.write(' - email = %s \n' % user.nickname()) self.response.write(' - user_id = %s \n' % user.user_id()) except oauth.OAuthRequestError, e: self.response.set_status(401) self.response.write(' -> %s %s \n' % (e.__class__.__name__, e.message)) logging.warn(traceback.format_exc())
def GetOauthClientId(scope=_EMAIL_SCOPE): """Returns the client id of the oauth user.""" try: return oauth.get_client_id(scope) except oauth.OAuthRequestError: return None # Invalid oauth token.
def extract_oauth_caller_identity(extra_client_ids=None): """Extracts and validates Identity of a caller for current request. Uses client_id whitelist fetched from the datastore to validate OAuth client used to build access_token. Also recognizes various types of service accounts and verifies that their client_id is what it should be. Service account's client_id doesn't have to be in client_id whitelist. Used by webapp2 request handlers and Cloud Endpoints API methods. Args: extra_client_ids: optional list of allowed client_ids, in addition to the whitelist in the datastore. Returns: Identity of the caller in case the request was successfully validated. Raises: AuthenticationError in case access_token is missing or invalid. AuthorizationError in case client_id is forbidden. """ # OAuth2 scope a token should have. oauth_scope = 'https://www.googleapis.com/auth/userinfo.email' # Extract client_id and email from access token. That also validates the token # and raises OAuthRequestError if token is revoked or otherwise not valid. try: client_id = oauth.get_client_id(oauth_scope) except oauth.OAuthRequestError: raise AuthenticationError('Invalid OAuth token') # This call just reads data cached by oauth.get_client_id, and thus should # never fail. email = oauth.get_current_user(oauth_scope).email() # Is client_id in the explicit whitelist? Used with three legged OAuth. good = (get_request_auth_db().is_allowed_oauth_client_id(client_id) or client_id in (extra_client_ids or [])) # Detect various sorts of service accounts. They have a property: client_id # and email are related in some way. No need to whitelist client_ids for each # of them, since email address uniquely identifies credentials used. # GAE service account. Token via app_identity.get_access_token(). if not good and email.endswith('@appspot.gserviceaccount.com'): good = (client_id == 'anonymous') # GCE service account. Token via GCE metadata server. if not good and email.endswith('@project.gserviceaccount.com'): project_id = email[:-len('@project.gserviceaccount.com')] good = (client_id == '%s.project.googleusercontent.com' % project_id) # Service account with *.p12 key. Token via SignedJwtAssertionCredentials. if not good and email.endswith('@developer.gserviceaccount.com'): prefix = email[:-len('@developer.gserviceaccount.com')] good = (client_id == '%s.apps.googleusercontent.com' % prefix) if not good: raise AuthorizationError('Invalid OAuth client_id: %s' % client_id) try: return model.Identity(model.IDENTITY_USER, email) except ValueError: raise AuthenticationError('Unsupported user email: %s' % email)
def api_base_checks(request, requester, services, cnxn, auth_client_ids, auth_emails): """Base checks for API users. Args: request: The HTTP request from Cloud Endpoints. requester: The user who sends the request. services: Services object. cnxn: connection to the SQL database. auth_client_ids: authorized client ids. auth_emails: authorized emails when client is anonymous. Returns: Client ID and client email. Raises: endpoints.UnauthorizedException: If the requester is anonymous. user_svc.NoSuchUserException: If the requester does not exist in Monorail. project_svc.NoSuchProjectException: If the project does not exist in Monorail. permissions.BannedUserException: If the requester is banned. permissions.PermissionException: If the requester does not have permisssion to view. """ valid_user = False auth_err = '' client_id = None try: client_id = oauth.get_client_id(framework_constants.OAUTH_SCOPE) logging.info('Oauth client ID %s', client_id) except oauth.Error as ex: auth_err = 'oauth.Error: %s' % ex if not requester: try: requester = oauth.get_current_user(framework_constants.OAUTH_SCOPE) logging.info('Oauth requester %s', requester.email()) except oauth.Error as ex: auth_err = 'oauth.Error: %s' % ex if client_id and requester: if client_id != 'anonymous': if client_id in auth_client_ids: valid_user = True else: auth_err = 'Client ID %s is not whitelisted' % client_id # Some service accounts may have anonymous client ID else: if requester.email() in auth_emails: valid_user = True else: auth_err = 'Client email %s is not whitelisted' % requester.email() if not valid_user: raise endpoints.UnauthorizedException('Auth error: %s' % auth_err) else: logging.info('API request from user %s:%s', client_id, requester.email()) project_name = None if hasattr(request, 'projectId'): project_name = request.projectId issue_local_id = None if hasattr(request, 'issueId'): issue_local_id = request.issueId # This could raise user_svc.NoSuchUserException requester_id = services.user.LookupUserID(cnxn, requester.email()) requester_pb = services.user.GetUser(cnxn, requester_id) requester_view = framework_views.UserView(requester_pb) if permissions.IsBanned(requester_pb, requester_view): raise permissions.BannedUserException( 'The user %s has been banned from using Monorail' % requester.email()) if project_name: project = services.project.GetProjectByName( cnxn, project_name) if not project: raise project_svc.NoSuchProjectException( 'Project %s does not exist' % project_name) if project.state != project_pb2.ProjectState.LIVE: raise permissions.PermissionException( 'API may not access project %s because it is not live' % project_name) requester_effective_ids = services.usergroup.LookupMemberships( cnxn, requester_id) requester_effective_ids.add(requester_id) if not permissions.UserCanViewProject( requester_pb, requester_effective_ids, project): raise permissions.PermissionException( 'The user %s has no permission for project %s' % (requester.email(), project_name)) if issue_local_id: # This may raise a NoSuchIssueException. issue = services.issue.GetIssueByLocalID( cnxn, project.project_id, issue_local_id) perms = permissions.GetPermissions( requester_pb, requester_effective_ids, project) config = services.config.GetProjectConfig(cnxn, project.project_id) granted_perms = tracker_bizobj.GetGrantedPerms( issue, requester_effective_ids, config) if not permissions.CanViewIssue( requester_effective_ids, perms, project, issue, granted_perms=granted_perms): raise permissions.PermissionException( 'User is not allowed to view this issue %s:%d' % (project_name, issue_local_id)) return client_id, requester.email()
def get_client_id(): try: return oauth.get_client_id(SCOPES) except oauth.Error as e: return None
def extract_oauth_caller_identity(extra_client_ids=None): """Extracts and validates Identity of a caller for current request. Uses client_id whitelist fetched from the datastore to validate OAuth client used to build access_token. Also recognizes various types of service accounts and verifies that their client_id is what it should be. Service account's client_id doesn't have to be in client_id whitelist. Used by webapp2 request handlers and Cloud Endpoints API methods. Args: extra_client_ids: optional list of allowed client_ids, in addition to the whitelist in the datastore. Returns: Identity of the caller in case the request was successfully validated. Raises: AuthenticationError in case access_token is missing or invalid. AuthorizationError in case client_id is forbidden. """ # OAuth2 scope a token should have. oauth_scope = 'https://www.googleapis.com/auth/userinfo.email' # Extract client_id and email from access token. That also validates the token # and raises OAuthRequestError if token is revoked or otherwise not valid. try: client_id = oauth.get_client_id(oauth_scope) except oauth.OAuthRequestError: raise AuthenticationError('Invalid OAuth token') # This call just reads data cached by oauth.get_client_id, and thus should # never fail. email = oauth.get_current_user(oauth_scope).email() # Is client_id in the explicit whitelist? Used with three legged OAuth. good = ( get_request_auth_db().is_allowed_oauth_client_id(client_id) or client_id in (extra_client_ids or [])) # Detect various sorts of service accounts. They have a property: client_id # and email are related in some way. No need to whitelist client_ids for each # of them, since email address uniquely identifies credentials used. # GAE service account. Token via app_identity.get_access_token(). if not good and email.endswith('@appspot.gserviceaccount.com'): good = (client_id == 'anonymous') # GCE service account. Token via GCE metadata server. if not good and email.endswith('@project.gserviceaccount.com'): project_id = email[:-len('@project.gserviceaccount.com')] good = (client_id == '%s.project.googleusercontent.com' % project_id) # Service account with *.p12 key. Token via SignedJwtAssertionCredentials. if not good and email.endswith('@developer.gserviceaccount.com'): prefix = email[:-len('@developer.gserviceaccount.com')] good = (client_id == '%s.apps.googleusercontent.com' % prefix) if not good: raise AuthorizationError('Invalid OAuth client_id: %s' % client_id) try: return model.Identity(model.IDENTITY_USER, email) except ValueError: raise AuthenticationError('Unsupported user email: %s' % email)