def _is_valid_token(self, token): """Verify the token is valid format and has not expired.""" current_time = timeutils.normalize_time(timeutils.utcnow()) try: # Get the data we need from the correct location (V2 and V3 tokens # differ in structure, Try V3 first, fall back to V2 second) token_data = token.get('token', token.get('access')) expires_at = token_data.get('expires_at', token_data.get('expires')) if not expires_at: expires_at = token_data['token']['expires'] expiry = timeutils.normalize_time( timeutils.parse_isotime(expires_at)) except Exception: LOG.exception(_('Unexpected error or malformed token determining ' 'token expiry: %s'), token) raise exception.TokenNotFound(_('Failed to validate token')) if current_time < expiry: self.check_revocation(token) # Token has not expired and has not been revoked. return None else: raise exception.TokenNotFound(_('Failed to validate token'))
def _is_valid_token(self, token): # Verify the token has not expired. current_time = timeutils.normalize_time(timeutils.utcnow()) try: # Get the data we need from the correct location (V2 and V3 tokens # differ in structure, Try V3 first, fall back to V2 second) token_data = token.get('token', token.get('access')) expires_at = token_data.get('expires_at', token_data.get('expires')) if not expires_at: expires_at = token_data['token']['expires'] expiry = timeutils.normalize_time( timeutils.parse_isotime(expires_at)) if current_time < expiry: # Token is has not expired and has not been revoked. return None except Exception: LOG.exception(_('Unexpected error or malformed token determining ' 'token expiry: %s') % token) # FIXME(morganfainberg): This error message needs to be updated to # reflect the token couldn't be found, but this change needs to wait # until Icehouse due to string freeze in Havana. This should be: # "Failed to find valid token" or something similar. raise exception.TokenNotFound(_('Failed to validate token'))
def build_token_values(token_data): token_expires_at = timeutils.parse_isotime(token_data['expires_at']) # Trim off the microseconds because the revocation event only has # expirations accurate to the second. token_expires_at = token_expires_at.replace(microsecond=0) token_values = { 'expires_at': timeutils.normalize_time(token_expires_at), 'issued_at': timeutils.normalize_time( timeutils.parse_isotime(token_data['issued_at']))} user = token_data.get('user') if user is not None: token_values['user_id'] = user['id'] token_values['identity_domain_id'] = user['domain']['id'] else: token_values['user_id'] = None token_values['identity_domain_id'] = None project = token_data.get('project', token_data.get('tenant')) if project is not None: token_values['project_id'] = project['id'] token_values['assignment_domain_id'] = project['domain']['id'] else: token_values['project_id'] = None domain = token_data.get('domain') if domain is not None: token_values['assignment_domain_id'] = domain['id'] else: token_values['assignment_domain_id'] = None role_list = [] roles = token_data.get('roles') if roles is not None: for role in roles: role_list.append(role['id']) token_values['roles'] = role_list trust = token_data.get('OS-TRUST:trust') if trust is None: token_values['trust_id'] = None token_values['trustor_id'] = None token_values['trustee_id'] = None else: token_values['trust_id'] = trust['id'] token_values['trustor_id'] = trust['trustor_user']['id'] token_values['trustee_id'] = trust['trustee_user']['id'] oauth1 = token_data.get('OS-OAUTH1') if oauth1 is None: token_values['consumer_id'] = None token_values['access_token_id'] = None else: token_values['consumer_id'] = oauth1['consumer_id'] token_values['access_token_id'] = oauth1['access_token_id'] return token_values
def _add_to_revocation_list(self, data, lock): filtered_list = [] revoked_token_data = {} current_time = self._get_current_time() expires = data['expires'] if isinstance(expires, six.string_types): expires = timeutils.parse_isotime(expires) expires = timeutils.normalize_time(expires) if expires < current_time: LOG.warning(_('Token `%s` is expired, not adding to the ' 'revocation list.'), data['id']) return revoked_token_data['expires'] = timeutils.isotime(expires, subsecond=True) revoked_token_data['id'] = data['id'] token_list = self._get_key_or_default(self.revocation_key, default=[]) if not isinstance(token_list, list): # NOTE(morganfainberg): In the case that the revocation list is not # in a format we understand, reinitialize it. This is an attempt to # not allow the revocation list to be completely broken if # somehow the key is changed outside of keystone (e.g. memcache # that is shared by multiple applications). Logging occurs at error # level so that the cloud administrators have some awareness that # the revocation_list needed to be cleared out. In all, this should # be recoverable. Keystone cannot control external applications # from changing a key in some backends, however, it is possible to # gracefully handle and notify of this event. LOG.error(_('Reinitializing revocation list due to error ' 'in loading revocation list from backend. ' 'Expected `list` type got `%(type)s`. Old ' 'revocation list data: %(list)r'), {'type': type(token_list), 'list': token_list}) token_list = [] # NOTE(morganfainberg): on revocation, cleanup the expired entries, try # to keep the list of tokens revoked at the minimum. for token_data in token_list: try: expires_at = timeutils.normalize_time( timeutils.parse_isotime(token_data['expires'])) except ValueError: LOG.warning(_('Removing `%s` from revocation list due to ' 'invalid expires data in revocation list.'), token_data.get('id', 'INVALID_TOKEN_DATA')) continue if expires_at > current_time: filtered_list.append(token_data) filtered_list.append(revoked_token_data) self._set_key(self.revocation_key, filtered_list, lock)
def _request_admin_token(self): """Retrieve new token as admin user from keystone. :return token id upon success :raises ServerError when unable to communicate with keystone """ params = { 'auth': { 'passwordCredentials': { 'username': self.admin_user, 'password': self.admin_password, }, 'tenantName': self.admin_tenant_name, } } response, data = self._json_request('POST', '/v2.0/tokens', body=params) try: token = data['access']['token']['id'] expiry = data['access']['token']['expires'] assert token assert expiry datetime_expiry = timeutils.parse_isotime(expiry) return (token, timeutils.normalize_time(datetime_expiry)) except (AssertionError, KeyError): LOG.warn("Unexpected response from keystone service: %s", data) raise ServiceError('invalid json response') except (ValueError): LOG.warn("Unable to parse expiration time from token: %s", data) raise ServiceError('invalid json response')
def issue_v2_token(self, token_ref, roles_ref=None, catalog_ref=None): token_data = self.v2_token_data_helper.format_token( token_ref, roles_ref, catalog_ref) token_id = self._get_token_id(token_data) token_data['access']['token']['id'] = token_id try: expiry = token_data['access']['token']['expires'] if isinstance(expiry, basestring): expiry = timeutils.normalize_time( timeutils.parse_isotime(expiry)) data = dict(key=token_id, id=token_id, expires=expiry, user=token_ref['user'], tenant=token_ref['tenant'], metadata=token_ref['metadata'], token_data=token_data, bind=token_ref.get('bind'), trust_id=token_ref['metadata'].get('trust_id'), token_version=token.provider.V2) self.token_api.create_token(token_id, data) except Exception: exc_info = sys.exc_info() # an identical token may have been created already. # if so, return the token_data as it is also identical try: self.token_api.get_token(token_id) except exception.TokenNotFound: raise exc_info[0], exc_info[1], exc_info[2] return (token_id, token_data)
def authenticate(self, context, auth_info, auth_context): """Turn a signed request with an access key into a keystone token.""" if not self.oauth_api: raise exception.Unauthorized(_("%s not supported") % self.method) headers = context["headers"] oauth_headers = oauth.get_oauth_headers(headers) access_token_id = oauth_headers.get("oauth_token") if not access_token_id: raise exception.ValidationError(attribute="oauth_token", target="request") acc_token = self.oauth_api.get_access_token(access_token_id) expires_at = acc_token["expires_at"] if expires_at: now = timeutils.utcnow() expires = timeutils.normalize_time(timeutils.parse_isotime(expires_at)) if now > expires: raise exception.Unauthorized(_("Access token is expired")) url = controller.V3Controller.base_url(context, context["path"]) access_verifier = oauth.ResourceEndpoint( request_validator=validator.OAuthValidator(), token_generator=oauth.token_generator ) result, request = access_verifier.validate_protected_resource_request( url, http_method="POST", body=context["query_string"], headers=headers, realms=None ) if not result: msg = _("Could not validate the access token") raise exception.Unauthorized(msg) auth_context["user_id"] = acc_token["authorizing_user_id"] auth_context["access_token_id"] = access_token_id auth_context["project_id"] = acc_token["project_id"]
def _convert_user_index_from_json(self, token_list, user_key): try: # NOTE(morganfainberg): Try loading in the old format # of the list. token_list = jsonutils.loads('[%s]' % token_list) # NOTE(morganfainberg): Build a delta based upon the # token TTL configured. Since we are using the old # format index-list, we will create a "fake" expiration # that should be further in the future than the actual # expiry. To avoid locking up keystone trying to # communicate to memcached, it is better to use a fake # value. The logic that utilizes this list already # knows how to handle the case of tokens that are # no longer valid being included. delta = datetime.timedelta( seconds=CONF.token.expiration) new_expiry = timeutils.normalize_time( timeutils.utcnow()) + delta for idx, token_id in enumerate(token_list): token_list[idx] = (token_id, new_expiry) except Exception: # NOTE(morganfainberg): Catch any errors thrown here. There is # nothing the admin or operator needs to do in this case, but # it should be logged that there was an error and some action was # taken to correct it LOG.exception(_('Error converting user-token-index to new format; ' 'clearing user token index record "%s".'), user_key) token_list = [] return token_list
def issue_v2_token(self, token_ref, roles_ref=None, catalog_ref=None): token_data = self.v2_token_data_helper.format_token(token_ref, roles_ref, catalog_ref) token_id = self._get_token_id(token_data) token_data["access"]["token"]["id"] = token_id try: expiry = token_data["access"]["token"]["expires"] if isinstance(expiry, six.string_types): expiry = timeutils.normalize_time(timeutils.parse_isotime(expiry)) data = dict( key=token_id, id=token_id, expires=expiry, user=token_ref["user"], tenant=token_ref["tenant"], metadata=token_ref["metadata"], token_data=token_data, bind=token_ref.get("bind"), trust_id=token_ref["metadata"].get("trust_id"), token_version=token.provider.V2, ) self.token_api.create_token(token_id, data) except Exception: exc_info = sys.exc_info() # an identical token may have been created already. # if so, return the token_data as it is also identical try: self.token_api.get_token(token_id) except exception.TokenNotFound: raise exc_info[0], exc_info[1], exc_info[2] return (token_id, token_data)
def build_token_values(token_data): token_values = { "expires_at": timeutils.normalize_time(timeutils.parse_isotime(token_data["expires_at"])), "issued_at": timeutils.normalize_time(timeutils.parse_isotime(token_data["issued_at"])), } user = token_data.get("user") if user is not None: token_values["user_id"] = user["id"] token_values["identity_domain_id"] = user["domain"]["id"] else: token_values["user_id"] = None token_values["identity_domain_id"] = None project = token_data.get("project", token_data.get("tenant")) if project is not None: token_values["project_id"] = project["id"] token_values["assignment_domain_id"] = project["domain"]["id"] else: token_values["project_id"] = None token_values["assignment_domain_id"] = None role_list = [] roles = token_data.get("roles") if roles is not None: for role in roles: role_list.append(role["id"]) token_values["roles"] = role_list trust = token_data.get("OS-TRUST:trust") if trust is None: token_values["trust_id"] = None token_values["trustor_id"] = None token_values["trustee_id"] = None else: token_values["trust_id"] = trust["id"] token_values["trustor_id"] = trust["trustor_user"]["id"] token_values["trustee_id"] = trust["trustee_user"]["id"] oauth1 = token_data.get("OS-OAUTH1") if oauth1 is None: token_values["consumer_id"] = None token_values["access_token_id"] = None else: token_values["consumer_id"] = oauth1["consumer_id"] token_values["access_token_id"] = oauth1["access_token_id"] return token_values
def create_token(context, auth_context, auth_info): token_data_helper = TokenDataHelper(context) (domain_id, project_id, trust) = auth_info.get_scope() method_names = list(set(auth_info.get_method_names() + auth_context.get('method_names', []))) token_data = token_data_helper.get_token_data( auth_context['user_id'], method_names, auth_context['extras'], domain_id, project_id, auth_context.get('expires_at', None), trust) if CONF.signing.token_format == 'UUID': token_id = uuid.uuid4().hex elif CONF.signing.token_format == 'PKI': try: token_id = cms.cms_sign_token(json.dumps(token_data), CONF.signing.certfile, CONF.signing.keyfile) except subprocess.CalledProcessError: raise exception.UnexpectedError(_( 'Unable to sign token.')) else: raise exception.UnexpectedError(_( 'Invalid value for token_format: %s.' ' Allowed values are PKI or UUID.') % CONF.signing.token_format) token_api = token_module.Manager() try: expiry = token_data['token']['expires_at'] if isinstance(expiry, basestring): expiry = timeutils.normalize_time(timeutils.parse_isotime(expiry)) role_ids = [] if 'project' in token_data['token']: # project-scoped token, fill in the v2 token data # all we care are the role IDs role_ids = [role['id'] for role in token_data['token']['roles']] metadata_ref = {'roles': role_ids} data = dict(key=token_id, id=token_id, expires=expiry, user=token_data['token']['user'], tenant=token_data['token'].get('project'), metadata=metadata_ref, token_data=token_data, trust_id=trust['id'] if trust else None) token_api.create_token(context, token_id, data) except Exception as e: # an identical token may have been created already. # if so, return the token_data as it is also identical try: token_api.get_token(context=context, token_id=token_id) except exception.TokenNotFound: raise e return (token_id, token_data)
def _issue_v3_token(self, **kwargs): user_id = kwargs.get('user_id') method_names = kwargs.get('method_names') expires_at = kwargs.get('expires_at') project_id = kwargs.get('project_id') domain_id = kwargs.get('domain_id') auth_context = kwargs.get('auth_context') trust = kwargs.get('trust') metadata_ref = kwargs.get('metadata_ref') # for V2, trust is stashed in metadata_ref if (CONF.trust.enabled and not trust and metadata_ref and 'trust_id' in metadata_ref): trust = self.trust_api.get_trust(metadata_ref['trust_id']) token_data = self.v3_token_data_helper.get_token_data( user_id, method_names, auth_context.get('extras') if auth_context else None, domain_id=domain_id, project_id=project_id, expires=expires_at, trust=trust) token_id = self._get_token_id(token_data) try: expiry = token_data['token']['expires_at'] if isinstance(expiry, basestring): expiry = timeutils.normalize_time( timeutils.parse_isotime(expiry)) # FIXME(gyee): is there really a need to store roles in metadata? role_ids = [] metadata_ref = kwargs.get('metadata_ref', {}) if 'project' in token_data['token']: # project-scoped token, fill in the v2 token data # all we care are the role IDs role_ids = [r['id'] for r in token_data['token']['roles']] metadata_ref = {'roles': role_ids} if trust: metadata_ref.setdefault('trust_id', trust['id']) metadata_ref.setdefault('trustee_user_id', trust['trustee_user_id']) data = dict(key=token_id, id=token_id, expires=expiry, user=token_data['token']['user'], tenant=token_data['token'].get('project'), metadata=metadata_ref, token_data=token_data, trust_id=trust['id'] if trust else None) self.token_api.create_token(token_id, data) except Exception: exc_info = sys.exc_info() # an identical token may have been created already. # if so, return the token_data as it is also identical try: self.token_api.get_token(token_id) except exception.TokenNotFound: raise exc_info[0], exc_info[1], exc_info[2] return (token_id, token_data)
def build_token_values_v2(access, default_domain_id): token_data = access['token'] token_expires_at = timeutils.parse_isotime(token_data['expires']) # Trim off the microseconds because the revocation event only has # expirations accurate to the second. token_expires_at = token_expires_at.replace(microsecond=0) token_values = { 'expires_at': timeutils.normalize_time(token_expires_at), 'issued_at': timeutils.normalize_time( timeutils.parse_isotime(token_data['issued_at']))} token_values['user_id'] = access.get('user', {}).get('id') project = token_data.get('tenant') if project is not None: token_values['project_id'] = project['id'] else: token_values['project_id'] = None token_values['identity_domain_id'] = default_domain_id token_values['assignment_domain_id'] = default_domain_id trust = token_data.get('trust') if trust is None: token_values['trust_id'] = None token_values['trustor_id'] = None token_values['trustee_id'] = None else: token_values['trust_id'] = trust['id'] token_values['trustor_id'] = trust['trustor_id'] token_values['trustee_id'] = trust['trustee_id'] token_values['consumer_id'] = None token_values['access_token_id'] = None role_list = [] # Roles are by ID in metadata and by name in the user section roles = access.get('metadata', {}).get('roles', []) for role in roles: role_list.append(role) token_values['roles'] = role_list return token_values
def _list_tokens(self, user_id, tenant_id=None, trust_id=None, consumer_id=None): tokens = [] user_key = self._prefix_user_id(user_id) current_time = timeutils.normalize_time(timeutils.utcnow()) token_list = self.client.get(user_key) or [] if not isinstance(token_list, list): # NOTE(morganfainberg): This is for compatibility for old-format # token-lists that were a JSON string of just token_ids. This code # will reference the underlying expires directly from the # token_ref vs in this list, so setting to none just ensures the # loop works as expected. token_list = [(i, None) for i in jsonutils.loads('[%s]' % token_list)] for token_id, expiry in token_list: ptk = self._prefix_token_id(token_id) token_ref = self.client.get(ptk) if token_ref: if tenant_id is not None: tenant = token_ref.get('tenant') if not tenant: continue if tenant.get('id') != tenant_id: continue if trust_id is not None: trust = token_ref.get('trust_id') if not trust: continue if trust != trust_id: continue if consumer_id is not None: try: oauth = token_ref['token_data']['token']['OS-OAUTH1'] if oauth.get('consumer_id') != consumer_id: continue except KeyError: continue if (timeutils.normalize_time(token_ref['expires']) < current_time): # Skip expired tokens. continue tokens.append(token_id) return tokens
def authorize_request_token(self, context, request_token_id, roles): """An authenticated user is going to authorize a request token. As a security precaution, the requested roles must match those in the request token. Because this is in a CLI-only world at the moment, there is not another easy way to make sure the user knows which roles are being requested before authorizing. """ req_token = self.oauth_api.get_request_token(request_token_id) expires_at = req_token['expires_at'] if expires_at: now = timeutils.utcnow() expires = timeutils.normalize_time( timeutils.parse_isotime(expires_at)) if now > expires: raise exception.Unauthorized(_('Request token is expired')) # put the roles in a set for easy comparison authed_roles = set() for role in roles: authed_roles.add(role['id']) # verify the authorizing user has the roles user_token = self.token_api.get_token(context['token_id']) user_id = user_token['user'].get('id') project_id = req_token['requested_project_id'] user_roles = self.assignment_api.get_roles_for_user_and_project( user_id, project_id) cred_set = set(user_roles) if not cred_set.issuperset(authed_roles): msg = _('authorizing user does not have role required') raise exception.Unauthorized(message=msg) # create list of just the id's for the backend role_list = list(authed_roles) # verify the user has the project too req_project_id = req_token['requested_project_id'] user_projects = self.assignment_api.list_projects_for_user(user_id) found = False for user_project in user_projects: if user_project['id'] == req_project_id: found = True break if not found: msg = _("User is not a member of the requested project") raise exception.Unauthorized(message=msg) # finally authorize the token authed_token = self.oauth_api.authorize_request_token( request_token_id, user_id, role_list) to_return = {'token': {'oauth_verifier': authed_token['verifier']}} return to_return
def assertReporteEventMatchesRecorded(self, event, sample, before_time): after_time = timeutils.utcnow() event_issued_before = timeutils.normalize_time( timeutils.parse_isotime(event['issued_before'])) self.assertTrue(before_time < event_issued_before, 'invalid event issued_before time; Too early') self.assertTrue(event_issued_before < after_time, 'invalid event issued_before time; too late') del (event['issued_before']) self.assertEqual(sample, event)
def authorize_request_token(self, context, request_token_id, roles): """An authenticated user is going to authorize a request token. As a security precaution, the requested roles must match those in the request token. Because this is in a CLI-only world at the moment, there is not another easy way to make sure the user knows which roles are being requested before authorizing. """ auth_context = context.get("environment", {}).get("KEYSTONE_AUTH_CONTEXT", {}) if auth_context.get("is_delegated_auth"): raise exception.Forbidden(_("Cannot authorize a request token" " with a token issued via delegation.")) req_token = self.oauth_api.get_request_token(request_token_id) expires_at = req_token["expires_at"] if expires_at: now = timeutils.utcnow() expires = timeutils.normalize_time(timeutils.parse_isotime(expires_at)) if now > expires: raise exception.Unauthorized(_("Request token is expired")) # put the roles in a set for easy comparison authed_roles = set() for role in roles: authed_roles.add(role["id"]) # verify the authorizing user has the roles user_token = self.token_api.get_token(context["token_id"]) user_id = user_token["user"].get("id") project_id = req_token["requested_project_id"] user_roles = self.assignment_api.get_roles_for_user_and_project(user_id, project_id) cred_set = set(user_roles) if not cred_set.issuperset(authed_roles): msg = _("authorizing user does not have role required") raise exception.Unauthorized(message=msg) # create list of just the id's for the backend role_list = list(authed_roles) # verify the user has the project too req_project_id = req_token["requested_project_id"] user_projects = self.assignment_api.list_projects_for_user(user_id) for user_project in user_projects: if user_project["id"] == req_project_id: break else: msg = _("User is not a member of the requested project") raise exception.Unauthorized(message=msg) # finally authorize the token authed_token = self.oauth_api.authorize_request_token(request_token_id, user_id, role_list) to_return = {"token": {"oauth_verifier": authed_token["verifier"]}} return to_return
def authorize(self, context, request_token_id): """An authenticated user is going to authorize a request token. As a security precaution, the requested roles must match those in the request token. Because this is in a CLI-only world at the moment, there is not another easy way to make sure the user knows which roles are being requested before authorizing. """ req_token = self.oauth_api.get_request_token(request_token_id) expires_at = req_token['expires_at'] if expires_at: now = timeutils.utcnow() expires = timeutils.normalize_time( timeutils.parse_isotime(expires_at)) if now > expires: raise exception.Unauthorized(_('Request token is expired')) req_roles = req_token['requested_roles'] req_roles_list = jsonutils.loads(req_roles) req_set = set() for x in req_roles_list: req_set.add(x['id']) # verify the authorizing user has the roles user_token = self.token_api.get_token(context['token_id']) credentials = user_token['metadata'].copy() user_roles = credentials.get('roles') user_id = user_token['user'].get('id') cred_set = set(user_roles) if not cred_set.issuperset(req_set): msg = _('authorizing user does not have role required') raise exception.Unauthorized(message=msg) # verify the user has the project too req_project_id = req_token['requested_project_id'] user_projects = self.assignment_api.list_user_projects(user_id) found = False for user_project in user_projects: if user_project['id'] == req_project_id: found = True break if not found: msg = _("User is not a member of the requested project") raise exception.Unauthorized(message=msg) # finally authorize the token authed_token = self.oauth_api.authorize_request_token( request_token_id, user_id) to_return = {'token': {'oauth_verifier': authed_token['verifier']}} return to_return
def build_token_values_v2(access, default_domain_id): token_data = access["token"] token_values = { "expires_at": timeutils.normalize_time(timeutils.parse_isotime(token_data["expires"])), "issued_at": timeutils.normalize_time(timeutils.parse_isotime(token_data["issued_at"])), } token_values["user_id"] = access.get("user", {}).get("id") project = token_data.get("tenant") if project is not None: token_values["project_id"] = project["id"] else: token_values["project_id"] = None token_values["identity_domain_id"] = default_domain_id token_values["assignment_domain_id"] = default_domain_id trust = token_data.get("trust") if trust is None: token_values["trust_id"] = None token_values["trustor_id"] = None token_values["trustee_id"] = None else: token_values["trust_id"] = trust["id"] token_values["trustor_id"] = trust["trustor_id"] token_values["trustee_id"] = trust["trustee_id"] token_values["consumer_id"] = None token_values["access_token_id"] = None role_list = [] # Roles are by ID in metadata and by name in the user section roles = access.get("metadata", {}).get("roles", []) for role in roles: role_list.append(role) token_values["roles"] = role_list return token_values
def _is_valid_token(self, token): # Verify the token has not expired. current_time = timeutils.normalize_time(timeutils.utcnow()) try: # Get the data we need from the correct location (V2 and V3 tokens # differ in structure, Try V3 first, fall back to V2 second) token_data = token.get('token', token.get('access')) expires_at = token_data.get('expires_at', token_data.get('expires')) if not expires_at: expires_at = token_data['token']['expires'] expiry = timeutils.normalize_time( timeutils.parse_isotime(expires_at)) if current_time < expiry: # Token is has not expired and has not been revoked. return None except Exception: LOG.exception(_('Unexpected error or malformed token determining ' 'token expiry: %s') % token) # Token is expired, we have a malformed token, or something went wrong. raise exception.Unauthorized(_('Failed to validate token'))
def _create_token(self, token_id, token_data): try: if isinstance(token_data['expires'], six.string_types): token_data['expires'] = timeutils.normalize_time( timeutils.parse_isotime(token_data['expires'])) self.token_api.create_token(token_id, token_data) except Exception: exc_info = sys.exc_info() # an identical token may have been created already. # if so, return the token_data as it is also identical try: self.token_api.get_token(token_id) except exception.TokenNotFound: six.reraise(*exc_info)
def assertReportedEventMatchesRecorded(self, event, sample, before_time): after_time = timeutils.utcnow() event_issued_before = timeutils.normalize_time( timeutils.parse_isotime(event['issued_before'])) self.assertTrue( before_time <= event_issued_before, 'invalid event issued_before time; %s is not later than %s.' % ( timeutils.isotime(event_issued_before, subsecond=True), timeutils.isotime(before_time, subsecond=True))) self.assertTrue( event_issued_before <= after_time, 'invalid event issued_before time; %s is not earlier than %s.' % ( timeutils.isotime(event_issued_before, subsecond=True), timeutils.isotime(after_time, subsecond=True))) del (event['issued_before']) self.assertEqual(sample, event)
def authenticate(self, context, auth_info, auth_context): """Turn a signed request with an access key into a keystone token.""" headers = context['headers'] oauth_headers = oauth.get_oauth_headers(headers) consumer_id = oauth_headers.get('oauth_consumer_key') access_token_id = oauth_headers.get('oauth_token') if not access_token_id: raise exception.ValidationError( attribute='oauth_token', target='request') acc_token = self.oauth_api.get_access_token(access_token_id) consumer = self.oauth_api.get_consumer_with_secret(consumer_id) expires_at = acc_token['expires_at'] if expires_at: now = timeutils.utcnow() expires = timeutils.normalize_time( timeutils.parse_isotime(expires_at)) if now > expires: raise exception.Unauthorized(_('Access token is expired')) consumer_obj = oauth1.Consumer(key=consumer['id'], secret=consumer['secret']) acc_token_obj = oauth1.Token(key=acc_token['id'], secret=acc_token['access_secret']) url = oauth.rebuild_url(context['path']) oauth_request = oauth1.Request.from_request( http_method='POST', http_url=url, headers=context['headers'], query_string=context['query_string']) oauth_server = oauth1.Server() oauth_server.add_signature_method(oauth1.SignatureMethod_HMAC_SHA1()) params = oauth_server.verify_request(oauth_request, consumer_obj, token=acc_token_obj) if params: msg = _('There should not be any non-oauth parameters') raise exception.Unauthorized(message=msg) auth_context['user_id'] = acc_token['authorizing_user_id'] auth_context['access_token_id'] = access_token_id auth_context['project_id'] = acc_token['project_id']
def create_trust(self, trust_id, trust, roles): with sql.transaction() as session: ref = TrustModel.from_dict(trust) ref['id'] = trust_id if ref.get('expires_at') and ref['expires_at'].tzinfo is not None: ref['expires_at'] = timeutils.normalize_time(ref['expires_at']) session.add(ref) added_roles = [] for role in roles: trust_role = TrustRole() trust_role.trust_id = trust_id trust_role.role_id = role['id'] added_roles.append({'id': role['id']}) session.add(trust_role) trust_dict = ref.to_dict() trust_dict['roles'] = added_roles return trust_dict
def authenticate(self, context, auth_payload, user_context): try: if 'id' not in auth_payload: raise exception.ValidationError(attribute='id', target=METHOD_NAME) token_id = auth_payload['id'] response = self.provider.validate_token(token_id) #for V3 tokens, the essential data is under the 'token' value. #For V2, the comparable data was nested under 'access' token_ref = response.get('token', response.get('access')) #Do not allow tokens used for delegation to #create another token, or perform any changes of #state in Keystone. TO do so is to invite elevation of #privilege attacks if 'OS-TRUST:trust' in token_ref: raise exception.Forbidden() if 'trust' in token_ref: raise exception.Forbidden() if 'trust_id' in token_ref.get('metadata', {}): raise exception.Forbidden() if 'OS-OAUTH1' in token_ref: raise exception.Forbidden() wsgi.validate_token_bind(context, token_ref) #new tokens are not allowed to extend the expiration #time of an old token, otherwise, they could be extened #forever. The expiration value was stored at different #locations in v2 and v3 tokens. expires_at = token_ref.get('expires_at') if not expires_at: expires_at = token_ref.get('expires') if not expires_at: expires_at = timeutils.normalize_time( timeutils.parse_isotime(token_ref['token']['expires'])) user_context.setdefault('expires_at', expires_at) user_context.setdefault('user_id', token_ref['user']['id']) user_context['extras'].update(token_ref.get('extras', {})) user_context['method_names'].extend(token_ref.get('methods', [])) except AssertionError as e: LOG.error(e) raise exception.Unauthorized(e)
def _format_token_index_item(self, item): try: token_id, expires = item except (TypeError, ValueError): LOG.debug(('Invalid token entry expected tuple of ' '`(<token_id>, <expires>)` got: `%(item)r`'), dict(item=item)) raise try: expires = timeutils.normalize_time( timeutils.parse_isotime(expires)) except ValueError: LOG.debug(('Invalid expires time on token `%(token_id)s`:' ' %(expires)r'), dict(token_id=token_id, expires=expires)) raise return token_id, expires
def create_trust(self, trust_id, trust, roles): session = db_session.get_session() with session.begin(): ref = TrustModel.from_dict(trust) ref["id"] = trust_id if ref.get("expires_at") and ref["expires_at"].tzinfo is not None: ref["expires_at"] = timeutils.normalize_time(ref["expires_at"]) session.add(ref) added_roles = [] for role in roles: trust_role = TrustRole() trust_role.trust_id = trust_id trust_role.role_id = role["id"] added_roles.append({"id": role["id"]}) session.add(trust_role) trust_dict = ref.to_dict() trust_dict["roles"] = added_roles return trust_dict
def create_trust(self, trust_id, trust, roles): trust_ref = trust trust_ref["id"] = trust_id trust_ref["deleted"] = False trust_ref["roles"] = roles if trust_ref.get("expires_at") and trust_ref["expires_at"].tzinfo is not None: trust_ref["expires_at"] = timeutils.normalize_time(trust_ref["expires_at"]) self.db.set("trust-%s" % trust_id, trust_ref) trustee_user_id = trust_ref["trustee_user_id"] trustee_list = self.db.get("trustee-%s" % trustee_user_id, []) trustee_list.append(trust_id) self.db.set("trustee-%s" % trustee_user_id, trustee_list) trustor_user_id = trust_ref["trustor_user_id"] trustor_list = self.db.get("trustor-%s" % trustor_user_id, []) trustor_list.append(trust_id) self.db.set("trustor-%s" % trustor_user_id, trustor_list) return copy.deepcopy(trust_ref)
def create_trust(self, trust_id, trust, roles): trust_ref = trust trust_ref['id'] = trust_id trust_ref['deleted'] = False trust_ref['roles'] = roles if (trust_ref.get('expires_at') and trust_ref['expires_at'].tzinfo is not None): trust_ref['expires_at'] = (timeutils.normalize_time (trust_ref['expires_at'])) self.db.set('trust-%s' % trust_id, trust_ref) trustee_user_id = trust_ref['trustee_user_id'] trustee_list = self.db.get('trustee-%s' % trustee_user_id, []) trustee_list.append(trust_id) self.db.set('trustee-%s' % trustee_user_id, trustee_list) trustor_user_id = trust_ref['trustor_user_id'] trustor_list = self.db.get('trustor-%s' % trustor_user_id, []) trustor_list.append(trust_id) self.db.set('trustor-%s' % trustor_user_id, trustor_list) return copy.deepcopy(trust_ref)
def list_revoke_events(self, context): since = context['query_string'].get('since') last_fetch = None if since: try: last_fetch = timeutils.normalize_time( timeutils.parse_isotime(since)) except ValueError: raise exception.ValidationError( message=_('invalid date format %s') % since) events = self.revoke_api.get_events(last_fetch=last_fetch) # Build the links by hand as the standard controller calls require ids response = {'events': [event.to_dict() for event in events], 'links': { 'next': None, 'self': RevokeController.base_url( path=context['path']) + '/events', 'previous': None} } return response
def _assert_valid(self, token_id, token_ref): """Raise TokenNotFound if the token is expired.""" current_time = timeutils.normalize_time(timeutils.utcnow()) expires = token_ref.get('expires') if not expires or current_time > timeutils.normalize_time(expires): raise exception.TokenNotFound(token_id=token_id)
def build_token_values(token_data): token_expires_at = timeutils.parse_isotime(token_data['expires_at']) # Trim off the microseconds because the revocation event only has # expirations accurate to the second. token_expires_at = token_expires_at.replace(microsecond=0) token_values = { 'expires_at': timeutils.normalize_time(token_expires_at), 'issued_at': timeutils.normalize_time( timeutils.parse_isotime(token_data['issued_at'])) } user = token_data.get('user') if user is not None: token_values['user_id'] = user['id'] token_values['identity_domain_id'] = user['domain']['id'] else: token_values['user_id'] = None token_values['identity_domain_id'] = None project = token_data.get('project', token_data.get('tenant')) if project is not None: token_values['project_id'] = project['id'] token_values['assignment_domain_id'] = project['domain']['id'] else: token_values['project_id'] = None domain = token_data.get('domain') if domain is not None: token_values['assignment_domain_id'] = domain['id'] else: token_values['assignment_domain_id'] = None role_list = [] roles = token_data.get('roles') if roles is not None: for role in roles: role_list.append(role['id']) token_values['roles'] = role_list trust = token_data.get('OS-TRUST:trust') if trust is None: token_values['trust_id'] = None token_values['trustor_id'] = None token_values['trustee_id'] = None else: token_values['trust_id'] = trust['id'] token_values['trustor_id'] = trust['trustor_user']['id'] token_values['trustee_id'] = trust['trustee_user']['id'] oauth1 = token_data.get('OS-OAUTH1') if oauth1 is None: token_values['consumer_id'] = None token_values['access_token_id'] = None else: token_values['consumer_id'] = oauth1['consumer_id'] token_values['access_token_id'] = oauth1['access_token_id'] return token_values
def issue_v3_token(self, user_id, method_names, expires_at=None, project_id=None, domain_id=None, auth_context=None, trust=None, metadata_ref=None, include_catalog=True): # for V2, trust is stashed in metadata_ref if (CONF.trust.enabled and not trust and metadata_ref and 'trust_id' in metadata_ref): trust = self.trust_api.get_trust(metadata_ref['trust_id']) access_token = None if 'oauth1' in method_names: if self.oauth_api: access_token_id = auth_context['access_token_id'] access_token = self.oauth_api.get_access_token(access_token_id) else: raise exception.Forbidden(_('Oauth is disabled.')) token_data = self.v3_token_data_helper.get_token_data( user_id, method_names, auth_context.get('extras') if auth_context else None, domain_id=domain_id, project_id=project_id, expires=expires_at, trust=trust, bind=auth_context.get('bind') if auth_context else None, include_catalog=include_catalog, access_token=access_token) token_id = self._get_token_id(token_data) try: expiry = token_data['token']['expires_at'] if isinstance(expiry, six.string_types): expiry = timeutils.normalize_time( timeutils.parse_isotime(expiry)) # FIXME(gyee): is there really a need to store roles in metadata? role_ids = [] if metadata_ref is None: metadata_ref = {} if 'project' in token_data['token']: # project-scoped token, fill in the v2 token data # all we care are the role IDs role_ids = [r['id'] for r in token_data['token']['roles']] metadata_ref = {'roles': role_ids} if trust: metadata_ref.setdefault('trust_id', trust['id']) metadata_ref.setdefault('trustee_user_id', trust['trustee_user_id']) data = dict(key=token_id, id=token_id, expires=expiry, user=token_data['token']['user'], tenant=token_data['token'].get('project'), metadata=metadata_ref, token_data=token_data, trust_id=trust['id'] if trust else None, token_version=token.provider.V3) self.token_api.create_token(token_id, data) except Exception: exc_info = sys.exc_info() # an identical token may have been created already. # if so, return the token_data as it is also identical try: self.token_api.get_token(token_id) except exception.TokenNotFound: raise exc_info[0], exc_info[1], exc_info[2] return (token_id, token_data)
def _update_user_list_with_cas(self, user_key, token_id): cas_retry = 0 max_cas_retry = CONF.memcache.max_compare_and_set_retry current_time = timeutils.normalize_time( timeutils.parse_isotime(timeutils.isotime())) self.client.reset_cas() while cas_retry <= max_cas_retry: # NOTE(morganfainberg): cas or "compare and set" is a function of # memcache. It will return false if the value has changed since the # last call to client.gets(). This is the memcache supported method # of avoiding race conditions on set(). Memcache is already atomic # on the back-end and serializes operations. # # cas_retry is for tracking our iterations before we give up (in # case memcache is down or something horrible happens we don't # iterate forever trying to compare and set the new value. cas_retry += 1 record = self.client.gets(user_key) filtered_list = [] if record is not None: token_list = jsonutils.loads('[%s]' % record) for token_i in token_list: ptk = self._prefix_token_id(token_i) token_ref = self.client.get(ptk) if not token_ref: # skip tokens that do not exist in memcache continue if 'expires' in token_ref: expires_at = timeutils.normalize_time( token_ref['expires']) if expires_at < current_time: # skip tokens that are expired. continue # Add the still valid token_id to the list. filtered_list.append(jsonutils.dumps(token_i)) # Add the new token_id to the list. filtered_list.append(token_id) # Use compare-and-set (cas) to set the new value for the # token-index-list for the user-key. Cas is used to prevent race # conditions from causing the loss of valid token ids from this # list. if self.client.cas(user_key, ','.join(filtered_list)): msg = _('Successful set of token-index-list for user-key ' '"%(user_key)s", #%(count)d records') LOG.debug(msg, {'user_key': user_key, 'count': len(filtered_list)}) return filtered_list # The cas function will return true if it succeeded or false if it # failed for any reason, including memcache server being down, cas # id changed since gets() called (the data changed between when # this loop started and this point, etc. error_msg = _('Failed to set token-index-list for user-key ' '"%(user_key)s". Attempt %(cas_retry)d of ' '%(cas_retry_max)d') LOG.debug(error_msg, {'user_key': user_key, 'cas_retry': cas_retry, 'cas_retry_max': max_cas_retry}) # Exceeded the maximum retry attempts. error_msg = _('Unable to add token user list') raise exception.UnexpectedError(error_msg)
def create_access_token(self, context): headers = context['headers'] oauth_headers = oauth1.get_oauth_headers(headers) consumer_id = oauth_headers.get('oauth_consumer_key') request_token_id = oauth_headers.get('oauth_token') oauth_verifier = oauth_headers.get('oauth_verifier') if not consumer_id: raise exception.ValidationError( attribute='oauth_consumer_key', target='request') if not request_token_id: raise exception.ValidationError( attribute='oauth_token', target='request') if not oauth_verifier: raise exception.ValidationError( attribute='oauth_verifier', target='request') req_token = self.oauth_api.get_request_token( request_token_id) expires_at = req_token['expires_at'] if expires_at: now = timeutils.utcnow() expires = timeutils.normalize_time( timeutils.parse_isotime(expires_at)) if now > expires: raise exception.Unauthorized(_('Request token is expired')) url = self.base_url(context, context['path']) access_verifier = oauth1.AccessTokenEndpoint( request_validator=validator.OAuthValidator(), token_generator=oauth1.token_generator) h, b, s = access_verifier.create_access_token_response( url, http_method='POST', body=context['query_string'], headers=headers) params = oauth1.extract_non_oauth_params(b) if len(params) != 0: msg = _('There should not be any non-oauth parameters') raise exception.Unauthorized(message=msg) if req_token['consumer_id'] != consumer_id: msg = _('provided consumer key does not match stored consumer key') raise exception.Unauthorized(message=msg) if req_token['verifier'] != oauth_verifier: msg = _('provided verifier does not match stored verifier') raise exception.Unauthorized(message=msg) if req_token['id'] != request_token_id: msg = _('provided request key does not match stored request key') raise exception.Unauthorized(message=msg) if not req_token.get('authorizing_user_id'): msg = _('Request Token does not have an authorizing user id') raise exception.Unauthorized(message=msg) access_token_duration = CONF.oauth1.access_token_duration token_ref = self.oauth_api.create_access_token(request_token_id, access_token_duration) result = ('oauth_token=%(key)s&oauth_token_secret=%(secret)s' % {'key': token_ref['id'], 'secret': token_ref['access_secret']}) if CONF.oauth1.access_token_duration: expiry_bit = '&oauth_expires_at=%s' % (token_ref['expires_at']) result += expiry_bit headers = [('Content-Type', 'application/x-www-urlformencoded')] response = wsgi.render_response(result, status=(201, 'Created'), headers=headers) return response
def _get_current_time(self): return timeutils.normalize_time(timeutils.utcnow())
def _issue_v3_token(self, **kwargs): user_id = kwargs.get('user_id') method_names = kwargs.get('method_names') expires_at = kwargs.get('expires_at') project_id = kwargs.get('project_id') domain_id = kwargs.get('domain_id') auth_context = kwargs.get('auth_context') trust = kwargs.get('trust') metadata_ref = kwargs.get('metadata_ref') include_catalog = kwargs.get('include_catalog') # for V2, trust is stashed in metadata_ref if (CONF.trust.enabled and not trust and metadata_ref and 'trust_id' in metadata_ref): trust = self.trust_api.get_trust(metadata_ref['trust_id']) token_data = self.v3_token_data_helper.get_token_data( user_id, method_names, auth_context.get('extras') if auth_context else None, domain_id=domain_id, project_id=project_id, expires=expires_at, trust=trust, bind=auth_context.get('bind') if auth_context else None, include_catalog=include_catalog) token_id = self._get_token_id(token_data) try: expiry = token_data['token']['expires_at'] if isinstance(expiry, basestring): expiry = timeutils.normalize_time( timeutils.parse_isotime(expiry)) # FIXME(gyee): is there really a need to store roles in metadata? role_ids = [] metadata_ref = kwargs.get('metadata_ref', {}) if 'project' in token_data['token']: # project-scoped token, fill in the v2 token data # all we care are the role IDs role_ids = [r['id'] for r in token_data['token']['roles']] metadata_ref = {'roles': role_ids} if trust: metadata_ref.setdefault('trust_id', trust['id']) metadata_ref.setdefault('trustee_user_id', trust['trustee_user_id']) data = dict(key=token_id, id=token_id, expires=expiry, user=token_data['token']['user'], tenant=token_data['token'].get('project'), metadata=metadata_ref, token_data=token_data, trust_id=trust['id'] if trust else None) self.token_api.create_token(token_id, data) except Exception: exc_info = sys.exc_info() # an identical token may have been created already. # if so, return the token_data as it is also identical try: self.token_api.get_token(token_id) except exception.TokenNotFound: raise exc_info[0], exc_info[1], exc_info[2] return (token_id, token_data)
def create_access_token(self, context): headers = context['headers'] oauth_headers = oauth1.get_oauth_headers(headers) consumer_id = oauth_headers.get('oauth_consumer_key') request_token_id = oauth_headers.get('oauth_token') oauth_verifier = oauth_headers.get('oauth_verifier') if not consumer_id: raise exception.ValidationError( attribute='oauth_consumer_key', target='request') if not request_token_id: raise exception.ValidationError( attribute='oauth_token', target='request') if not oauth_verifier: raise exception.ValidationError( attribute='oauth_verifier', target='request') consumer = self.oauth_api.get_consumer_with_secret(consumer_id) req_token = self.oauth_api.get_request_token( request_token_id) expires_at = req_token['expires_at'] if expires_at: now = timeutils.utcnow() expires = timeutils.normalize_time( timeutils.parse_isotime(expires_at)) if now > expires: raise exception.Unauthorized(_('Request token is expired')) consumer_obj = oauth1.Consumer(key=consumer['id'], secret=consumer['secret']) req_token_obj = oauth1.Token(key=req_token['id'], secret=req_token['request_secret']) req_token_obj.set_verifier(oauth_verifier) url = oauth1.rebuild_url(context['path']) oauth_request = oauth1.Request.from_request( http_method='POST', http_url=url, headers=context['headers'], query_string=context['query_string']) oauth_server = oauth1.Server() oauth_server.add_signature_method(oauth1.SignatureMethod_HMAC_SHA1()) params = oauth_server.verify_request(oauth_request, consumer_obj, token=req_token_obj) if len(params) != 0: msg = _('There should not be any non-oauth parameters') raise exception.Unauthorized(message=msg) if req_token['consumer_id'] != consumer_id: msg = _('provided consumer key does not match stored consumer key') raise exception.Unauthorized(message=msg) if req_token['verifier'] != oauth_verifier: msg = _('provided verifier does not match stored verifier') raise exception.Unauthorized(message=msg) if req_token['id'] != request_token_id: msg = _('provided request key does not match stored request key') raise exception.Unauthorized(message=msg) if not req_token.get('authorizing_user_id'): msg = _('Request Token does not have an authorizing user id') raise exception.Unauthorized(message=msg) access_token_duration = CONF.oauth1.access_token_duration token_ref = self.oauth_api.create_access_token(request_token_id, access_token_duration) result = ('oauth_token=%(key)s&oauth_token_secret=%(secret)s' % {'key': token_ref['id'], 'secret': token_ref['access_secret']}) if CONF.oauth1.access_token_duration: expiry_bit = '&oauth_expires_at=%s' % (token_ref['expires_at']) result += expiry_bit headers = [('Content-Type', 'application/x-www-urlformencoded')] response = wsgi.render_response(result, status=(201, 'Created'), headers=headers) return response