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:
Harry Rybacki 2018-09-18 12:03:52 -04:00 committed by Morgan Fainberg
parent 86f968163e
commit 46380baeb7
11 changed files with 530 additions and 530 deletions

View File

@ -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
View 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,)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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,
})

View File

@ -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 = {}

View File

@ -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

View File

@ -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, [])

View File

@ -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
})

View File

@ -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]