Convert projects API to Flask
Convert the projects API to Flask native dispatching. Change-Id: I3406284acfb7950b701f6a98a3a173a427415f97 Co-Authored-By: Morgan Fainberg <morgan.fainberg@gmail.com> Partial-Bug: #1776504
This commit is contained in:
parent
86f968163e
commit
46380baeb7
@ -24,6 +24,7 @@ from keystone.api import os_oauth1
|
||||
from keystone.api import os_revoke
|
||||
from keystone.api import os_simple_cert
|
||||
from keystone.api import policy
|
||||
from keystone.api import projects
|
||||
from keystone.api import regions
|
||||
from keystone.api import registered_limits
|
||||
from keystone.api import role_assignments
|
||||
@ -49,6 +50,7 @@ __all__ = (
|
||||
'os_revoke',
|
||||
'os_simple_cert',
|
||||
'policy',
|
||||
'projects',
|
||||
'regions',
|
||||
'registered_limits',
|
||||
'role_assignments',
|
||||
@ -75,6 +77,7 @@ __apis__ = (
|
||||
os_revoke,
|
||||
os_simple_cert,
|
||||
policy,
|
||||
projects,
|
||||
regions,
|
||||
registered_limits,
|
||||
role_assignments,
|
||||
|
522
keystone/api/projects.py
Normal file
522
keystone/api/projects.py
Normal file
@ -0,0 +1,522 @@
|
||||
# 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/projects
|
||||
|
||||
import functools
|
||||
|
||||
import flask
|
||||
from six.moves import http_client
|
||||
|
||||
from keystone.common import json_home
|
||||
from keystone.common import provider_api
|
||||
from keystone.common import rbac_enforcer
|
||||
from keystone.common import validation
|
||||
import keystone.conf
|
||||
from keystone import exception
|
||||
from keystone.i18n import _
|
||||
from keystone.resource import schema
|
||||
from keystone.server import flask as ks_flask
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
ENFORCER = rbac_enforcer.RBACEnforcer
|
||||
PROVIDERS = provider_api.ProviderAPIs
|
||||
|
||||
|
||||
class ProjectResource(ks_flask.ResourceBase):
|
||||
collection_key = 'projects'
|
||||
member_key = 'project'
|
||||
get_member_from_driver = PROVIDERS.deferred_provider_lookup(
|
||||
api='resource_api', method='get_project')
|
||||
|
||||
def _expand_project_ref(self, ref):
|
||||
parents_as_list = self.query_filter_is_true('parents_as_list')
|
||||
parents_as_ids = self.query_filter_is_true('parents_as_ids')
|
||||
|
||||
subtree_as_list = self.query_filter_is_true('subtree_as_list')
|
||||
|
||||
subtree_as_ids = self.query_filter_is_true('subtree_as_ids')
|
||||
include_limits = self.query_filter_is_true('include_limits')
|
||||
|
||||
# parents_as_list and parents_as_ids are mutually exclusive
|
||||
if parents_as_list and parents_as_ids:
|
||||
msg = _('Cannot use parents_as_list and parents_as_ids query '
|
||||
'params at the same time.')
|
||||
raise exception.ValidationError(msg)
|
||||
|
||||
# subtree_as_list and subtree_as_ids are mutually exclusive
|
||||
if subtree_as_list and subtree_as_ids:
|
||||
msg = _('Cannot use subtree_as_list and subtree_as_ids query '
|
||||
'params at the same time.')
|
||||
raise exception.ValidationError(msg)
|
||||
|
||||
if parents_as_list:
|
||||
parents = PROVIDERS.resource_api.list_project_parents(
|
||||
ref['id'], self.oslo_context.user_id, include_limits)
|
||||
ref['parents'] = [self.wrap_member(p)
|
||||
for p in parents]
|
||||
elif parents_as_ids:
|
||||
ref['parents'] = PROVIDERS.resource_api.get_project_parents_as_ids(
|
||||
ref
|
||||
)
|
||||
|
||||
if subtree_as_list:
|
||||
subtree = PROVIDERS.resource_api.list_projects_in_subtree(
|
||||
ref['id'], self.oslo_context.user_id, include_limits)
|
||||
ref['subtree'] = [self.wrap_member(p)
|
||||
for p in subtree]
|
||||
elif subtree_as_ids:
|
||||
ref['subtree'] = (
|
||||
PROVIDERS.resource_api.get_projects_in_subtree_as_ids(
|
||||
ref['id']
|
||||
)
|
||||
)
|
||||
|
||||
def _get_project(self, project_id):
|
||||
"""Get project.
|
||||
|
||||
GET/HEAD /v3/projects/{project_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:get_project')
|
||||
project = PROVIDERS.resource_api.get_project(project_id)
|
||||
self._expand_project_ref(project)
|
||||
return self.wrap_member(project)
|
||||
|
||||
def _list_projects(self):
|
||||
"""List projects.
|
||||
|
||||
GET/HEAD /v3/projects
|
||||
"""
|
||||
filters = ('domain_id', 'enabled', 'name', 'parent_id', 'is_domain')
|
||||
ENFORCER.enforce_call(action='identity:list_projects',
|
||||
filters=filters)
|
||||
hints = self.build_driver_hints(filters)
|
||||
|
||||
# If 'is_domain' has not been included as a query, we default it to
|
||||
# False (which in query terms means '0')
|
||||
if 'is_domain' not in flask.request.args:
|
||||
hints.add_filter('is_domain', '0')
|
||||
|
||||
tag_params = ['tags', 'tags-any', 'not-tags', 'not-tags-any']
|
||||
for t in tag_params:
|
||||
if t in flask.request.args:
|
||||
hints.add_filter(t, flask.request.args[t])
|
||||
refs = PROVIDERS.resource_api.list_projects(hints=hints)
|
||||
return self.wrap_collection(refs, hints=hints)
|
||||
|
||||
def get(self, project_id=None):
|
||||
"""Get project or list projects.
|
||||
|
||||
GET/HEAD /v3/projects
|
||||
GET/HEAD /v3/projects/{project_id}
|
||||
"""
|
||||
if project_id is not None:
|
||||
return self._get_project(project_id)
|
||||
else:
|
||||
return self._list_projects()
|
||||
|
||||
def post(self):
|
||||
"""Create project.
|
||||
|
||||
POST /v3/projects
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:create_project')
|
||||
project = self.request_body_json.get('project', {})
|
||||
validation.lazy_validate(schema.project_create, project)
|
||||
project = self._assign_unique_id(project)
|
||||
if not project.get('is_domain'):
|
||||
project = self._normalize_domain_id(project)
|
||||
# Our API requires that you specify the location in the hierarchy
|
||||
# unambiguously. This could be by parent_id or, if it is a top
|
||||
# level project, just by providing a domain_id.
|
||||
if not project.get('parent_id'):
|
||||
project['parent_id'] = project.get('domain_id')
|
||||
project = self._normalize_dict(project)
|
||||
try:
|
||||
ref = PROVIDERS.resource_api.create_project(
|
||||
project['id'],
|
||||
project,
|
||||
initiator=self.audit_initiator)
|
||||
except (exception.DomainNotFound, exception.ProjectNotFound) as e:
|
||||
raise exception.ValidationError(e)
|
||||
return self.wrap_member(ref), http_client.CREATED
|
||||
|
||||
def patch(self, project_id):
|
||||
"""Update project.
|
||||
|
||||
PATCH /v3/projects/{project_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:update_project')
|
||||
project = self.request_body_json.get('project', {})
|
||||
validation.lazy_validate(schema.project_update, project)
|
||||
self._require_matching_id(project)
|
||||
ref = PROVIDERS.resource_api.update_project(
|
||||
project_id,
|
||||
project,
|
||||
initiator=self.audit_initiator)
|
||||
return self.wrap_member(ref)
|
||||
|
||||
def delete(self, project_id):
|
||||
"""Delete project.
|
||||
|
||||
DELETE /v3/projects/{project_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:delete_project')
|
||||
PROVIDERS.resource_api.delete_project(
|
||||
project_id,
|
||||
initiator=self.audit_initiator)
|
||||
return None, http_client.NO_CONTENT
|
||||
|
||||
|
||||
class _ProjectTagResourceBase(ks_flask.ResourceBase):
|
||||
collection_key = 'projects'
|
||||
member_key = 'tags'
|
||||
get_member_from_driver = PROVIDERS.deferred_provider_lookup(
|
||||
api='resource_api', method='get_project_tag')
|
||||
|
||||
@classmethod
|
||||
def wrap_member(cls, ref, collection_name=None, member_name=None):
|
||||
member_name = member_name or cls.member_key
|
||||
# NOTE(gagehugo): Overriding this due to how the common controller
|
||||
# expects the ref to have an id, which for tags it does not.
|
||||
new_ref = {'links': {'self': ks_flask.full_url()}}
|
||||
new_ref[member_name] = (ref or [])
|
||||
return new_ref
|
||||
|
||||
|
||||
class ProjectTagsResource(_ProjectTagResourceBase):
|
||||
def get(self, project_id):
|
||||
"""List tags associated with a given project.
|
||||
|
||||
GET /v3/projects/{project_id}/tags
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:list_project_tags')
|
||||
ref = PROVIDERS.resource_api.list_project_tags(project_id)
|
||||
return self.wrap_member(ref)
|
||||
|
||||
def put(self, project_id):
|
||||
"""Update all tags associated with a given project.
|
||||
|
||||
PUT /v3/projects/{project_id}/tags
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:update_project_tags')
|
||||
tags = self.request_body_json.get('tags', {})
|
||||
validation.lazy_validate(schema.project_tags_update, tags)
|
||||
ref = PROVIDERS.resource_api.update_project_tags(
|
||||
project_id, tags, initiator=self.audit_initiator)
|
||||
return self.wrap_member(ref)
|
||||
|
||||
def delete(self, project_id):
|
||||
"""Delete all tags associated with a given project.
|
||||
|
||||
DELETE /v3/projects/{project_id}/tags
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:delete_project_tags')
|
||||
PROVIDERS.resource_api.update_project_tags(project_id, [])
|
||||
return None, http_client.NO_CONTENT
|
||||
|
||||
|
||||
class ProjectTagResource(_ProjectTagResourceBase):
|
||||
def get(self, project_id, value):
|
||||
"""Get information for a single tag associated with a given project.
|
||||
|
||||
GET /v3/projects/{project_id}/tags/{value}
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:get_project_tag')
|
||||
PROVIDERS.resource_api.get_project_tag(project_id, value)
|
||||
return None, http_client.NO_CONTENT
|
||||
|
||||
def put(self, project_id, value):
|
||||
"""Add a single tag to a project.
|
||||
|
||||
PUT /v3/projects/{project_id}/tags/{value}
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:create_project_tag')
|
||||
validation.lazy_validate(schema.project_tag_create, value)
|
||||
# Check if we will exceed the max number of tags on this project
|
||||
tags = PROVIDERS.resource_api.list_project_tags(project_id)
|
||||
tags.append(value)
|
||||
validation.lazy_validate(schema.project_tags_update, tags)
|
||||
PROVIDERS.resource_api.create_project_tag(
|
||||
project_id,
|
||||
value,
|
||||
initiator=self.audit_initiator
|
||||
)
|
||||
url = '/'.join((ks_flask.base_url(), project_id, 'tags', value))
|
||||
response = flask.make_response('', http_client.CREATED)
|
||||
response.headers['Location'] = url
|
||||
return response
|
||||
|
||||
def delete(self, project_id, value):
|
||||
"""Delete a single tag from a project.
|
||||
|
||||
/v3/projects/{project_id}/tags/{value}
|
||||
"""
|
||||
ENFORCER.enforce_call(action='identity:delete_project_tag')
|
||||
PROVIDERS.resource_api.delete_project_tag(project_id, value)
|
||||
return None, http_client.NO_CONTENT
|
||||
|
||||
|
||||
class _ProjectGrantResourceBase(ks_flask.ResourceBase):
|
||||
collection_key = 'roles'
|
||||
member_key = 'role'
|
||||
get_member_from_driver = PROVIDERS.deferred_provider_lookup(
|
||||
api='role_api', method='get_role')
|
||||
|
||||
@staticmethod
|
||||
def _check_if_inherited():
|
||||
return flask.request.path.endswith('/inherited_to_projects')
|
||||
|
||||
@staticmethod
|
||||
def _build_enforcement_target_attr(role_id=None, user_id=None,
|
||||
group_id=None, domain_id=None,
|
||||
project_id=None,
|
||||
allow_non_existing=False):
|
||||
ref = {}
|
||||
if role_id:
|
||||
ref['role'] = PROVIDERS.role_api.get_role(role_id)
|
||||
|
||||
try:
|
||||
if user_id:
|
||||
ref['user'] = PROVIDERS.identity_api.get_user(user_id)
|
||||
else:
|
||||
ref['group'] = PROVIDERS.identity_api.get_group(group_id)
|
||||
except (exception.UserNotFound, exception.GroupNotFound):
|
||||
if not allow_non_existing:
|
||||
raise
|
||||
|
||||
# NOTE(lbragstad): This if/else check will need to be expanded in the
|
||||
# future to handle system hierarchies if that is implemented.
|
||||
if domain_id:
|
||||
ref['domain'] = PROVIDERS.resource_api.get_domain(domain_id)
|
||||
elif project_id:
|
||||
ref['project'] = PROVIDERS.resource_api.get_project(project_id)
|
||||
|
||||
return ref
|
||||
|
||||
|
||||
class ProjectUserGrantResource(_ProjectGrantResourceBase):
|
||||
def get(self, project_id, user_id, role_id):
|
||||
"""Check grant for project, user, role.
|
||||
|
||||
GET/HEAD /v3/projects/{project_id/users/{user_id}/roles/{role_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(
|
||||
action='identity:check_grant',
|
||||
build_target=functools.partial(
|
||||
self._build_enforcement_target_attr, role_id=role_id,
|
||||
project_id=project_id, user_id=user_id)
|
||||
)
|
||||
inherited = self._check_if_inherited()
|
||||
PROVIDERS.assignment_api.get_grant(
|
||||
role_id=role_id, user_id=user_id, project_id=project_id,
|
||||
inherited_to_projects=inherited)
|
||||
return None, http_client.NO_CONTENT
|
||||
|
||||
def put(self, project_id, user_id, role_id):
|
||||
"""Grant role for user on project.
|
||||
|
||||
PUT /v3/projects/{project_id}/users/{user_id}/roles/{role_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(
|
||||
action='identity:create_grant',
|
||||
build_target=functools.partial(
|
||||
self._build_enforcement_target_attr,
|
||||
role_id=role_id, project_id=project_id, user_id=user_id)
|
||||
)
|
||||
inherited = self._check_if_inherited()
|
||||
PROVIDERS.assignment_api.create_grant(
|
||||
role_id=role_id, user_id=user_id, project_id=project_id,
|
||||
inherited_to_projects=inherited, initiator=self.audit_initiator)
|
||||
return None, http_client.NO_CONTENT
|
||||
|
||||
def delete(self, project_id, user_id, role_id):
|
||||
"""Delete grant of role for user on project.
|
||||
|
||||
DELETE /v3/projects/{project_id}/users/{user_id}/roles/{role_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(
|
||||
action='identity:revoke_grant',
|
||||
build_target=functools.partial(
|
||||
self._build_enforcement_target_attr,
|
||||
role_id=role_id, user_id=user_id, project_id=project_id,
|
||||
allow_non_existing=True)
|
||||
)
|
||||
inherited = self._check_if_inherited()
|
||||
PROVIDERS.assignment_api.delete_grant(
|
||||
role_id=role_id, user_id=user_id, project_id=project_id,
|
||||
inherited_to_projects=inherited, initiator=self.audit_initiator)
|
||||
return None, http_client.NO_CONTENT
|
||||
|
||||
|
||||
class ProjectUserListGrantResource(_ProjectGrantResourceBase):
|
||||
def get(self, project_id, user_id):
|
||||
"""List grants for user on project.
|
||||
|
||||
GET/HEAD /v3/projects/{project_id}/users/{user_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(
|
||||
action='identity:list_grants',
|
||||
build_target=functools.partial(
|
||||
self._build_enforcement_target_attr,
|
||||
project_id=project_id, user_id=user_id)
|
||||
)
|
||||
inherited = self._check_if_inherited()
|
||||
refs = PROVIDERS.assignment_api.list_grants(
|
||||
user_id=user_id, project_id=project_id,
|
||||
inherited_to_projects=inherited)
|
||||
return self.wrap_collection(refs)
|
||||
|
||||
|
||||
class ProjectGroupGrantResource(_ProjectGrantResourceBase):
|
||||
def get(self, project_id, group_id, role_id):
|
||||
"""Check grant for project, group, role.
|
||||
|
||||
GET/HEAD /v3/projects/{project_id/groups/{group_id}/roles/{role_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(
|
||||
action='identity:check_grant',
|
||||
build_target=functools.partial(
|
||||
self._build_enforcement_target_attr, role_id=role_id,
|
||||
project_id=project_id, group_id=group_id)
|
||||
)
|
||||
inherited = self._check_if_inherited()
|
||||
PROVIDERS.assignment_api.get_grant(
|
||||
role_id=role_id, group_id=group_id, project_id=project_id,
|
||||
inherited_to_projects=inherited)
|
||||
return None, http_client.NO_CONTENT
|
||||
|
||||
def put(self, project_id, group_id, role_id):
|
||||
"""Grant role for group on project.
|
||||
|
||||
PUT /v3/projects/{project_id}/groups/{group_id}/roles/{role_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(
|
||||
action='identity:create_grant',
|
||||
build_target=functools.partial(
|
||||
self._build_enforcement_target_attr,
|
||||
role_id=role_id, project_id=project_id, group_id=group_id)
|
||||
)
|
||||
inherited = self._check_if_inherited()
|
||||
PROVIDERS.assignment_api.create_grant(
|
||||
role_id=role_id, group_id=group_id, project_id=project_id,
|
||||
inherited_to_projects=inherited, initiator=self.audit_initiator)
|
||||
return None, http_client.NO_CONTENT
|
||||
|
||||
def delete(self, project_id, group_id, role_id):
|
||||
"""Delete grant of role for group on project.
|
||||
|
||||
DELETE /v3/projects/{project_id}/groups/{group_id}/roles/{role_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(
|
||||
action='identity:revoke_grant',
|
||||
build_target=functools.partial(
|
||||
self._build_enforcement_target_attr,
|
||||
role_id=role_id, group_id=group_id, project_id=project_id,
|
||||
allow_non_existing=True)
|
||||
)
|
||||
inherited = self._check_if_inherited()
|
||||
PROVIDERS.assignment_api.delete_grant(
|
||||
role_id=role_id, group_id=group_id, project_id=project_id,
|
||||
inherited_to_projects=inherited, initiator=self.audit_initiator)
|
||||
return None, http_client.NO_CONTENT
|
||||
|
||||
|
||||
class ProjectGroupListGrantResource(_ProjectGrantResourceBase):
|
||||
def get(self, project_id, group_id):
|
||||
"""List grants for group on project.
|
||||
|
||||
GET/HEAD /v3/projects/{project_id}/groups/{group_id}
|
||||
"""
|
||||
ENFORCER.enforce_call(
|
||||
action='identity:list_grants',
|
||||
build_target=functools.partial(
|
||||
self._build_enforcement_target_attr,
|
||||
project_id=project_id, group_id=group_id)
|
||||
)
|
||||
inherited = self._check_if_inherited()
|
||||
refs = PROVIDERS.assignment_api.list_grants(
|
||||
group_id=group_id, project_id=project_id,
|
||||
inherited_to_projects=inherited)
|
||||
return self.wrap_collection(refs)
|
||||
|
||||
|
||||
class ProjectAPI(ks_flask.APIBase):
|
||||
_name = 'projects'
|
||||
_import_name = __name__
|
||||
resources = [ProjectResource]
|
||||
resource_mapping = [
|
||||
ks_flask.construct_resource_map(
|
||||
resource=ProjectTagsResource,
|
||||
url='/projects/<string:project_id>/tags',
|
||||
resource_kwargs={},
|
||||
rel='project_tags',
|
||||
path_vars={
|
||||
'project_id': json_home.Parameters.PROJECT_ID}
|
||||
),
|
||||
ks_flask.construct_resource_map(
|
||||
resource=ProjectTagResource,
|
||||
url='/projects/<string:project_id>/tags/<string:value>',
|
||||
resource_kwargs={},
|
||||
rel='project_tags',
|
||||
path_vars={
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
'value': json_home.Parameters.TAG_VALUE}
|
||||
),
|
||||
ks_flask.construct_resource_map(
|
||||
resource=ProjectUserGrantResource,
|
||||
url=('/projects/<string:project_id>/users/<string:user_id>/'
|
||||
'roles/<string:role_id>'),
|
||||
resource_kwargs={},
|
||||
rel='project_user_role',
|
||||
path_vars={
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
'role_id': json_home.Parameters.ROLE_ID
|
||||
},
|
||||
),
|
||||
ks_flask.construct_resource_map(
|
||||
resource=ProjectUserListGrantResource,
|
||||
url='/projects/<string:project_id>/users/<string:user_id>/roles',
|
||||
resource_kwargs={},
|
||||
rel='project_user_roles',
|
||||
path_vars={
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
'user_id': json_home.Parameters.USER_ID
|
||||
}
|
||||
),
|
||||
ks_flask.construct_resource_map(
|
||||
resource=ProjectGroupGrantResource,
|
||||
url=('/projects/<string:project_id>/groups/<string:group_id>/'
|
||||
'roles/<string:role_id>'),
|
||||
resource_kwargs={},
|
||||
rel='project_group_role',
|
||||
path_vars={
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
'group_id': json_home.Parameters.GROUP_ID,
|
||||
'role_id': json_home.Parameters.ROLE_ID
|
||||
},
|
||||
),
|
||||
ks_flask.construct_resource_map(
|
||||
resource=ProjectGroupListGrantResource,
|
||||
url='/projects/<string:project_id>/groups/<string:group_id>/roles',
|
||||
resource_kwargs={},
|
||||
rel='project_group_roles',
|
||||
path_vars={
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
'group_id': json_home.Parameters.GROUP_ID
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
APIs = (ProjectAPI,)
|
@ -12,5 +12,4 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystone.assignment import controllers # noqa
|
||||
from keystone.assignment.core import * # noqa
|
||||
|
@ -1,155 +0,0 @@
|
||||
# Copyright 2013 Metacloud, Inc.
|
||||
# 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 Assignment service."""
|
||||
|
||||
import functools
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
from keystone.common import controller
|
||||
from keystone.common import provider_api
|
||||
import keystone.conf
|
||||
from keystone import exception
|
||||
from keystone.i18n import _
|
||||
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
PROVIDERS = provider_api.ProviderAPIs
|
||||
|
||||
|
||||
class GrantAssignmentV3(controller.V3Controller):
|
||||
"""The V3 Grant Assignment APIs."""
|
||||
|
||||
collection_name = 'roles'
|
||||
member_name = 'role'
|
||||
|
||||
def __init__(self):
|
||||
super(GrantAssignmentV3, self).__init__()
|
||||
self.get_member_from_driver = PROVIDERS.role_api.get_role
|
||||
|
||||
def _require_domain_xor_project(self, domain_id, project_id):
|
||||
if domain_id and project_id:
|
||||
msg = _('Specify a domain or project, not both')
|
||||
raise exception.ValidationError(msg)
|
||||
if not domain_id and not project_id:
|
||||
msg = _('Specify one of domain or project')
|
||||
raise exception.ValidationError(msg)
|
||||
|
||||
def _require_user_xor_group(self, user_id, group_id):
|
||||
if user_id and group_id:
|
||||
msg = _('Specify a user or group, not both')
|
||||
raise exception.ValidationError(msg)
|
||||
if not user_id and not group_id:
|
||||
msg = _('Specify one of user or group')
|
||||
raise exception.ValidationError(msg)
|
||||
|
||||
def _check_if_inherited(self, context):
|
||||
return (context['path'].startswith('/OS-INHERIT') and
|
||||
context['path'].endswith('/inherited_to_projects'))
|
||||
|
||||
def _check_grant_protection(self, request, protection, role_id=None,
|
||||
user_id=None, group_id=None,
|
||||
domain_id=None, project_id=None,
|
||||
allow_non_existing=False):
|
||||
"""Check protection for role grant APIs.
|
||||
|
||||
The policy rule might want to inspect attributes of any of the entities
|
||||
involved in the grant. So we get these and pass them to the
|
||||
check_protection() handler in the controller.
|
||||
|
||||
"""
|
||||
ref = {}
|
||||
if role_id:
|
||||
ref['role'] = PROVIDERS.role_api.get_role(role_id)
|
||||
if user_id:
|
||||
try:
|
||||
ref['user'] = PROVIDERS.identity_api.get_user(user_id)
|
||||
except exception.UserNotFound:
|
||||
if not allow_non_existing:
|
||||
raise
|
||||
else:
|
||||
try:
|
||||
ref['group'] = PROVIDERS.identity_api.get_group(group_id)
|
||||
except exception.GroupNotFound:
|
||||
if not allow_non_existing:
|
||||
raise
|
||||
|
||||
# NOTE(lbragstad): This if/else check will need to be expanded in the
|
||||
# future to handle system hierarchies if that is implemented.
|
||||
if domain_id:
|
||||
ref['domain'] = PROVIDERS.resource_api.get_domain(domain_id)
|
||||
elif project_id:
|
||||
ref['project'] = PROVIDERS.resource_api.get_project(project_id)
|
||||
|
||||
self.check_protection(request, protection, ref)
|
||||
|
||||
@controller.protected(callback=_check_grant_protection)
|
||||
def create_grant(self, request, role_id, user_id=None,
|
||||
group_id=None, domain_id=None, project_id=None):
|
||||
"""Grant a role to a user or group on either a domain or project."""
|
||||
self._require_domain_xor_project(domain_id, project_id)
|
||||
self._require_user_xor_group(user_id, group_id)
|
||||
|
||||
inherited_to_projects = self._check_if_inherited(request.context_dict)
|
||||
PROVIDERS.assignment_api.create_grant(
|
||||
role_id, user_id=user_id, group_id=group_id, domain_id=domain_id,
|
||||
project_id=project_id, inherited_to_projects=inherited_to_projects,
|
||||
context=request.context_dict)
|
||||
|
||||
@controller.protected(callback=_check_grant_protection)
|
||||
def list_grants(self, request, user_id=None,
|
||||
group_id=None, domain_id=None, project_id=None):
|
||||
"""List roles granted to user/group on either a domain or project."""
|
||||
self._require_domain_xor_project(domain_id, project_id)
|
||||
self._require_user_xor_group(user_id, group_id)
|
||||
|
||||
inherited_to_projects = self._check_if_inherited(request.context_dict)
|
||||
refs = PROVIDERS.assignment_api.list_grants(
|
||||
user_id=user_id, group_id=group_id, domain_id=domain_id,
|
||||
project_id=project_id, inherited_to_projects=inherited_to_projects
|
||||
)
|
||||
return GrantAssignmentV3.wrap_collection(request.context_dict, refs)
|
||||
|
||||
@controller.protected(callback=_check_grant_protection)
|
||||
def check_grant(self, request, role_id, user_id=None,
|
||||
group_id=None, domain_id=None, project_id=None):
|
||||
"""Check if a role has been granted on either a domain or project."""
|
||||
self._require_domain_xor_project(domain_id, project_id)
|
||||
self._require_user_xor_group(user_id, group_id)
|
||||
|
||||
inherited_to_projects = self._check_if_inherited(request.context_dict)
|
||||
PROVIDERS.assignment_api.get_grant(
|
||||
role_id, user_id=user_id, group_id=group_id, domain_id=domain_id,
|
||||
project_id=project_id, inherited_to_projects=inherited_to_projects
|
||||
)
|
||||
|
||||
# NOTE(lbragstad): This will allow users to clean up role assignments
|
||||
# from the backend in the event the user was removed prior to the role
|
||||
# assignment being removed.
|
||||
@controller.protected(callback=functools.partial(
|
||||
_check_grant_protection, allow_non_existing=True))
|
||||
def revoke_grant(self, request, role_id, user_id=None,
|
||||
group_id=None, domain_id=None, project_id=None):
|
||||
"""Revoke a role from user/group on either a domain or project."""
|
||||
self._require_domain_xor_project(domain_id, project_id)
|
||||
self._require_user_xor_group(user_id, group_id)
|
||||
|
||||
inherited_to_projects = self._check_if_inherited(request.context_dict)
|
||||
PROVIDERS.assignment_api.delete_grant(
|
||||
role_id, user_id=user_id, group_id=group_id, domain_id=domain_id,
|
||||
project_id=project_id, inherited_to_projects=inherited_to_projects,
|
||||
context=request.context_dict)
|
@ -285,7 +285,7 @@ class Manager(manager.Manager):
|
||||
@notifications.role_assignment('created')
|
||||
def create_grant(self, role_id, user_id=None, group_id=None,
|
||||
domain_id=None, project_id=None,
|
||||
inherited_to_projects=False, context=None,
|
||||
inherited_to_projects=False,
|
||||
initiator=None):
|
||||
role = PROVIDERS.role_api.get_role(role_id)
|
||||
if domain_id:
|
||||
@ -336,7 +336,7 @@ class Manager(manager.Manager):
|
||||
@notifications.role_assignment('deleted')
|
||||
def delete_grant(self, role_id, user_id=None, group_id=None,
|
||||
domain_id=None, project_id=None,
|
||||
inherited_to_projects=False, context=None,
|
||||
inherited_to_projects=False,
|
||||
initiator=None):
|
||||
|
||||
# check if role exist before any processing
|
||||
|
@ -1,71 +0,0 @@
|
||||
# Copyright 2013 Metacloud, Inc.
|
||||
# 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 Assignment service."""
|
||||
|
||||
from keystone.assignment import controllers
|
||||
from keystone.common import json_home
|
||||
from keystone.common import wsgi
|
||||
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
_path_prefixes = ('projects',)
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
|
||||
grant_controller = controllers.GrantAssignmentV3()
|
||||
self._add_resource(
|
||||
mapper, grant_controller,
|
||||
path='/projects/{project_id}/users/{user_id}/roles/{role_id}',
|
||||
get_head_action='check_grant',
|
||||
put_action='create_grant',
|
||||
delete_action='revoke_grant',
|
||||
rel=json_home.build_v3_resource_relation('project_user_role'),
|
||||
path_vars={
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
'role_id': json_home.Parameters.ROLE_ID,
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, grant_controller,
|
||||
path='/projects/{project_id}/groups/{group_id}/roles/{role_id}',
|
||||
get_head_action='check_grant',
|
||||
put_action='create_grant',
|
||||
delete_action='revoke_grant',
|
||||
rel=json_home.build_v3_resource_relation('project_group_role'),
|
||||
path_vars={
|
||||
'group_id': json_home.Parameters.GROUP_ID,
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
'role_id': json_home.Parameters.ROLE_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, grant_controller,
|
||||
path='/projects/{project_id}/users/{user_id}/roles',
|
||||
get_head_action='list_grants',
|
||||
rel=json_home.build_v3_resource_relation('project_user_roles'),
|
||||
path_vars={
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, grant_controller,
|
||||
path='/projects/{project_id}/groups/{group_id}/roles',
|
||||
get_head_action='list_grants',
|
||||
rel=json_home.build_v3_resource_relation('project_group_roles'),
|
||||
path_vars={
|
||||
'group_id': json_home.Parameters.GROUP_ID,
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
})
|
@ -636,16 +636,7 @@ class CadfRoleAssignmentNotificationWrapper(object):
|
||||
call_args = inspect.getcallargs(
|
||||
f, wrapped_self, role_id, *args, **kwargs)
|
||||
inherited = call_args['inherited_to_projects']
|
||||
context = call_args['context']
|
||||
|
||||
# TODO(gagehugo): Once all of the APIs for grant creation
|
||||
# and deletion are moved over to flask, get rid of checking
|
||||
# context here and only grab the initiator from the
|
||||
# passed in value
|
||||
if context:
|
||||
initiator = _get_request_audit_info(context)
|
||||
else:
|
||||
initiator = call_args.get('initiator', None)
|
||||
initiator = call_args.get('initiator', None)
|
||||
target = resource.Resource(typeURI=taxonomy.ACCOUNT_USER)
|
||||
|
||||
audit_kwargs = {}
|
||||
|
@ -10,5 +10,4 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystone.resource import controllers # noqa
|
||||
from keystone.resource.core import * # noqa
|
||||
|
@ -1,226 +0,0 @@
|
||||
# Copyright 2013 Metacloud, Inc.
|
||||
# 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 Resource service."""
|
||||
|
||||
from six.moves import http_client
|
||||
|
||||
from keystone.common import controller
|
||||
from keystone.common import provider_api
|
||||
from keystone.common import validation
|
||||
from keystone.common import wsgi
|
||||
import keystone.conf
|
||||
from keystone import exception
|
||||
from keystone.i18n import _
|
||||
from keystone.resource import schema
|
||||
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
PROVIDERS = provider_api.ProviderAPIs
|
||||
|
||||
|
||||
class DomainV3(controller.V3Controller):
|
||||
collection_name = 'domains'
|
||||
member_name = 'domain'
|
||||
|
||||
def __init__(self):
|
||||
super(DomainV3, self).__init__()
|
||||
self.get_member_from_driver = PROVIDERS.resource_api.get_domain
|
||||
|
||||
|
||||
class ProjectV3(controller.V3Controller):
|
||||
collection_name = 'projects'
|
||||
member_name = 'project'
|
||||
|
||||
def __init__(self):
|
||||
super(ProjectV3, self).__init__()
|
||||
self.get_member_from_driver = PROVIDERS.resource_api.get_project
|
||||
|
||||
@controller.protected()
|
||||
def create_project(self, request, project):
|
||||
validation.lazy_validate(schema.project_create, project)
|
||||
ref = self._assign_unique_id(self._normalize_dict(project))
|
||||
|
||||
if not ref.get('is_domain'):
|
||||
ref = self._normalize_domain_id(request, ref)
|
||||
# Our API requires that you specify the location in the hierarchy
|
||||
# unambiguously. This could be by parent_id or, if it is a top level
|
||||
# project, just by providing a domain_id.
|
||||
if not ref.get('parent_id'):
|
||||
ref['parent_id'] = ref.get('domain_id')
|
||||
|
||||
try:
|
||||
ref = PROVIDERS.resource_api.create_project(
|
||||
ref['id'],
|
||||
ref,
|
||||
initiator=request.audit_initiator)
|
||||
except (exception.DomainNotFound, exception.ProjectNotFound) as e:
|
||||
raise exception.ValidationError(e)
|
||||
return ProjectV3.wrap_member(request.context_dict, ref)
|
||||
|
||||
@controller.filterprotected('domain_id', 'enabled', 'name',
|
||||
'parent_id', 'is_domain')
|
||||
def list_projects(self, request, filters):
|
||||
hints = ProjectV3.build_driver_hints(request, filters)
|
||||
# If 'is_domain' has not been included as a query, we default it to
|
||||
# False (which in query terms means '0')
|
||||
if 'is_domain' not in request.params:
|
||||
hints.add_filter('is_domain', '0')
|
||||
# If any tags filters are passed in when listing projects, add them
|
||||
# to the hint filters
|
||||
tag_params = ['tags', 'tags-any', 'not-tags', 'not-tags-any']
|
||||
for t in tag_params:
|
||||
if t in request.params:
|
||||
hints.add_filter(t, request.params[t])
|
||||
refs = PROVIDERS.resource_api.list_projects(hints=hints)
|
||||
return ProjectV3.wrap_collection(request.context_dict,
|
||||
refs, hints=hints)
|
||||
|
||||
def _expand_project_ref(self, request, ref):
|
||||
params = request.params
|
||||
context = request.context_dict
|
||||
|
||||
parents_as_list = 'parents_as_list' in params and (
|
||||
self.query_filter_is_true(params['parents_as_list']))
|
||||
parents_as_ids = 'parents_as_ids' in params and (
|
||||
self.query_filter_is_true(params['parents_as_ids']))
|
||||
|
||||
subtree_as_list = 'subtree_as_list' in params and (
|
||||
self.query_filter_is_true(params['subtree_as_list']))
|
||||
subtree_as_ids = 'subtree_as_ids' in params and (
|
||||
self.query_filter_is_true(params['subtree_as_ids']))
|
||||
include_limits = 'include_limits' in params and (
|
||||
self.query_filter_is_true(params['include_limits']))
|
||||
|
||||
# parents_as_list and parents_as_ids are mutually exclusive
|
||||
if parents_as_list and parents_as_ids:
|
||||
msg = _('Cannot use parents_as_list and parents_as_ids query '
|
||||
'params at the same time.')
|
||||
raise exception.ValidationError(msg)
|
||||
|
||||
# subtree_as_list and subtree_as_ids are mutually exclusive
|
||||
if subtree_as_list and subtree_as_ids:
|
||||
msg = _('Cannot use subtree_as_list and subtree_as_ids query '
|
||||
'params at the same time.')
|
||||
raise exception.ValidationError(msg)
|
||||
|
||||
if parents_as_list:
|
||||
parents = PROVIDERS.resource_api.list_project_parents(
|
||||
ref['id'], request.context.user_id, include_limits)
|
||||
ref['parents'] = [ProjectV3.wrap_member(context, p)
|
||||
for p in parents]
|
||||
elif parents_as_ids:
|
||||
ref['parents'] = PROVIDERS.resource_api.get_project_parents_as_ids(
|
||||
ref
|
||||
)
|
||||
|
||||
if subtree_as_list:
|
||||
subtree = PROVIDERS.resource_api.list_projects_in_subtree(
|
||||
ref['id'], request.context.user_id, include_limits)
|
||||
ref['subtree'] = [ProjectV3.wrap_member(context, p)
|
||||
for p in subtree]
|
||||
elif subtree_as_ids:
|
||||
ref['subtree'] = (
|
||||
PROVIDERS.resource_api.get_projects_in_subtree_as_ids(
|
||||
ref['id']
|
||||
)
|
||||
)
|
||||
|
||||
@controller.protected()
|
||||
def get_project(self, request, project_id):
|
||||
ref = PROVIDERS.resource_api.get_project(project_id)
|
||||
self._expand_project_ref(request, ref)
|
||||
return ProjectV3.wrap_member(request.context_dict, ref)
|
||||
|
||||
@controller.protected()
|
||||
def update_project(self, request, project_id, project):
|
||||
validation.lazy_validate(schema.project_update, project)
|
||||
self._require_matching_id(project_id, project)
|
||||
ref = PROVIDERS.resource_api.update_project(
|
||||
project_id,
|
||||
project,
|
||||
initiator=request.audit_initiator)
|
||||
return ProjectV3.wrap_member(request.context_dict, ref)
|
||||
|
||||
@controller.protected()
|
||||
def delete_project(self, request, project_id):
|
||||
return PROVIDERS.resource_api.delete_project(
|
||||
project_id,
|
||||
initiator=request.audit_initiator)
|
||||
|
||||
|
||||
class ProjectTagV3(controller.V3Controller):
|
||||
collection_name = 'projects'
|
||||
member_name = 'tags'
|
||||
|
||||
def __init__(self):
|
||||
super(ProjectTagV3, self).__init__()
|
||||
self.get_member_from_driver = PROVIDERS.resource_api.get_project_tag
|
||||
|
||||
@classmethod
|
||||
def wrap_member(cls, context, ref):
|
||||
# NOTE(gagehugo): Overriding this due to how the common controller
|
||||
# expects the ref to have an id, which for tags it does not.
|
||||
new_ref = {'links': {'self': cls.full_url(context)}}
|
||||
new_ref[cls.member_name] = (ref or [])
|
||||
return new_ref
|
||||
|
||||
@classmethod
|
||||
def wrap_header(cls, context, query):
|
||||
# NOTE(gagehugo: The API spec for tags has a specific guideline for
|
||||
# what to return when adding a single tag. This wrapper handles
|
||||
# returning the specified url in the header while the body is empty.
|
||||
context['environment']['QUERY_STRING'] = '/' + query
|
||||
url = cls.full_url(context)
|
||||
headers = [('Location', url.replace('?', ''))]
|
||||
status = (http_client.CREATED,
|
||||
http_client.responses[http_client.CREATED])
|
||||
return wsgi.render_response(status=status, headers=headers)
|
||||
|
||||
@controller.protected()
|
||||
def create_project_tag(self, request, project_id, value):
|
||||
validation.lazy_validate(schema.project_tag_create, value)
|
||||
# Check if we will exceed the max number of tags on this project
|
||||
tags = PROVIDERS.resource_api.list_project_tags(project_id)
|
||||
tags.append(value)
|
||||
validation.lazy_validate(schema.project_tags_update, tags)
|
||||
PROVIDERS.resource_api.create_project_tag(
|
||||
project_id, value, initiator=request.audit_initiator)
|
||||
query = '/'.join((project_id, 'tags', value))
|
||||
return ProjectTagV3.wrap_header(request.context_dict, query)
|
||||
|
||||
@controller.protected()
|
||||
def get_project_tag(self, request, project_id, value):
|
||||
PROVIDERS.resource_api.get_project_tag(project_id, value)
|
||||
|
||||
@controller.protected()
|
||||
def delete_project_tag(self, request, project_id, value):
|
||||
PROVIDERS.resource_api.delete_project_tag(project_id, value)
|
||||
|
||||
@controller.protected()
|
||||
def list_project_tags(self, request, project_id):
|
||||
ref = PROVIDERS.resource_api.list_project_tags(project_id)
|
||||
return ProjectTagV3.wrap_member(request.context_dict, ref)
|
||||
|
||||
@controller.protected()
|
||||
def update_project_tags(self, request, project_id, tags):
|
||||
validation.lazy_validate(schema.project_tags_update, tags)
|
||||
ref = PROVIDERS.resource_api.update_project_tags(
|
||||
project_id, tags, initiator=request.audit_initiator)
|
||||
return ProjectTagV3.wrap_member(request.context_dict, ref)
|
||||
|
||||
@controller.protected()
|
||||
def delete_project_tags(self, request, project_id):
|
||||
PROVIDERS.resource_api.update_project_tags(project_id, [])
|
@ -1,59 +0,0 @@
|
||||
# Copyright 2013 Metacloud, Inc.
|
||||
# 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 Resource service."""
|
||||
|
||||
from keystone.common import json_home
|
||||
from keystone.common import router
|
||||
from keystone.common import wsgi
|
||||
from keystone.resource import controllers
|
||||
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
_path_prefixes = ('projects')
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
tag_controller = controllers.ProjectTagV3()
|
||||
|
||||
routers.append(
|
||||
router.Router(controllers.ProjectV3(),
|
||||
'projects', 'project',
|
||||
resource_descriptions=self.v3_resources))
|
||||
|
||||
self._add_resource(
|
||||
mapper, tag_controller,
|
||||
path='/projects/{project_id}/tags',
|
||||
get_head_action='list_project_tags',
|
||||
put_action='update_project_tags',
|
||||
delete_action='delete_project_tags',
|
||||
rel=json_home.build_v3_resource_relation(
|
||||
'project_tags'),
|
||||
path_vars={
|
||||
'project_id': json_home.Parameters.PROJECT_ID
|
||||
})
|
||||
|
||||
self._add_resource(
|
||||
mapper, tag_controller,
|
||||
path='/projects/{project_id}/tags/{value}',
|
||||
get_head_action='get_project_tag',
|
||||
put_action='create_project_tag',
|
||||
delete_action='delete_project_tag',
|
||||
rel=json_home.build_v3_resource_relation(
|
||||
'project_tags'),
|
||||
path_vars={
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
'value': json_home.Parameters.TAG_VALUE
|
||||
})
|
@ -24,11 +24,9 @@ import routes
|
||||
import werkzeug.wsgi
|
||||
|
||||
import keystone.api
|
||||
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.resource import routers as resource_routers
|
||||
|
||||
# TODO(morgan): _MOVED_API_PREFIXES to be removed when the legacy dispatch
|
||||
# support is removed.
|
||||
@ -47,6 +45,7 @@ _MOVED_API_PREFIXES = frozenset(
|
||||
'OS-TRUST',
|
||||
'limits',
|
||||
'policy',
|
||||
'projects',
|
||||
'regions',
|
||||
'registered_limits',
|
||||
'role_assignments',
|
||||
@ -61,9 +60,7 @@ _MOVED_API_PREFIXES = frozenset(
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
ALL_API_ROUTERS = [assignment_routers,
|
||||
resource_routers,
|
||||
ec2_routers,
|
||||
ALL_API_ROUTERS = [ec2_routers,
|
||||
s3_routers]
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user