Convert /v3/users to flask native dispatching
Convert /v3/users to use flask native dispatching. The following test changes were required: * Application Credentials did not have the plural form in the JSON Home document. The JSON Home document was corrected both in code and in tests. * Application Credentials "patch" test needed to be refactored to look for METHOD_NOT_ALLOWED instead of NOT FOUND for invalid/unimplemented methods. The "assertValidErrorResponse" method was insufficient and the test now uses the flask test_client mechanism instead. Change-Id: Iedaf405d11450b11e2d1fcdfae45ccb8eeb6f255 Partial-Bug: #1776504
This commit is contained in:
parent
f872a40290
commit
86f968163e
|
@ -32,6 +32,7 @@ from keystone.api import roles
|
|||
from keystone.api import services
|
||||
from keystone.api import system
|
||||
from keystone.api import trusts
|
||||
from keystone.api import users
|
||||
|
||||
__all__ = (
|
||||
'auth',
|
||||
|
@ -56,6 +57,7 @@ __all__ = (
|
|||
'services',
|
||||
'system',
|
||||
'trusts',
|
||||
'users',
|
||||
)
|
||||
|
||||
__apis__ = (
|
||||
|
@ -81,4 +83,5 @@ __apis__ = (
|
|||
services,
|
||||
system,
|
||||
trusts,
|
||||
users,
|
||||
)
|
||||
|
|
|
@ -19,6 +19,11 @@ import functools
|
|||
|
||||
from keystone.common import json_home
|
||||
|
||||
# OS-EC2 "extension"
|
||||
os_ec2_resource_rel_func = functools.partial(
|
||||
json_home.build_v3_extension_resource_relation,
|
||||
extension_name='OS-EC2', extension_version='1.0')
|
||||
|
||||
# OS-EP-FILTER "extension"
|
||||
os_ep_filter_resource_rel_func = functools.partial(
|
||||
json_home.build_v3_extension_resource_relation,
|
||||
|
|
|
@ -0,0 +1,723 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
# This file handles all flask-restful resources for /v3/users
|
||||
|
||||
import base64
|
||||
import os
|
||||
import uuid
|
||||
|
||||
import flask
|
||||
from oslo_log import log
|
||||
from oslo_serialization import jsonutils
|
||||
from six.moves import http_client
|
||||
from werkzeug import exceptions
|
||||
|
||||
from keystone.api._shared import json_home_relations
|
||||
from keystone.application_credential import schema as app_cred_schema
|
||||
from keystone.common import json_home
|
||||
from keystone.common import provider_api
|
||||
from keystone.common import rbac_enforcer
|
||||
from keystone.common import utils
|
||||
from keystone.common import validation
|
||||
import keystone.conf
|
||||
from keystone import exception as ks_exception
|
||||
from keystone.i18n import _
|
||||
from keystone.identity import schema
|
||||
from keystone import notifications
|
||||
from keystone.server import flask as ks_flask
|
||||
|
||||
|
||||
CRED_TYPE_EC2 = 'ec2'
|
||||
CONF = keystone.conf.CONF
|
||||
ENFORCER = rbac_enforcer.RBACEnforcer
|
||||
LOG = log.getLogger(__name__)
|
||||
PROVIDERS = provider_api.ProviderAPIs
|
||||
|
||||
ACCESS_TOKEN_ID_PARAMETER_RELATION = (
|
||||
json_home_relations.os_oauth1_parameter_rel_func(
|
||||
parameter_name='access_token_id')
|
||||
)
|
||||
|
||||
|
||||
def _convert_v3_to_ec2_credential(credential):
|
||||
# Prior to bug #1259584 fix, blob was stored unserialized
|
||||
# but it should be stored as a json string for compatibility
|
||||
# with the v3 credentials API. Fall back to the old behavior
|
||||
# for backwards compatibility with existing DB contents
|
||||
try:
|
||||
blob = jsonutils.loads(credential['blob'])
|
||||
except TypeError:
|
||||
blob = credential['blob']
|
||||
return {'user_id': credential.get('user_id'),
|
||||
'tenant_id': credential.get('project_id'),
|
||||
'access': blob.get('access'),
|
||||
'secret': blob.get('secret'),
|
||||
'trust_id': blob.get('trust_id')}
|
||||
|
||||
|
||||
def _format_token_entity(entity):
|
||||
|
||||
formatted_entity = entity.copy()
|
||||
access_token_id = formatted_entity['id']
|
||||
user_id = formatted_entity.get('authorizing_user_id', '')
|
||||
if 'role_ids' in entity:
|
||||
formatted_entity.pop('role_ids')
|
||||
if 'access_secret' in entity:
|
||||
formatted_entity.pop('access_secret')
|
||||
|
||||
url = ('/users/%(user_id)s/OS-OAUTH1/access_tokens/%(access_token_id)s'
|
||||
'/roles' % {'user_id': user_id,
|
||||
'access_token_id': access_token_id})
|
||||
|
||||
formatted_entity.setdefault('links', {})
|
||||
formatted_entity['links']['roles'] = (ks_flask.base_url(url))
|
||||
|
||||
return formatted_entity
|
||||
|
||||
|
||||
def _check_unrestricted_application_credential(token):
|
||||
if 'application_credential' in token.methods:
|
||||
if not token.application_credential['unrestricted']:
|
||||
action = _("Using method 'application_credential' is not "
|
||||
"allowed for managing additional application "
|
||||
"credentials.")
|
||||
raise ks_exception.ForbiddenAction(action=action)
|
||||
|
||||
|
||||
def _build_enforcer_target_data_owner_and_user_id_match():
|
||||
ref = {}
|
||||
if flask.request.view_args:
|
||||
credential_id = flask.request.view_args.get('credential_id')
|
||||
if credential_id is not None:
|
||||
hashed_id = utils.hash_access_key(credential_id)
|
||||
ref['credential'] = PROVIDERS.credential_api.get_credential(
|
||||
hashed_id)
|
||||
return ref
|
||||
|
||||
|
||||
def _format_role_entity(role_id):
|
||||
role = PROVIDERS.role_api.get_role(role_id)
|
||||
formatted_entity = role.copy()
|
||||
if 'description' in role:
|
||||
formatted_entity.pop('description')
|
||||
if 'enabled' in role:
|
||||
formatted_entity.pop('enabled')
|
||||
return formatted_entity
|
||||
|
||||
|
||||
class UserResource(ks_flask.ResourceBase):
|
||||
collection_key = 'users'
|
||||
member_key = 'user'
|
||||
get_member_from_driver = PROVIDERS.deferred_provider_lookup(
|
||||
api='identity_api', method='get_user')
|
||||
|
||||
def get(self, user_id=None):
|
||||
"""Get a user resource or list users.
|
||||
|
||||
GET/HEAD /v3/users
|
||||
GET/HEAD /v3/users/{user_id}
|
||||
"""
|
||||
if user_id is not None:
|
||||
return self._get_user(user_id)
|
||||
return self._list_users()
|
||||
|
||||
def _get_user(self, user_id):
|
||||
"""Get a user resource.
|
||||
|
||||
GET/HEAD /v3/users/{user_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:get_user')
|
||||
ref = PROVIDERS.identity_api.get_user(user_id)
|
||||
return self.wrap_member(ref)
|
||||
|
||||
def _list_users(self):
|
||||
"""List users.
|
||||
|
||||
GET/HEAD /v3/users
|
||||
"""
|
||||
filters = ('domain_id', 'enabled', 'idp_id', 'name', 'protocol_id',
|
||||
'unique_id', 'password_expires_at')
|
||||
hints = self.build_driver_hints(filters)
|
||||
ENFORCER.enforce_call(action='identity:list_users', filters=filters)
|
||||
domain = self._get_domain_id_for_list_request()
|
||||
refs = PROVIDERS.identity_api.list_users(
|
||||
domain_scope=domain, hints=hints)
|
||||
return self.wrap_collection(refs, hints=hints)
|
||||
|
||||
def post(self):
|
||||
"""Create a user.
|
||||
|
||||
POST /v3/users
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:create_user')
|
||||
user_data = self.request_body_json.get('user', {})
|
||||
validation.lazy_validate(schema.user_create, user_data)
|
||||
user_data = self._normalize_dict(user_data)
|
||||
user_data = self._normalize_domain_id(user_data)
|
||||
ref = PROVIDERS.identity_api.create_user(
|
||||
user_data,
|
||||
initiator=self.audit_initiator)
|
||||
return self.wrap_member(ref), http_client.CREATED
|
||||
|
||||
def patch(self, user_id):
|
||||
"""Update a user.
|
||||
|
||||
PATCH /v3/users/{user_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:update_user')
|
||||
user_data = self.request_body_json.get('user', {})
|
||||
validation.lazy_validate(schema.user_update, user_data)
|
||||
self._require_matching_id(user_data)
|
||||
ref = PROVIDERS.identity_api.update_user(
|
||||
user_id, user_data, initiator=self.audit_initiator)
|
||||
return self.wrap_member(ref)
|
||||
|
||||
def delete(self, user_id):
|
||||
"""Delete a user.
|
||||
|
||||
DELETE /v3/users/{user_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:delete_user')
|
||||
PROVIDERS.identity_api.delete_user(user_id)
|
||||
return None, http_client.NO_CONTENT
|
||||
|
||||
|
||||
class UserChangePasswordResource(ks_flask.ResourceBase):
|
||||
collection_key = '__UNUSED__'
|
||||
member_key = '__UNUSED__'
|
||||
|
||||
@ks_flask.unenforced_api
|
||||
def get(self, user_id):
|
||||
# Special case, GET is not allowed.
|
||||
raise exceptions.MethodNotAllowed(valid_methods=['POST'])
|
||||
|
||||
@ks_flask.unenforced_api
|
||||
def post(self, user_id):
|
||||
user_data = self.request_body_json.get('user', {})
|
||||
original_password = user_data.get('original_password')
|
||||
new_password = user_data.get('password')
|
||||
|
||||
# TODO(morgan): Convert this to JSON Schema validation
|
||||
if original_password is None:
|
||||
raise ks_exception.ValidationError(
|
||||
target='user',
|
||||
attribute='original_password')
|
||||
|
||||
# TODO(morgan): Convert this to JSON Schema validation
|
||||
if new_password is None:
|
||||
raise ks_exception.ValidationError(
|
||||
target='user',
|
||||
attribute='password')
|
||||
|
||||
try:
|
||||
PROVIDERS.identity_api.change_password(
|
||||
user_id=user_id,
|
||||
original_password=original_password,
|
||||
new_password=new_password,
|
||||
initiator=self.audit_initiator)
|
||||
except AssertionError as e:
|
||||
raise ks_exception.Unauthorized(
|
||||
_('Error when changing user password: %s') % e
|
||||
)
|
||||
return None, http_client.NO_CONTENT
|
||||
|
||||
|
||||
class UserProjectsResource(ks_flask.ResourceBase):
|
||||
collection_key = 'projects'
|
||||
member_key = 'project'
|
||||
get_member_from_driver = PROVIDERS.deferred_provider_lookup(
|
||||
api='resource_api', method='get_project')
|
||||
|
||||
def get(self, user_id):
|
||||
filters = ('domain_id', 'enabled', 'name')
|
||||
ENFORCER.enforce_call(action='identity:list_user_projects',
|
||||
filters=filters)
|
||||
hints = self.build_driver_hints(filters)
|
||||
refs = PROVIDERS.assignment_api.list_projects_for_user(user_id)
|
||||
return self.wrap_collection(refs, hints=hints)
|
||||
|
||||
|
||||
class UserGroupsResource(ks_flask.ResourceBase):
|
||||
collection_key = 'groups'
|
||||
member_key = 'group'
|
||||
get_member_from_driver = PROVIDERS.deferred_provider_lookup(
|
||||
api='identity_api', method='get_group')
|
||||
|
||||
@staticmethod
|
||||
def _built_target_attr_enforcement():
|
||||
ref = {}
|
||||
if flask.request.view_args:
|
||||
ref['user'] = PROVIDERS.identity_api.get_user(
|
||||
flask.request.view_args.get('user_id'))
|
||||
return ref
|
||||
|
||||
def get(self, user_id):
|
||||
"""Get groups for a user.
|
||||
|
||||
GET/HEAD /v3/users/{user_id}/groups
|
||||
"""
|
||||
filters = ('name',)
|
||||
hints = self.build_driver_hints(filters)
|
||||
ENFORCER.enforce_call(action='identity:list_groups_for_user',
|
||||
build_target=self._built_target_attr_enforcement,
|
||||
filters=filters)
|
||||
refs = PROVIDERS.identity_api.list_groups_for_user(user_id=user_id,
|
||||
hints=hints)
|
||||
return self.wrap_collection(refs, hints=hints)
|
||||
|
||||
|
||||
class _UserOSEC2CredBaseResource(ks_flask.ResourceBase):
|
||||
collection_key = 'credentials'
|
||||
member_key = 'credential'
|
||||
|
||||
@classmethod
|
||||
def _add_self_referential_link(cls, ref, collection_name=None):
|
||||
# NOTE(morgan): This should be refactored to have an EC2 Cred API with
|
||||
# a sane prefix instead of overloading the "_add_self_referential_link"
|
||||
# method. This was chosen as it more closely mirrors the pre-flask
|
||||
# code (for transition).
|
||||
path = '/users/%(user_id)s/credentials/OS-EC2/%(credential_id)s'
|
||||
|
||||
url = ks_flask.base_url(path) % {
|
||||
'user_id': ref['user_id'],
|
||||
'credential_id': ref['access']}
|
||||
ref.setdefault('links', {})
|
||||
ref['links']['self'] = url
|
||||
|
||||
|
||||
class UserOSEC2CredentialsResourceListCreate(_UserOSEC2CredBaseResource):
|
||||
def get(self, user_id):
|
||||
"""List EC2 Credentials for user.
|
||||
|
||||
GET/HEAD /v3/users/{user_id}/credentials/OS-EC2
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:ec2_list_credentials')
|
||||
PROVIDERS.identity_api.get_user(user_id)
|
||||
credential_refs = PROVIDERS.credential_api.list_credentials_for_user(
|
||||
user_id, type=CRED_TYPE_EC2)
|
||||
collection_refs = [
|
||||
_convert_v3_to_ec2_credential(cred)
|
||||
for cred in credential_refs
|
||||
]
|
||||
return self.wrap_collection(collection_refs)
|
||||
|
||||
def post(self, user_id):
|
||||
"""Create EC2 Credential for user.
|
||||
|
||||
POST /v3/users/{user_id}/credentials/OS-EC2
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:ec2_create_credential')
|
||||
PROVIDERS.identity_api.get_user(user_id)
|
||||
tenant_id = self.request_body_json.get('tenant_id')
|
||||
PROVIDERS.resource_api.get_project(tenant_id)
|
||||
blob = dict(
|
||||
access=uuid.uuid4().hex,
|
||||
secret=uuid.uuid4().hex,
|
||||
trust_id=self.oslo_context.trust_id
|
||||
)
|
||||
credential_id = utils.hash_access_key(blob['access'])
|
||||
cred_data = dict(
|
||||
user_id=user_id,
|
||||
project_id=tenant_id,
|
||||
blob=jsonutils.dumps(blob),
|
||||
id=credential_id,
|
||||
type=CRED_TYPE_EC2
|
||||
)
|
||||
PROVIDERS.credential_api.create_credential(credential_id, cred_data)
|
||||
ref = _convert_v3_to_ec2_credential(cred_data)
|
||||
return self.wrap_member(ref), http_client.CREATED
|
||||
|
||||
|
||||
class UserOSEC2CredentialsResourceGetDelete(_UserOSEC2CredBaseResource):
|
||||
@staticmethod
|
||||
def _get_cred_data(credential_id):
|
||||
cred = PROVIDERS.credential_api.get_credential(credential_id)
|
||||
if not cred or cred['type'] != CRED_TYPE_EC2:
|
||||
raise ks_exception.Unauthorized(
|
||||
message=_('EC2 access key not found.'))
|
||||
return _convert_v3_to_ec2_credential(cred)
|
||||
|
||||
def get(self, user_id, credential_id):
|
||||
"""Get a specific EC2 credential.
|
||||
|
||||
GET/HEAD /users/{user_id}/credentials/OS-EC2/{credential_id}
|
||||
"""
|
||||
func = _build_enforcer_target_data_owner_and_user_id_match
|
||||
ENFORCER.enforce_call(
|
||||
action='identity:ec2_get_credential',
|
||||
build_target=func)
|
||||
PROVIDERS.identity_api.get_user(user_id)
|
||||
ec2_cred_id = utils.hash_access_key(credential_id)
|
||||
cred_data = self._get_cred_data(ec2_cred_id)
|
||||
return self.wrap_member(cred_data)
|
||||
|
||||
def delete(self, user_id, credential_id):
|
||||
"""Delete a specific EC2 credential.
|
||||
|
||||
DELETE /users/{user_id}/credentials/OS-EC2/{credential_id}
|
||||
"""
|
||||
func = _build_enforcer_target_data_owner_and_user_id_match
|
||||
ENFORCER.enforce_call(action='identity:ec2_delete_credential',
|
||||
build_target=func)
|
||||
PROVIDERS.identity_api.get_user(user_id)
|
||||
ec2_cred_id = utils.hash_access_key(credential_id)
|
||||
self._get_cred_data(ec2_cred_id)
|
||||
PROVIDERS.credential_api.delete_credential(ec2_cred_id)
|
||||
return None, http_client.NO_CONTENT
|
||||
|
||||
|
||||
class _OAuth1ResourceBase(ks_flask.ResourceBase):
|
||||
collection_key = 'access_tokens'
|
||||
member_key = 'access_token'
|
||||
|
||||
@classmethod
|
||||
def _add_self_referential_link(cls, ref, collection_name=None):
|
||||
# NOTE(morgan): This should be refactored to have an OAuth1 API with
|
||||
# a sane prefix instead of overloading the "_add_self_referential_link"
|
||||
# method. This was chosen as it more closely mirrors the pre-flask
|
||||
# code (for transition).
|
||||
ref.setdefault('links', {})
|
||||
path = '/users/%(user_id)s/OS-OAUTH1/access_tokens' % {
|
||||
'user_id': ref.get('authorizing_user_id', '')
|
||||
}
|
||||
ref['links']['self'] = ks_flask.base_url(path) + '/' + ref['id']
|
||||
|
||||
|
||||
class OAuth1ListAccessTokensResource(_OAuth1ResourceBase):
|
||||
def get(self, user_id):
|
||||
"""List OAuth1 Access Tokens for user.
|
||||
|
||||
GET /v3/users/{user_id}/OS=OAUTH1/access_tokens
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:list_access_tokens')
|
||||
if self.oslo_context.is_delegated_auth:
|
||||
raise ks_exception.Forbidden(
|
||||
_('Cannot list request tokens with a token '
|
||||
'issued via delegation.'))
|
||||
refs = PROVIDERS.oauth_api.list_access_tokens(user_id)
|
||||
formatted_refs = ([_format_token_entity(x) for x in refs])
|
||||
return self.wrap_collection(formatted_refs)
|
||||
|
||||
|
||||
class OAuth1AccessTokenCRUDResource(_OAuth1ResourceBase):
|
||||
def get(self, user_id, access_token_id):
|
||||
"""Get specific access token.
|
||||
|
||||
GET/HEAD /v3/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:get_access_token')
|
||||
access_token = PROVIDERS.oauth_api.get_access_token(access_token_id)
|
||||
if access_token['authorizing_user_id'] != user_id:
|
||||
raise ks_exception.NotFound()
|
||||
access_token = _format_token_entity(access_token)
|
||||
return self.wrap_member(access_token)
|
||||
|
||||
def delete(self, user_id, access_token_id):
|
||||
"""Delete specific access token.
|
||||
|
||||
DELETE /v3/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(
|
||||
action='identity:ec2_delete_credential',
|
||||
build_target=_build_enforcer_target_data_owner_and_user_id_match)
|
||||
access_token = PROVIDERS.oauth_api.get_access_token(access_token_id)
|
||||
reason = (
|
||||
'Invalidating the token cache because an access token for '
|
||||
'consumer %(consumer_id)s has been deleted. Authorization for '
|
||||
'users with OAuth tokens will be recalculated and enforced '
|
||||
'accordingly the next time they authenticate or validate a '
|
||||
'token.' % {'consumer_id': access_token['consumer_id']}
|
||||
)
|
||||
notifications.invalidate_token_cache_notification(reason)
|
||||
PROVIDERS.oauth_api.delete_access_token(
|
||||
user_id, access_token_id, initiator=self.audit_initiator)
|
||||
return None, http_client.NO_CONTENT
|
||||
|
||||
|
||||
class OAuth1AccessTokenRoleListResource(ks_flask.ResourceBase):
|
||||
collection_key = 'roles'
|
||||
member_key = 'role'
|
||||
|
||||
def get(self, user_id, access_token_id):
|
||||
"""List roles for a user access token.
|
||||
|
||||
GET/HEAD /v3/users/{user_id}/OS-OAUTH1/access_tokens/
|
||||
{access_token_id}/roles
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:list_access_token_roles')
|
||||
access_token = PROVIDERS.oauth_api.get_access_token(access_token_id)
|
||||
if access_token['authorizing_user_id'] != user_id:
|
||||
raise ks_exception.NotFound()
|
||||
authed_role_ids = access_token['role_ids']
|
||||
authed_role_ids = jsonutils.loads(authed_role_ids)
|
||||
refs = ([_format_role_entity(x) for x in authed_role_ids])
|
||||
return self.wrap_collection(refs)
|
||||
|
||||
|
||||
class OAuth1AccessTokenRoleResource(ks_flask.ResourceBase):
|
||||
collection_key = 'roles'
|
||||
member_key = 'role'
|
||||
|
||||
def get(self, user_id, access_token_id, role_id):
|
||||
"""Get role for access token.
|
||||
|
||||
GET/HEAD /v3/users/{user_id}/OS-OAUTH1/access_tokens/
|
||||
{access_token_id}/roles/{role_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:get_access_token_role')
|
||||
access_token = PROVIDERS.oauth_api.get_access_token(access_token_id)
|
||||
if access_token['authorizing_user_id'] != user_id:
|
||||
raise ks_exception.Unauthorized(_('User IDs do not match'))
|
||||
authed_role_ids = access_token['role_ids']
|
||||
authed_role_ids = jsonutils.loads(authed_role_ids)
|
||||
for authed_role_id in authed_role_ids:
|
||||
if authed_role_id == role_id:
|
||||
role = _format_role_entity(role_id)
|
||||
return self.wrap_member(role)
|
||||
raise ks_exception.RoleNotFound(role_id=role_id)
|
||||
|
||||
|
||||
class UserAppCredListCreateResource(ks_flask.ResourceBase):
|
||||
collection_key = 'application_credentials'
|
||||
member_key = 'application_credential'
|
||||
_public_parameters = frozenset([
|
||||
'id',
|
||||
'name',
|
||||
'description',
|
||||
'expires_at',
|
||||
'project_id',
|
||||
'roles',
|
||||
# secret is only exposed after create, it is not stored
|
||||
'secret',
|
||||
'links',
|
||||
'unrestricted'
|
||||
])
|
||||
|
||||
@staticmethod
|
||||
def _generate_secret():
|
||||
length = 64
|
||||
secret = os.urandom(length)
|
||||
secret = base64.urlsafe_b64encode(secret)
|
||||
secret = secret.rstrip(b'=')
|
||||
secret = secret.decode('utf-8')
|
||||
return secret
|
||||
|
||||
@staticmethod
|
||||
def _normalize_role_list(app_cred_roles):
|
||||
roles = []
|
||||
for role in app_cred_roles:
|
||||
if role.get('id'):
|
||||
roles.append(role)
|
||||
else:
|
||||
roles.append(PROVIDERS.role_api.get_unique_role_by_name(
|
||||
role['name']))
|
||||
return roles
|
||||
|
||||
def get(self, user_id):
|
||||
"""List application credentials for user.
|
||||
|
||||
GET/HEAD /v3/users/{user_id}/application_credentials
|
||||
"""
|
||||
filters = ('name',)
|
||||
ENFORCER.enforce_call(action='identity:list_application_credentials',
|
||||
filters=filters)
|
||||
app_cred_api = PROVIDERS.application_credential_api
|
||||
hints = self.build_driver_hints(filters)
|
||||
refs = app_cred_api.list_application_credentials(user_id, hints=hints)
|
||||
return self.wrap_collection(refs, hints=hints)
|
||||
|
||||
def post(self, user_id):
|
||||
"""Create application credential.
|
||||
|
||||
POST /v3/users/{user_id}/application_credentials
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:create_application_credential')
|
||||
app_cred_data = self.request_body_json.get(
|
||||
'application_credential', {})
|
||||
validation.lazy_validate(app_cred_schema.application_credential_create,
|
||||
app_cred_data)
|
||||
token = self.auth_context['token']
|
||||
_check_unrestricted_application_credential(token)
|
||||
if self.oslo_context.user_id != user_id:
|
||||
action = _('Cannot create an application credential for another '
|
||||
'user.')
|
||||
raise ks_exception.ForbiddenAction(action=action)
|
||||
project_id = self.oslo_context.project_id
|
||||
app_cred_data = self._assign_unique_id(app_cred_data)
|
||||
if not app_cred_data.get('secret'):
|
||||
app_cred_data['secret'] = self._generate_secret()
|
||||
app_cred_data['user_id'] = user_id
|
||||
app_cred_data['project_id'] = project_id
|
||||
app_cred_data['roles'] = self._normalize_role_list(
|
||||
app_cred_data.get('roles', token.roles))
|
||||
if app_cred_data.get('expires_at'):
|
||||
app_cred_data['expires_at'] = utils.parse_expiration_date(
|
||||
app_cred_data['expires_at'])
|
||||
app_cred_data = self._normalize_dict(app_cred_data)
|
||||
app_cred_api = PROVIDERS.application_credential_api
|
||||
|
||||
try:
|
||||
ref = app_cred_api.create_application_credential(
|
||||
app_cred_data, initiator=self.audit_initiator)
|
||||
except ks_exception.RoleAssignmentNotFound as e:
|
||||
# Raise a Bad Request, not a Not Found, in accordance with the
|
||||
# API-SIG recommendations:
|
||||
# https://specs.openstack.org/openstack/api-wg/guidelines/http.html#failure-code-clarifications
|
||||
raise ks_exception.ApplicationCredentialValidationError(
|
||||
detail=str(e))
|
||||
return self.wrap_member(ref), http_client.CREATED
|
||||
|
||||
|
||||
class UserAppCredGetDeleteResource(ks_flask.ResourceBase):
|
||||
collection_key = 'application_credentials'
|
||||
member_key = 'application_credential'
|
||||
|
||||
def get(self, user_id, application_credential_id):
|
||||
"""Get application credential resource.
|
||||
|
||||
GET/HEAD /v3/users/{user_id}/application_credentials/
|
||||
{application_credential_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:get_application_credential')
|
||||
ref = PROVIDERS.application_credential_api.get_application_credential(
|
||||
application_credential_id)
|
||||
return self.wrap_member(ref)
|
||||
|
||||
def delete(self, user_id, application_credential_id):
|
||||
"""Delete application credential resource.
|
||||
|
||||
DELETE /v3/users/{user_id}/application_credentials/
|
||||
{application_credential_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:delete_application_credential')
|
||||
token = self.auth_context['token']
|
||||
_check_unrestricted_application_credential(token)
|
||||
PROVIDERS.application_credential_api.delete_application_credential(
|
||||
application_credential_id, initiator=self.audit_initiator)
|
||||
return None, http_client.NO_CONTENT
|
||||
|
||||
|
||||
class UserAPI(ks_flask.APIBase):
|
||||
_name = 'users'
|
||||
_import_name = __name__
|
||||
resources = [UserResource]
|
||||
resource_mapping = [
|
||||
ks_flask.construct_resource_map(
|
||||
resource=UserChangePasswordResource,
|
||||
url='/users/<string:user_id>/password',
|
||||
resource_kwargs={},
|
||||
rel='user_change_password',
|
||||
path_vars={'user_id': json_home.Parameters.USER_ID}
|
||||
),
|
||||
ks_flask.construct_resource_map(
|
||||
resource=UserGroupsResource,
|
||||
url='/users/<string:user_id>/groups',
|
||||
resource_kwargs={},
|
||||
rel='user_groups',
|
||||
path_vars={'user_id': json_home.Parameters.USER_ID}
|
||||
),
|
||||
ks_flask.construct_resource_map(
|
||||
resource=UserProjectsResource,
|
||||
url='/users/<string:user_id>/projects',
|
||||
resource_kwargs={},
|
||||
rel='user_projects',
|
||||
path_vars={'user_id': json_home.Parameters.USER_ID}
|
||||
),
|
||||
ks_flask.construct_resource_map(
|
||||
resource=UserOSEC2CredentialsResourceListCreate,
|
||||
url='/users/<string:user_id>/credentials/OS-EC2',
|
||||
resource_kwargs={},
|
||||
rel='user_credentials',
|
||||
resource_relation_func=(
|
||||
json_home_relations.os_ec2_resource_rel_func),
|
||||
path_vars={'user_id': json_home.Parameters.USER_ID}
|
||||
),
|
||||
ks_flask.construct_resource_map(
|
||||
resource=UserOSEC2CredentialsResourceGetDelete,
|
||||
url=('/users/<string:user_id>/credentials/OS-EC2/'
|
||||
'<string:credential_id>'),
|
||||
resource_kwargs={},
|
||||
rel='user_credential',
|
||||
resource_relation_func=(
|
||||
json_home_relations.os_ec2_resource_rel_func),
|
||||
path_vars={
|
||||
'credential_id': json_home.build_v3_parameter_relation(
|
||||
'credential_id'),
|
||||
'user_id': json_home.Parameters.USER_ID}
|
||||
),
|
||||
ks_flask.construct_resource_map(
|
||||
resource=OAuth1ListAccessTokensResource,
|
||||
url='/users/<string:user_id>/OS-OAUTH1/access_tokens',
|
||||
resource_kwargs={},
|
||||
rel='user_access_tokens',
|
||||
resource_relation_func=(
|
||||
json_home_relations.os_oauth1_resource_rel_func),
|
||||
path_vars={'user_id': json_home.Parameters.USER_ID}
|
||||
),
|
||||
ks_flask.construct_resource_map(
|
||||
resource=OAuth1AccessTokenCRUDResource,
|
||||
url=('/users/<string:user_id>/OS-OAUTH1/'
|
||||
'access_tokens/<string:access_token_id>'),
|
||||
resource_kwargs={},
|
||||
rel='user_access_token',
|
||||
resource_relation_func=(
|
||||
json_home_relations.os_oauth1_resource_rel_func),
|
||||
path_vars={
|
||||
'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
|
||||
'user_id': json_home.Parameters.USER_ID}
|
||||
),
|
||||
ks_flask.construct_resource_map(
|
||||
resource=OAuth1AccessTokenRoleListResource,
|
||||
url=('/users/<string:user_id>/OS-OAUTH1/access_tokens/'
|
||||
'<string:access_token_id>/roles'),
|
||||
resource_kwargs={},
|
||||
rel='user_access_token_roles',
|
||||
resource_relation_func=(
|
||||
json_home_relations.os_oauth1_resource_rel_func),
|
||||
path_vars={'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
|
||||
'user_id': json_home.Parameters.USER_ID}
|
||||
),
|
||||
ks_flask.construct_resource_map(
|
||||
resource=OAuth1AccessTokenRoleResource,
|
||||
url=('/users/<string:user_id>/OS-OAUTH1/access_tokens/'
|
||||
'<string:access_token_id>/roles/<string:role_id>'),
|
||||
resource_kwargs={},
|
||||
rel='user_access_token_role',
|
||||
resource_relation_func=(
|
||||
json_home_relations.os_oauth1_resource_rel_func),
|
||||
path_vars={'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
|
||||
'role_id': json_home.Parameters.ROLE_ID,
|
||||
'user_id': json_home.Parameters.USER_ID}
|
||||
),
|
||||
ks_flask.construct_resource_map(
|
||||
resource=UserAppCredListCreateResource,
|
||||
url='/users/<string:user_id>/application_credentials',
|
||||
resource_kwargs={},
|
||||
rel='application_credentials',
|
||||
path_vars={'user_id': json_home.Parameters.USER_ID}
|
||||
),
|
||||
ks_flask.construct_resource_map(
|
||||
resource=UserAppCredGetDeleteResource,
|
||||
url=('/users/<string:user_id>/application_credentials/'
|
||||
'<string:application_credential_id>'),
|
||||
resource_kwargs={},
|
||||
rel='application_credential',
|
||||
path_vars={
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
'application_credential_id':
|
||||
json_home.Parameters.APPLICATION_CRED_ID}
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
APIs = (UserAPI,)
|
|
@ -10,5 +10,4 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystone.application_credential import controllers # noqa
|
||||
from keystone.application_credential.core import * # noqa
|
||||
|
|
|
@ -1,153 +0,0 @@
|
|||
# Copyright 2018 SUSE Linux GmbH
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Workflow Logic the Application Credential service."""
|
||||
|
||||
import base64
|
||||
import os
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
from keystone.application_credential import schema
|
||||
from keystone.common import controller
|
||||
from keystone.common import provider_api
|
||||
from keystone.common import utils
|
||||
from keystone.common import validation
|
||||
import keystone.conf
|
||||
from keystone import exception
|
||||
from keystone.i18n import _
|
||||
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
PROVIDERS = provider_api.ProviderAPIs
|
||||
|
||||
|
||||
class ApplicationCredentialV3(controller.V3Controller):
|
||||
collection_name = 'application_credentials'
|
||||
member_name = 'application_credential'
|
||||
_public_parameters = frozenset([
|
||||
'id',
|
||||
'name',
|
||||
'description',
|
||||
'expires_at',
|
||||
'project_id',
|
||||
'roles',
|
||||
# secret is only exposed after create, it is not stored
|
||||
'secret',
|
||||
'links',
|
||||
'unrestricted'
|
||||
])
|
||||
|
||||
def _normalize_role_list(self, app_cred_roles):
|
||||
roles = []
|
||||
for role in app_cred_roles:
|
||||
if role.get('id'):
|
||||
roles.append(role)
|
||||
else:
|
||||
roles.append(PROVIDERS.role_api.get_unique_role_by_name(
|
||||
role['name']))
|
||||
return roles
|
||||
|
||||
def _generate_secret(self):
|
||||
length = 64
|
||||
secret = os.urandom(length)
|
||||
secret = base64.urlsafe_b64encode(secret)
|
||||
secret = secret.rstrip(b'=')
|
||||
secret = secret.decode('utf-8')
|
||||
return secret
|
||||
|
||||
@classmethod
|
||||
def _add_self_referential_link(cls, context, ref):
|
||||
path = ('/users/%(user_id)s/application_credentials') % {
|
||||
'user_id': ref['user_id']}
|
||||
ref.setdefault('links', {})
|
||||
ref['links']['self'] = cls.base_url(
|
||||
context, path=path) + '/' + ref['id']
|
||||
return ref
|
||||
|
||||
@classmethod
|
||||
def wrap_member(cls, context, ref):
|
||||
cls._add_self_referential_link(context, ref)
|
||||
ref = cls.filter_params(ref)
|
||||
return {cls.member_name: ref}
|
||||
|
||||
def _check_unrestricted(self, token):
|
||||
if 'application_credential' in token.methods:
|
||||
if not token.application_credential['unrestricted']:
|
||||
action = _("Using method 'application_credential' is not "
|
||||
"allowed for managing additional application "
|
||||
"credentials.")
|
||||
raise exception.ForbiddenAction(action=action)
|
||||
|
||||
@controller.protected()
|
||||
def create_application_credential(self, request, user_id,
|
||||
application_credential):
|
||||
validation.lazy_validate(schema.application_credential_create,
|
||||
application_credential)
|
||||
|
||||
token = request.auth_context['token']
|
||||
self._check_unrestricted(token)
|
||||
if request.context.user_id != user_id:
|
||||
action = _("Cannot create an application credential for another "
|
||||
"user")
|
||||
raise exception.ForbiddenAction(action=action)
|
||||
project_id = request.context.project_id
|
||||
app_cred = self._assign_unique_id(application_credential)
|
||||
if not app_cred.get('secret'):
|
||||
app_cred['secret'] = self._generate_secret()
|
||||
app_cred['user_id'] = user_id
|
||||
app_cred['project_id'] = project_id
|
||||
app_cred['roles'] = self._normalize_role_list(
|
||||
app_cred.get('roles', token.roles))
|
||||
if app_cred.get('expires_at'):
|
||||
app_cred['expires_at'] = utils.parse_expiration_date(
|
||||
app_cred['expires_at'])
|
||||
app_cred = self._normalize_dict(app_cred)
|
||||
app_cred_api = PROVIDERS.application_credential_api
|
||||
try:
|
||||
ref = app_cred_api.create_application_credential(
|
||||
app_cred, initiator=request.audit_initiator
|
||||
)
|
||||
except exception.RoleAssignmentNotFound as e:
|
||||
# Raise a Bad Request, not a Not Found, in accordance with the
|
||||
# API-SIG recommendations:
|
||||
# https://specs.openstack.org/openstack/api-wg/guidelines/http.html#failure-code-clarifications
|
||||
raise exception.ApplicationCredentialValidationError(
|
||||
detail=str(e))
|
||||
return ApplicationCredentialV3.wrap_member(request.context_dict, ref)
|
||||
|
||||
@controller.filterprotected('name')
|
||||
def list_application_credentials(self, request, filters, user_id):
|
||||
app_cred_api = PROVIDERS.application_credential_api
|
||||
hints = ApplicationCredentialV3.build_driver_hints(request, filters)
|
||||
refs = app_cred_api.list_application_credentials(user_id, hints=hints)
|
||||
return ApplicationCredentialV3.wrap_collection(request.context_dict,
|
||||
refs)
|
||||
|
||||
@controller.protected()
|
||||
def get_application_credential(self, request, user_id,
|
||||
application_credential_id):
|
||||
ref = PROVIDERS.application_credential_api.get_application_credential(
|
||||
application_credential_id)
|
||||
return ApplicationCredentialV3.wrap_member(request.context_dict, ref)
|
||||
|
||||
@controller.protected()
|
||||
def delete_application_credential(self, request, user_id,
|
||||
application_credential_id):
|
||||
token = request.auth_context['token']
|
||||
self._check_unrestricted(token)
|
||||
PROVIDERS.application_credential_api.delete_application_credential(
|
||||
application_credential_id, initiator=request.audit_initiator
|
||||
)
|
|
@ -1,55 +0,0 @@
|
|||
# Copyright 2018 SUSE Linux GmbH
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""WSGI Routers for the Application Credential service."""
|
||||
|
||||
from keystone.application_credential import controllers
|
||||
from keystone.common import json_home
|
||||
from keystone.common import wsgi
|
||||
|
||||
APP_CRED_RESOURCE_RELATION = json_home.build_v3_resource_relation(
|
||||
'application_credential')
|
||||
APP_CRED_PARAMETER_RELATION = json_home.build_v3_parameter_relation(
|
||||
'application_credential_id')
|
||||
APP_CRED_COLLECTION_PATH = '/users/{user_id}/application_credentials'
|
||||
APP_CRED_RESOURCE_PATH = (
|
||||
'/users/{user_id}/application_credentials/{application_credential_id}'
|
||||
)
|
||||
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
_path_prefixes = (APP_CRED_COLLECTION_PATH, 'users',)
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
app_cred_controller = controllers.ApplicationCredentialV3()
|
||||
|
||||
self._add_resource(
|
||||
mapper, app_cred_controller,
|
||||
path=APP_CRED_COLLECTION_PATH,
|
||||
get_head_action='list_application_credentials',
|
||||
post_action='create_application_credential',
|
||||
rel=APP_CRED_RESOURCE_RELATION,
|
||||
path_vars={
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
|
||||
self._add_resource(
|
||||
mapper, app_cred_controller,
|
||||
path=APP_CRED_RESOURCE_PATH,
|
||||
get_head_action='get_application_credential',
|
||||
delete_action='delete_application_credential',
|
||||
rel=APP_CRED_RESOURCE_RELATION,
|
||||
path_vars={
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
'application_credential_id': APP_CRED_PARAMETER_RELATION,
|
||||
})
|
|
@ -31,25 +31,6 @@ LOG = log.getLogger(__name__)
|
|||
PROVIDERS = provider_api.ProviderAPIs
|
||||
|
||||
|
||||
class ProjectAssignmentV3(controller.V3Controller):
|
||||
"""The V3 Project APIs that are processing assignments."""
|
||||
|
||||
collection_name = 'projects'
|
||||
member_name = 'project'
|
||||
|
||||
def __init__(self):
|
||||
super(ProjectAssignmentV3, self).__init__()
|
||||
self.get_member_from_driver = PROVIDERS.resource_api.get_project
|
||||
|
||||
@controller.filterprotected('domain_id', 'enabled', 'name')
|
||||
def list_user_projects(self, request, filters, user_id):
|
||||
hints = ProjectAssignmentV3.build_driver_hints(request, filters)
|
||||
refs = PROVIDERS.assignment_api.list_projects_for_user(user_id)
|
||||
return ProjectAssignmentV3.wrap_collection(request.context_dict,
|
||||
refs,
|
||||
hints=hints)
|
||||
|
||||
|
||||
class GrantAssignmentV3(controller.V3Controller):
|
||||
"""The V3 Grant Assignment APIs."""
|
||||
|
||||
|
|
|
@ -20,31 +20,12 @@ from keystone.common import json_home
|
|||
from keystone.common import wsgi
|
||||
|
||||
|
||||
class Public(wsgi.ComposableRouter):
|
||||
def add_routes(self, mapper):
|
||||
tenant_controller = controllers.TenantAssignment()
|
||||
mapper.connect('/tenants',
|
||||
controller=tenant_controller,
|
||||
action='get_projects_for_token',
|
||||
conditions=dict(method=['GET']))
|
||||
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
_path_prefixes = ('users', 'projects')
|
||||
_path_prefixes = ('projects',)
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
|
||||
project_controller = controllers.ProjectAssignmentV3()
|
||||
self._add_resource(
|
||||
mapper, project_controller,
|
||||
path='/users/{user_id}/projects',
|
||||
get_head_action='list_user_projects',
|
||||
rel=json_home.build_v3_resource_relation('user_projects'),
|
||||
path_vars={
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
|
||||
grant_controller = controllers.GrantAssignmentV3()
|
||||
self._add_resource(
|
||||
mapper, grant_controller,
|
||||
|
|
|
@ -57,6 +57,8 @@ class Parameters(object):
|
|||
TAG_VALUE = build_v3_parameter_relation('tag_value')
|
||||
REGISTERED_LIMIT_ID = build_v3_parameter_relation('registered_limit_id')
|
||||
LIMIT_ID = build_v3_parameter_relation('limit_id')
|
||||
APPLICATION_CRED_ID = build_v3_parameter_relation(
|
||||
'application_credential_id')
|
||||
|
||||
|
||||
class Status(object):
|
||||
|
|
|
@ -34,7 +34,6 @@ Glance to list images needed to perform the requested task.
|
|||
|
||||
import abc
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
from keystoneclient.contrib.ec2 import utils as ec2_utils
|
||||
from oslo_serialization import jsonutils
|
||||
|
@ -156,70 +155,6 @@ class Ec2ControllerCommon(provider_api.ProviderAPIMixin, object):
|
|||
|
||||
return user_ref, tenant_ref, roles_ref
|
||||
|
||||
def create_credential(self, request, user_id, tenant_id):
|
||||
"""Create a secret/access pair for use with ec2 style auth.
|
||||
|
||||
Generates a new set of credentials that map the user/tenant
|
||||
pair.
|
||||
|
||||
:param request: current request
|
||||
:param user_id: id of user
|
||||
:param tenant_id: id of tenant
|
||||
:returns: credential: dict of ec2 credential
|
||||
"""
|
||||
self.identity_api.get_user(user_id)
|
||||
self.resource_api.get_project(tenant_id)
|
||||
blob = {'access': uuid.uuid4().hex,
|
||||
'secret': uuid.uuid4().hex,
|
||||
'trust_id': request.context.trust_id}
|
||||
credential_id = utils.hash_access_key(blob['access'])
|
||||
cred_ref = {'user_id': user_id,
|
||||
'project_id': tenant_id,
|
||||
'blob': jsonutils.dumps(blob),
|
||||
'id': credential_id,
|
||||
'type': CRED_TYPE_EC2}
|
||||
self.credential_api.create_credential(credential_id, cred_ref)
|
||||
return {'credential': self._convert_v3_to_ec2_credential(cred_ref)}
|
||||
|
||||
def get_credentials(self, user_id):
|
||||
"""List all credentials for a user.
|
||||
|
||||
:param user_id: id of user
|
||||
:returns: credentials: list of ec2 credential dicts
|
||||
"""
|
||||
self.identity_api.get_user(user_id)
|
||||
credential_refs = self.credential_api.list_credentials_for_user(
|
||||
user_id, type=CRED_TYPE_EC2)
|
||||
return {'credentials':
|
||||
[self._convert_v3_to_ec2_credential(credential)
|
||||
for credential in credential_refs]}
|
||||
|
||||
def get_credential(self, user_id, credential_id):
|
||||
"""Retrieve a user's access/secret pair by the access key.
|
||||
|
||||
Grab the full access/secret pair for a given access key.
|
||||
|
||||
:param user_id: id of user
|
||||
:param credential_id: access key for credentials
|
||||
:returns: credential: dict of ec2 credential
|
||||
"""
|
||||
self.identity_api.get_user(user_id)
|
||||
return {'credential': self._get_credentials(credential_id)}
|
||||
|
||||
def delete_credential(self, user_id, credential_id):
|
||||
"""Delete a user's access/secret pair.
|
||||
|
||||
Used to revoke a user's access/secret pair
|
||||
|
||||
:param user_id: id of user
|
||||
:param credential_id: access key for credentials
|
||||
:returns: bool: success
|
||||
"""
|
||||
self.identity_api.get_user(user_id)
|
||||
self._get_credentials(credential_id)
|
||||
ec2_credential_id = utils.hash_access_key(credential_id)
|
||||
return self.credential_api.delete_credential(ec2_credential_id)
|
||||
|
||||
@staticmethod
|
||||
def _convert_v3_to_ec2_credential(credential):
|
||||
# Prior to bug #1259584 fix, blob was stored unserialized
|
||||
|
@ -271,22 +206,6 @@ class Ec2ControllerV3(Ec2ControllerCommon, controller.V3Controller):
|
|||
collection_name = 'credentials'
|
||||
member_name = 'credential'
|
||||
|
||||
def _check_credential_owner_and_user_id_match(self, request, prep_info,
|
||||
user_id, credential_id):
|
||||
# NOTE(morganfainberg): this method needs to capture the arguments of
|
||||
# the method that is decorated with @controller.protected() (with
|
||||
# exception of the first argument ('context') since the protected
|
||||
# method passes in *args, **kwargs. In this case, it is easier to see
|
||||
# the expected input if the argspec is `user_id` and `credential_id`
|
||||
# explicitly (matching the :class:`.ec2_delete_credential()` method
|
||||
# below).
|
||||
ref = {}
|
||||
credential_id = utils.hash_access_key(credential_id)
|
||||
ref['credential'] = self.credential_api.get_credential(credential_id)
|
||||
# NOTE(morganfainberg): policy_api is required for this
|
||||
# check_protection to properly be able to perform policy enforcement.
|
||||
self.check_protection(request, prep_info, ref)
|
||||
|
||||
def authenticate(self, context, credentials=None, ec2Credentials=None):
|
||||
(user_ref, project_ref, roles_ref) = self._authenticate(
|
||||
credentials=credentials, ec2credentials=ec2Credentials
|
||||
|
@ -299,37 +218,3 @@ class Ec2ControllerV3(Ec2ControllerCommon, controller.V3Controller):
|
|||
)
|
||||
token_reference = render_token.render_token_response_from_model(token)
|
||||
return self.render_token_data_response(token.id, token_reference)
|
||||
|
||||
@controller.protected(callback=_check_credential_owner_and_user_id_match)
|
||||
def ec2_get_credential(self, request, user_id, credential_id):
|
||||
ref = super(Ec2ControllerV3, self).get_credential(user_id,
|
||||
credential_id)
|
||||
return Ec2ControllerV3.wrap_member(request.context_dict,
|
||||
ref['credential'])
|
||||
|
||||
@controller.protected()
|
||||
def ec2_list_credentials(self, request, user_id):
|
||||
refs = super(Ec2ControllerV3, self).get_credentials(user_id)
|
||||
return Ec2ControllerV3.wrap_collection(request.context_dict,
|
||||
refs['credentials'])
|
||||
|
||||
@controller.protected()
|
||||
def ec2_create_credential(self, request, user_id, tenant_id):
|
||||
ref = super(Ec2ControllerV3, self).create_credential(
|
||||
request, user_id, tenant_id)
|
||||
return Ec2ControllerV3.wrap_member(request.context_dict,
|
||||
ref['credential'])
|
||||
|
||||
@controller.protected(callback=_check_credential_owner_and_user_id_match)
|
||||
def ec2_delete_credential(self, request, user_id, credential_id):
|
||||
return super(Ec2ControllerV3, self).delete_credential(user_id,
|
||||
credential_id)
|
||||
|
||||
@classmethod
|
||||
def _add_self_referential_link(cls, context, ref):
|
||||
path = '/users/%(user_id)s/credentials/OS-EC2/%(credential_id)s'
|
||||
url = cls.base_url(context, path) % {
|
||||
'user_id': ref['user_id'],
|
||||
'credential_id': ref['access']}
|
||||
ref.setdefault('links', {})
|
||||
ref['links']['self'] = url
|
||||
|
|
|
@ -26,7 +26,7 @@ build_resource_relation = functools.partial(
|
|||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
_path_prefixes = ('ec2tokens', 'users')
|
||||
_path_prefixes = ('ec2tokens',)
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
ec2_controller = controllers.Ec2ControllerV3()
|
||||
|
@ -36,25 +36,3 @@ class Routers(wsgi.RoutersBase):
|
|||
path='/ec2tokens',
|
||||
post_action='authenticate',
|
||||
rel=build_resource_relation(resource_name='ec2tokens'))
|
||||
|
||||
# crud
|
||||
self._add_resource(
|
||||
mapper, ec2_controller,
|
||||
path='/users/{user_id}/credentials/OS-EC2',
|
||||
get_head_action='ec2_list_credentials',
|
||||
post_action='ec2_create_credential',
|
||||
rel=build_resource_relation(resource_name='user_credentials'),
|
||||
path_vars={
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, ec2_controller,
|
||||
path='/users/{user_id}/credentials/OS-EC2/{credential_id}',
|
||||
get_head_action='ec2_get_credential',
|
||||
delete_action='ec2_delete_credential',
|
||||
rel=build_resource_relation(resource_name='user_credential'),
|
||||
path_vars={
|
||||
'credential_id':
|
||||
json_home.build_v3_parameter_relation('credential_id'),
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
|
|
|
@ -12,6 +12,5 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystone.identity import controllers # noqa
|
||||
from keystone.identity.core import * # noqa
|
||||
from keystone.identity import generator # noqa
|
||||
|
|
|
@ -1,134 +0,0 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Workflow Logic the Identity service."""
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
from keystone.common import controller
|
||||
from keystone.common import provider_api
|
||||
from keystone.common import validation
|
||||
import keystone.conf
|
||||
from keystone import exception
|
||||
from keystone.i18n import _
|
||||
from keystone.identity import schema
|
||||
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
PROVIDERS = provider_api.ProviderAPIs
|
||||
|
||||
|
||||
class UserV3(controller.V3Controller):
|
||||
collection_name = 'users'
|
||||
member_name = 'user'
|
||||
|
||||
def __init__(self):
|
||||
super(UserV3, self).__init__()
|
||||
self.get_member_from_driver = PROVIDERS.identity_api.get_user
|
||||
|
||||
def _check_user_and_group_protection(self, request, prep_info,
|
||||
user_id, group_id):
|
||||
ref = {}
|
||||
ref['user'] = PROVIDERS.identity_api.get_user(user_id)
|
||||
ref['group'] = PROVIDERS.identity_api.get_group(group_id)
|
||||
self.check_protection(request, prep_info, ref)
|
||||
|
||||
@controller.protected()
|
||||
def create_user(self, request, user):
|
||||
validation.lazy_validate(schema.user_create, user)
|
||||
# The manager layer will generate the unique ID for users
|
||||
ref = self._normalize_dict(user)
|
||||
ref = self._normalize_domain_id(request, ref)
|
||||
ref = PROVIDERS.identity_api.create_user(
|
||||
ref, initiator=request.audit_initiator
|
||||
)
|
||||
return UserV3.wrap_member(request.context_dict, ref)
|
||||
|
||||
@controller.filterprotected('domain_id', 'enabled', 'idp_id', 'name',
|
||||
'protocol_id', 'unique_id',
|
||||
'password_expires_at')
|
||||
def list_users(self, request, filters):
|
||||
hints = UserV3.build_driver_hints(request, filters)
|
||||
domain = self._get_domain_id_for_list_request(request)
|
||||
refs = PROVIDERS.identity_api.list_users(
|
||||
domain_scope=domain, hints=hints
|
||||
)
|
||||
return UserV3.wrap_collection(request.context_dict, refs, hints=hints)
|
||||
|
||||
@controller.protected()
|
||||
def get_user(self, request, user_id):
|
||||
ref = PROVIDERS.identity_api.get_user(user_id)
|
||||
return UserV3.wrap_member(request.context_dict, ref)
|
||||
|
||||
def _update_user(self, request, user_id, user):
|
||||
self._require_matching_id(user_id, user)
|
||||
ref = PROVIDERS.identity_api.update_user(
|
||||
user_id, user, initiator=request.audit_initiator
|
||||
)
|
||||
return UserV3.wrap_member(request.context_dict, ref)
|
||||
|
||||
@controller.protected()
|
||||
def update_user(self, request, user_id, user):
|
||||
validation.lazy_validate(schema.user_update, user)
|
||||
return self._update_user(request, user_id, user)
|
||||
|
||||
@controller.protected()
|
||||
def delete_user(self, request, user_id):
|
||||
return PROVIDERS.identity_api.delete_user(
|
||||
user_id, initiator=request.audit_initiator
|
||||
)
|
||||
|
||||
# NOTE(gagehugo): We do not need this to be @protected.
|
||||
# A user is already expected to know their password in order
|
||||
# to change it, and can be authenticated as such.
|
||||
def change_password(self, request, user_id, user):
|
||||
original_password = user.get('original_password')
|
||||
if original_password is None:
|
||||
raise exception.ValidationError(target='user',
|
||||
attribute='original_password')
|
||||
|
||||
password = user.get('password')
|
||||
if password is None:
|
||||
raise exception.ValidationError(target='user',
|
||||
attribute='password')
|
||||
try:
|
||||
PROVIDERS.identity_api.change_password(
|
||||
user_id, original_password,
|
||||
password, initiator=request.audit_initiator)
|
||||
except AssertionError as e:
|
||||
raise exception.Unauthorized(_(
|
||||
'Error when changing user password: %s') % e)
|
||||
|
||||
|
||||
class GroupV3(controller.V3Controller):
|
||||
collection_name = 'groups'
|
||||
member_name = 'group'
|
||||
|
||||
def __init__(self):
|
||||
super(GroupV3, self).__init__()
|
||||
self.get_member_from_driver = PROVIDERS.identity_api.get_group
|
||||
|
||||
def _check_user_protection(self, request, prep_info, user_id):
|
||||
ref = {}
|
||||
ref['user'] = PROVIDERS.identity_api.get_user(user_id)
|
||||
self.check_protection(request, prep_info, ref)
|
||||
|
||||
@controller.filterprotected('name', callback=_check_user_protection)
|
||||
def list_groups_for_user(self, request, filters, user_id):
|
||||
hints = GroupV3.build_driver_hints(request, filters)
|
||||
refs = PROVIDERS.identity_api.list_groups_for_user(
|
||||
user_id, hints=hints
|
||||
)
|
||||
return GroupV3.wrap_collection(request.context_dict, refs, hints=hints)
|
|
@ -899,20 +899,10 @@ class Manager(manager.Manager):
|
|||
# - select the right driver for this domain
|
||||
# - clear/set domain_ids for drivers that do not support domains
|
||||
# - create any ID mapping that might be required
|
||||
|
||||
# TODO(morgan): The split of "authenticate" and "_authenticate" is done
|
||||
# until user API is converted to flask. This is to make webob and flask
|
||||
# play nicely with the authenticate mechanism during self-service password
|
||||
# changes. While this is in place, CADF notifications will not be emitted
|
||||
# for self-service password changes indicating an auth attempt was being
|
||||
# made. This is a very limited time transitional change.
|
||||
@notifications.emit_event('authenticate')
|
||||
def authenticate(self, user_id, password):
|
||||
return self._authenticate(user_id, password)
|
||||
|
||||
@domains_configured
|
||||
@exception_translated('assertion')
|
||||
def _authenticate(self, user_id, password):
|
||||
def authenticate(self, user_id, password):
|
||||
domain_id, driver, entity_id = (
|
||||
self._get_domain_driver_and_entity_id(user_id))
|
||||
ref = driver.authenticate(entity_id, password)
|
||||
|
@ -1390,9 +1380,7 @@ class Manager(manager.Manager):
|
|||
|
||||
# authenticate() will raise an AssertionError if authentication fails
|
||||
try:
|
||||
# TODO(morgan): When users is ported to flask, ensure this is
|
||||
# mapped back to self.authenticate instead of self._authenticate.
|
||||
self._authenticate(user_id, original_password)
|
||||
self.authenticate(user_id, original_password)
|
||||
except exception.PasswordExpired:
|
||||
# If a password has expired, we want users to be able to change it
|
||||
pass
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""WSGI Routers for the Identity service."""
|
||||
|
||||
from keystone.common import json_home
|
||||
from keystone.common import router
|
||||
from keystone.common import wsgi
|
||||
from keystone.identity import controllers
|
||||
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
_path_prefixes = ('users', )
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
user_controller = controllers.UserV3()
|
||||
routers.append(
|
||||
router.Router(user_controller,
|
||||
'users', 'user',
|
||||
resource_descriptions=self.v3_resources))
|
||||
|
||||
self._add_resource(
|
||||
mapper, user_controller,
|
||||
path='/users/{user_id}/password',
|
||||
post_action='change_password',
|
||||
rel=json_home.build_v3_resource_relation('user_change_password'),
|
||||
path_vars={
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
|
||||
group_controller = controllers.GroupV3()
|
||||
|
||||
self._add_resource(
|
||||
mapper, group_controller,
|
||||
path='/users/{user_id}/groups',
|
||||
get_head_action='list_groups_for_user',
|
||||
rel=json_home.build_v3_resource_relation('user_groups'),
|
||||
path_vars={
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
|
@ -1,143 +0,0 @@
|
|||
# Copyright 2013 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Extensions supporting OAuth1."""
|
||||
|
||||
from oslo_log import log
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from keystone.common import controller
|
||||
from keystone.common import provider_api
|
||||
import keystone.conf
|
||||
from keystone import exception
|
||||
from keystone.i18n import _
|
||||
from keystone import notifications
|
||||
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
PROVIDERS = provider_api.ProviderAPIs
|
||||
|
||||
|
||||
class AccessTokenCrudV3(controller.V3Controller):
|
||||
collection_name = 'access_tokens'
|
||||
member_name = 'access_token'
|
||||
|
||||
@classmethod
|
||||
def _add_self_referential_link(cls, context, ref):
|
||||
# NOTE(lwolf): overriding method to add proper path to self link
|
||||
ref.setdefault('links', {})
|
||||
path = '/users/%(user_id)s/OS-OAUTH1/access_tokens' % {
|
||||
'user_id': cls._get_user_id(ref)
|
||||
}
|
||||
ref['links']['self'] = cls.base_url(context, path) + '/' + ref['id']
|
||||
|
||||
@controller.protected()
|
||||
def get_access_token(self, request, user_id, access_token_id):
|
||||
access_token = PROVIDERS.oauth_api.get_access_token(access_token_id)
|
||||
if access_token['authorizing_user_id'] != user_id:
|
||||
raise exception.NotFound()
|
||||
access_token = self._format_token_entity(request.context_dict,
|
||||
access_token)
|
||||
return AccessTokenCrudV3.wrap_member(request.context_dict,
|
||||
access_token)
|
||||
|
||||
@controller.protected()
|
||||
def list_access_tokens(self, request, user_id):
|
||||
if request.context.is_delegated_auth:
|
||||
raise exception.Forbidden(
|
||||
_('Cannot list request tokens'
|
||||
' with a token issued via delegation.'))
|
||||
refs = PROVIDERS.oauth_api.list_access_tokens(user_id)
|
||||
formatted_refs = ([self._format_token_entity(request.context_dict, x)
|
||||
for x in refs])
|
||||
return AccessTokenCrudV3.wrap_collection(request.context_dict,
|
||||
formatted_refs)
|
||||
|
||||
@controller.protected()
|
||||
def delete_access_token(self, request, user_id, access_token_id):
|
||||
access_token = PROVIDERS.oauth_api.get_access_token(access_token_id)
|
||||
reason = (
|
||||
'Invalidating the token cache because an access token for '
|
||||
'consumer %(consumer_id)s has been deleted. Authorization for '
|
||||
'users with OAuth tokens will be recalculated and enforced '
|
||||
'accordingly the next time they authenticate or validate a '
|
||||
'token.' % {'consumer_id': access_token['consumer_id']}
|
||||
)
|
||||
notifications.invalidate_token_cache_notification(reason)
|
||||
return PROVIDERS.oauth_api.delete_access_token(
|
||||
user_id, access_token_id, initiator=request.audit_initiator
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _get_user_id(entity):
|
||||
return entity.get('authorizing_user_id', '')
|
||||
|
||||
def _format_token_entity(self, context, entity):
|
||||
|
||||
formatted_entity = entity.copy()
|
||||
access_token_id = formatted_entity['id']
|
||||
user_id = self._get_user_id(formatted_entity)
|
||||
if 'role_ids' in entity:
|
||||
formatted_entity.pop('role_ids')
|
||||
if 'access_secret' in entity:
|
||||
formatted_entity.pop('access_secret')
|
||||
|
||||
url = ('/users/%(user_id)s/OS-OAUTH1/access_tokens/%(access_token_id)s'
|
||||
'/roles' % {'user_id': user_id,
|
||||
'access_token_id': access_token_id})
|
||||
|
||||
formatted_entity.setdefault('links', {})
|
||||
formatted_entity['links']['roles'] = (self.base_url(context, url))
|
||||
|
||||
return formatted_entity
|
||||
|
||||
|
||||
class AccessTokenRolesV3(controller.V3Controller):
|
||||
collection_name = 'roles'
|
||||
member_name = 'role'
|
||||
|
||||
@controller.protected()
|
||||
def list_access_token_roles(self, request, user_id, access_token_id):
|
||||
access_token = PROVIDERS.oauth_api.get_access_token(access_token_id)
|
||||
if access_token['authorizing_user_id'] != user_id:
|
||||
raise exception.NotFound()
|
||||
authed_role_ids = access_token['role_ids']
|
||||
authed_role_ids = jsonutils.loads(authed_role_ids)
|
||||
refs = ([self._format_role_entity(x) for x in authed_role_ids])
|
||||
return AccessTokenRolesV3.wrap_collection(request.context_dict, refs)
|
||||
|
||||
@controller.protected()
|
||||
def get_access_token_role(self, request, user_id,
|
||||
access_token_id, role_id):
|
||||
access_token = PROVIDERS.oauth_api.get_access_token(access_token_id)
|
||||
if access_token['authorizing_user_id'] != user_id:
|
||||
raise exception.Unauthorized(_('User IDs do not match'))
|
||||
authed_role_ids = access_token['role_ids']
|
||||
authed_role_ids = jsonutils.loads(authed_role_ids)
|
||||
for authed_role_id in authed_role_ids:
|
||||
if authed_role_id == role_id:
|
||||
role = self._format_role_entity(role_id)
|
||||
return AccessTokenRolesV3.wrap_member(request.context_dict,
|
||||
role)
|
||||
raise exception.RoleNotFound(role_id=role_id)
|
||||
|
||||
def _format_role_entity(self, role_id):
|
||||
role = PROVIDERS.role_api.get_role(role_id)
|
||||
formatted_entity = role.copy()
|
||||
if 'description' in role:
|
||||
formatted_entity.pop('description')
|
||||
if 'enabled' in role:
|
||||
formatted_entity.pop('enabled')
|
||||
return formatted_entity
|
|
@ -1,101 +0,0 @@
|
|||
# Copyright 2013 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import functools
|
||||
|
||||
from keystone.common import json_home
|
||||
from keystone.common import wsgi
|
||||
from keystone.oauth1 import controllers
|
||||
|
||||
|
||||
build_resource_relation = functools.partial(
|
||||
json_home.build_v3_extension_resource_relation,
|
||||
extension_name='OS-OAUTH1', extension_version='1.0')
|
||||
|
||||
build_parameter_relation = functools.partial(
|
||||
json_home.build_v3_extension_parameter_relation,
|
||||
extension_name='OS-OAUTH1', extension_version='1.0')
|
||||
|
||||
ACCESS_TOKEN_ID_PARAMETER_RELATION = build_parameter_relation(
|
||||
parameter_name='access_token_id')
|
||||
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
"""API Endpoints for the OAuth1 extension.
|
||||
|
||||
The goal of this extension is to allow third-party service providers
|
||||
to acquire tokens with a limited subset of a user's roles for acting
|
||||
on behalf of that user. This is done using an oauth-similar flow and
|
||||
api.
|
||||
|
||||
The API looks like::
|
||||
|
||||
# User access token crud
|
||||
GET /users/{user_id}/OS-OAUTH1/access_tokens
|
||||
GET /users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}
|
||||
GET /users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/roles
|
||||
GET /users/{user_id}/OS-OAUTH1/access_tokens
|
||||
/{access_token_id}/roles/{role_id}
|
||||
DELETE /users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}
|
||||
|
||||
"""
|
||||
|
||||
_path_prefixes = ('users',)
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
access_token_controller = controllers.AccessTokenCrudV3()
|
||||
access_token_roles_controller = controllers.AccessTokenRolesV3()
|
||||
|
||||
# user access token crud
|
||||
self._add_resource(
|
||||
mapper, access_token_controller,
|
||||
path='/users/{user_id}/OS-OAUTH1/access_tokens',
|
||||
get_head_action='list_access_tokens',
|
||||
rel=build_resource_relation(resource_name='user_access_tokens'),
|
||||
path_vars={
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, access_token_controller,
|
||||
path='/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}',
|
||||
get_head_action='get_access_token',
|
||||
delete_action='delete_access_token',
|
||||
rel=build_resource_relation(resource_name='user_access_token'),
|
||||
path_vars={
|
||||
'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, access_token_roles_controller,
|
||||
path='/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/'
|
||||
'roles',
|
||||
get_head_action='list_access_token_roles',
|
||||
rel=build_resource_relation(
|
||||
resource_name='user_access_token_roles'),
|
||||
path_vars={
|
||||
'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, access_token_roles_controller,
|
||||
path='/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/'
|
||||
'roles/{role_id}',
|
||||
get_head_action='get_access_token_role',
|
||||
rel=build_resource_relation(
|
||||
resource_name='user_access_token_role'),
|
||||
path_vars={
|
||||
'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
|
||||
'role_id': json_home.Parameters.ROLE_ID,
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
|
@ -24,13 +24,10 @@ import routes
|
|||
import werkzeug.wsgi
|
||||
|
||||
import keystone.api
|
||||
from keystone.application_credential import routers as app_cred_routers
|
||||
from keystone.assignment import routers as assignment_routers
|
||||
from keystone.common import wsgi as keystone_wsgi
|
||||
from keystone.contrib.ec2 import routers as ec2_routers
|
||||
from keystone.contrib.s3 import routers as s3_routers
|
||||
from keystone.identity import routers as identity_routers
|
||||
from keystone.oauth1 import routers as oauth1_routers
|
||||
from keystone.resource import routers as resource_routers
|
||||
|
||||
# TODO(morgan): _MOVED_API_PREFIXES to be removed when the legacy dispatch
|
||||
|
@ -57,6 +54,7 @@ _MOVED_API_PREFIXES = frozenset(
|
|||
'roles',
|
||||
'services',
|
||||
'system',
|
||||
'users',
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -64,10 +62,7 @@ LOG = log.getLogger(__name__)
|
|||
|
||||
|
||||
ALL_API_ROUTERS = [assignment_routers,
|
||||
identity_routers,
|
||||
app_cred_routers,
|
||||
resource_routers,
|
||||
oauth1_routers,
|
||||
ec2_routers,
|
||||
s3_routers]
|
||||
|
||||
|
|
|
@ -921,7 +921,7 @@ class ResourceBase(flask_restful.Resource):
|
|||
if token_ref.domain_scoped:
|
||||
return token_ref.domain_id
|
||||
elif token_ref.project_scoped:
|
||||
return token_ref.project_domain_id
|
||||
return token_ref.project_domain['id']
|
||||
else:
|
||||
msg = 'No domain information specified as part of list request'
|
||||
tr_msg = _('No domain information specified as part of list '
|
||||
|
@ -941,7 +941,8 @@ class ResourceBase(flask_restful.Resource):
|
|||
# Retrieve the auth context that was prepared by
|
||||
# AuthContextMiddleware.
|
||||
|
||||
auth_context = cls.auth_context
|
||||
auth_context = flask.request.environ.get(
|
||||
authorization.AUTH_CONTEXT_ENV, {})
|
||||
return auth_context['token']
|
||||
except KeyError:
|
||||
LOG.warning("Couldn't find the auth context.")
|
||||
|
|
|
@ -292,15 +292,26 @@ class ApplicationCredentialTestCase(test_v3.RestfulTestCase):
|
|||
expected_status=http_client.NO_CONTENT)
|
||||
|
||||
def test_update_application_credential(self):
|
||||
roles = [{'id': self.role_id}]
|
||||
app_cred_body = self._app_cred_body(roles=roles)
|
||||
resp = self.post('/users/%s/application_credentials' % self.user_id,
|
||||
body=app_cred_body,
|
||||
expected_status=http_client.CREATED)
|
||||
# Application credentials are immutable
|
||||
app_cred_body['application_credential']['description'] = "New Things"
|
||||
app_cred_id = resp.json['application_credential']['id']
|
||||
self.patch(MEMBER_PATH_FMT % {'user_id': self.user_id,
|
||||
'app_cred_id': app_cred_id},
|
||||
body=app_cred_body,
|
||||
expected_status=http_client.NOT_FOUND)
|
||||
with self.test_client() as c:
|
||||
roles = [{'id': self.role_id}]
|
||||
app_cred_body = self._app_cred_body(roles=roles)
|
||||
token = self.get_scoped_token()
|
||||
resp = c.post(
|
||||
'/v3/users/%s/application_credentials' % self.user_id,
|
||||
json=app_cred_body,
|
||||
expected_status_code=http_client.CREATED,
|
||||
headers={'X-Auth-Token': token})
|
||||
# Application credentials are immutable
|
||||
app_cred_body['application_credential'][
|
||||
'description'] = "New Things"
|
||||
app_cred_id = resp.json['application_credential']['id']
|
||||
# NOTE(morgan): when the whole test case is converted to using
|
||||
# flask test_client, this extra v3 prefix will
|
||||
# need to be rolled into the base MEMBER_PATH_FMT
|
||||
member_path = '/v3%s' % MEMBER_PATH_FMT % {
|
||||
'user_id': self.user_id,
|
||||
'app_cred_id': app_cred_id}
|
||||
c.patch(member_path,
|
||||
json=app_cred_body,
|
||||
expected_status_code=http_client.METHOD_NOT_ALLOWED,
|
||||
headers={'X-Auth-Token': token})
|
||||
|
|
|
@ -142,6 +142,7 @@ FEDERATED_IDP_SPECIFIC_WEBSSO = ('/auth/OS-FEDERATION/identity_providers/'
|
|||
|
||||
APPLICATION_CREDENTIAL = ('/users/{user_id}/application_credentials/'
|
||||
'{application_credential_id}')
|
||||
APPLICATION_CREDENTIALS = '/users/{user_id}/application_credentials'
|
||||
APPLICATION_CREDENTIAL_RELATION = (
|
||||
json_home.build_v3_parameter_relation('application_credential_id'))
|
||||
|
||||
|
@ -633,6 +634,10 @@ V3_JSON_HOME_RESOURCES = {
|
|||
'href': '/limits/model',
|
||||
'hints': {'status': 'experimental'}
|
||||
},
|
||||
json_home.build_v3_resource_relation('application_credentials'): {
|
||||
'href-template': APPLICATION_CREDENTIALS,
|
||||
'href-vars': {
|
||||
'user_id': json_home.build_v3_parameter_relation('user_id')}},
|
||||
json_home.build_v3_resource_relation('application_credential'): {
|
||||
'href-template': APPLICATION_CREDENTIAL,
|
||||
'href-vars': {
|
||||
|
|
Loading…
Reference in New Issue