From 46380baeb76fc624bf8b286ce403656767c403cf Mon Sep 17 00:00:00 2001 From: Harry Rybacki Date: Tue, 18 Sep 2018 12:03:52 -0400 Subject: [PATCH] Convert projects API to Flask Convert the projects API to Flask native dispatching. Change-Id: I3406284acfb7950b701f6a98a3a173a427415f97 Co-Authored-By: Morgan Fainberg Partial-Bug: #1776504 --- keystone/api/__init__.py | 3 + keystone/api/projects.py | 522 +++++++++++++++++++++++++++ keystone/assignment/__init__.py | 1 - keystone/assignment/controllers.py | 155 -------- keystone/assignment/core.py | 4 +- keystone/assignment/routers.py | 71 ---- keystone/notifications.py | 11 +- keystone/resource/__init__.py | 1 - keystone/resource/controllers.py | 226 ------------ keystone/resource/routers.py | 59 --- keystone/server/flask/application.py | 7 +- 11 files changed, 530 insertions(+), 530 deletions(-) create mode 100644 keystone/api/projects.py delete mode 100644 keystone/assignment/controllers.py delete mode 100644 keystone/assignment/routers.py delete mode 100644 keystone/resource/controllers.py delete mode 100644 keystone/resource/routers.py diff --git a/keystone/api/__init__.py b/keystone/api/__init__.py index 134f80046a..24d6222832 100644 --- a/keystone/api/__init__.py +++ b/keystone/api/__init__.py @@ -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, diff --git a/keystone/api/projects.py b/keystone/api/projects.py new file mode 100644 index 0000000000..95900d80c5 --- /dev/null +++ b/keystone/api/projects.py @@ -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//tags', + resource_kwargs={}, + rel='project_tags', + path_vars={ + 'project_id': json_home.Parameters.PROJECT_ID} + ), + ks_flask.construct_resource_map( + resource=ProjectTagResource, + url='/projects//tags/', + 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//users//' + 'roles/'), + 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//users//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//groups//' + 'roles/'), + 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//groups//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,) diff --git a/keystone/assignment/__init__.py b/keystone/assignment/__init__.py index 4aa04ee650..e2e831c6b2 100644 --- a/keystone/assignment/__init__.py +++ b/keystone/assignment/__init__.py @@ -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 diff --git a/keystone/assignment/controllers.py b/keystone/assignment/controllers.py deleted file mode 100644 index 021cd64b3d..0000000000 --- a/keystone/assignment/controllers.py +++ /dev/null @@ -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) diff --git a/keystone/assignment/core.py b/keystone/assignment/core.py index c9f90de5ee..e545f000b7 100644 --- a/keystone/assignment/core.py +++ b/keystone/assignment/core.py @@ -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 diff --git a/keystone/assignment/routers.py b/keystone/assignment/routers.py deleted file mode 100644 index 6ca378564b..0000000000 --- a/keystone/assignment/routers.py +++ /dev/null @@ -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, - }) diff --git a/keystone/notifications.py b/keystone/notifications.py index 3f522aac58..cdcd8a2365 100644 --- a/keystone/notifications.py +++ b/keystone/notifications.py @@ -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 = {} diff --git a/keystone/resource/__init__.py b/keystone/resource/__init__.py index 7f879f4b67..c9ca13901d 100644 --- a/keystone/resource/__init__.py +++ b/keystone/resource/__init__.py @@ -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 diff --git a/keystone/resource/controllers.py b/keystone/resource/controllers.py deleted file mode 100644 index 476d5a4fda..0000000000 --- a/keystone/resource/controllers.py +++ /dev/null @@ -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, []) diff --git a/keystone/resource/routers.py b/keystone/resource/routers.py deleted file mode 100644 index 17a7a818a4..0000000000 --- a/keystone/resource/routers.py +++ /dev/null @@ -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 - }) diff --git a/keystone/server/flask/application.py b/keystone/server/flask/application.py index de04f3ca6e..65d6847073 100644 --- a/keystone/server/flask/application.py +++ b/keystone/server/flask/application.py @@ -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]