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_revoke
|
||||||
from keystone.api import os_simple_cert
|
from keystone.api import os_simple_cert
|
||||||
from keystone.api import policy
|
from keystone.api import policy
|
||||||
|
from keystone.api import projects
|
||||||
from keystone.api import regions
|
from keystone.api import regions
|
||||||
from keystone.api import registered_limits
|
from keystone.api import registered_limits
|
||||||
from keystone.api import role_assignments
|
from keystone.api import role_assignments
|
||||||
@ -49,6 +50,7 @@ __all__ = (
|
|||||||
'os_revoke',
|
'os_revoke',
|
||||||
'os_simple_cert',
|
'os_simple_cert',
|
||||||
'policy',
|
'policy',
|
||||||
|
'projects',
|
||||||
'regions',
|
'regions',
|
||||||
'registered_limits',
|
'registered_limits',
|
||||||
'role_assignments',
|
'role_assignments',
|
||||||
@ -75,6 +77,7 @@ __apis__ = (
|
|||||||
os_revoke,
|
os_revoke,
|
||||||
os_simple_cert,
|
os_simple_cert,
|
||||||
policy,
|
policy,
|
||||||
|
projects,
|
||||||
regions,
|
regions,
|
||||||
registered_limits,
|
registered_limits,
|
||||||
role_assignments,
|
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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from keystone.assignment import controllers # noqa
|
|
||||||
from keystone.assignment.core import * # 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')
|
@notifications.role_assignment('created')
|
||||||
def create_grant(self, role_id, user_id=None, group_id=None,
|
def create_grant(self, role_id, user_id=None, group_id=None,
|
||||||
domain_id=None, project_id=None,
|
domain_id=None, project_id=None,
|
||||||
inherited_to_projects=False, context=None,
|
inherited_to_projects=False,
|
||||||
initiator=None):
|
initiator=None):
|
||||||
role = PROVIDERS.role_api.get_role(role_id)
|
role = PROVIDERS.role_api.get_role(role_id)
|
||||||
if domain_id:
|
if domain_id:
|
||||||
@ -336,7 +336,7 @@ class Manager(manager.Manager):
|
|||||||
@notifications.role_assignment('deleted')
|
@notifications.role_assignment('deleted')
|
||||||
def delete_grant(self, role_id, user_id=None, group_id=None,
|
def delete_grant(self, role_id, user_id=None, group_id=None,
|
||||||
domain_id=None, project_id=None,
|
domain_id=None, project_id=None,
|
||||||
inherited_to_projects=False, context=None,
|
inherited_to_projects=False,
|
||||||
initiator=None):
|
initiator=None):
|
||||||
|
|
||||||
# check if role exist before any processing
|
# 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(
|
call_args = inspect.getcallargs(
|
||||||
f, wrapped_self, role_id, *args, **kwargs)
|
f, wrapped_self, role_id, *args, **kwargs)
|
||||||
inherited = call_args['inherited_to_projects']
|
inherited = call_args['inherited_to_projects']
|
||||||
context = call_args['context']
|
initiator = call_args.get('initiator', None)
|
||||||
|
|
||||||
# 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)
|
|
||||||
target = resource.Resource(typeURI=taxonomy.ACCOUNT_USER)
|
target = resource.Resource(typeURI=taxonomy.ACCOUNT_USER)
|
||||||
|
|
||||||
audit_kwargs = {}
|
audit_kwargs = {}
|
||||||
|
@ -10,5 +10,4 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from keystone.resource import controllers # noqa
|
|
||||||
from keystone.resource.core import * # 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 werkzeug.wsgi
|
||||||
|
|
||||||
import keystone.api
|
import keystone.api
|
||||||
from keystone.assignment import routers as assignment_routers
|
|
||||||
from keystone.common import wsgi as keystone_wsgi
|
from keystone.common import wsgi as keystone_wsgi
|
||||||
from keystone.contrib.ec2 import routers as ec2_routers
|
from keystone.contrib.ec2 import routers as ec2_routers
|
||||||
from keystone.contrib.s3 import routers as s3_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
|
# TODO(morgan): _MOVED_API_PREFIXES to be removed when the legacy dispatch
|
||||||
# support is removed.
|
# support is removed.
|
||||||
@ -47,6 +45,7 @@ _MOVED_API_PREFIXES = frozenset(
|
|||||||
'OS-TRUST',
|
'OS-TRUST',
|
||||||
'limits',
|
'limits',
|
||||||
'policy',
|
'policy',
|
||||||
|
'projects',
|
||||||
'regions',
|
'regions',
|
||||||
'registered_limits',
|
'registered_limits',
|
||||||
'role_assignments',
|
'role_assignments',
|
||||||
@ -61,9 +60,7 @@ _MOVED_API_PREFIXES = frozenset(
|
|||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
ALL_API_ROUTERS = [assignment_routers,
|
ALL_API_ROUTERS = [ec2_routers,
|
||||||
resource_routers,
|
|
||||||
ec2_routers,
|
|
||||||
s3_routers]
|
s3_routers]
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user