Move user and admin crud to core

Move the user_crud and admin_crud extensions for V2 to core.

Change-Id: If1b6bd354d05f5dafcbcd93b77b515b90522c1e7
bp: extension-to-core
This commit is contained in:
Morgan Fainberg 2016-01-31 20:25:26 -06:00
parent 6e4cf708c3
commit f75f7e872f
8 changed files with 410 additions and 341 deletions

View File

@ -18,12 +18,6 @@ use = egg:keystone#admin_token_auth
[filter:json_body]
use = egg:keystone#json_body
[filter:user_crud_extension]
use = egg:keystone#user_crud_extension
[filter:crud_extension]
use = egg:keystone#crud_extension
[filter:ec2_extension]
use = egg:keystone#ec2_extension
@ -51,12 +45,12 @@ use = egg:keystone#admin_service
[pipeline:public_api]
# The last item in this pipeline must be public_service or an equivalent
# application. It cannot be a filter.
pipeline = sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension user_crud_extension public_service
pipeline = sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension public_service
[pipeline:admin_api]
# The last item in this pipeline must be admin_service or an equivalent
# application. It cannot be a filter.
pipeline = sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension s3_extension crud_extension admin_service
pipeline = sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension s3_extension admin_service
[pipeline:api_v3]
# The last item in this pipeline must be service_v3 or an equivalent

View File

@ -12,229 +12,21 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystone import assignment
from keystone import catalog
from keystone.common import extension
from oslo_log import log
from oslo_log import versionutils
from keystone.common import wsgi
from keystone import identity
from keystone import resource
from keystone.i18n import _
extension.register_admin_extension(
'OS-KSADM', {
'name': 'OpenStack Keystone Admin',
'namespace': 'http://docs.openstack.org/identity/api/ext/'
'OS-KSADM/v1.0',
'alias': 'OS-KSADM',
'updated': '2013-07-11T17:14:00-00:00',
'description': 'OpenStack extensions to Keystone v2.0 API '
'enabling Administrative Operations.',
'links': [
{
'rel': 'describedby',
'type': 'text/html',
'href': 'http://developer.openstack.org/'
'api-ref-identity-v2-ext.html',
}
]})
LOG = log.getLogger(__name__)
class CrudExtension(wsgi.ExtensionRouter):
"""Previously known as the OS-KSADM extension.
Provides a bunch of CRUD operations for internal data types.
"""
def add_routes(self, mapper):
tenant_controller = resource.controllers.Tenant()
assignment_tenant_controller = (
assignment.controllers.TenantAssignment())
user_controller = identity.controllers.User()
role_controller = assignment.controllers.Role()
assignment_role_controller = assignment.controllers.RoleAssignmentV2()
service_controller = catalog.controllers.Service()
endpoint_controller = catalog.controllers.Endpoint()
# Tenant Operations
mapper.connect(
'/tenants',
controller=tenant_controller,
action='create_project',
conditions=dict(method=['POST']))
mapper.connect(
'/tenants/{tenant_id}',
controller=tenant_controller,
action='update_project',
conditions=dict(method=['PUT', 'POST']))
mapper.connect(
'/tenants/{tenant_id}',
controller=tenant_controller,
action='delete_project',
conditions=dict(method=['DELETE']))
mapper.connect(
'/tenants/{tenant_id}/users',
controller=assignment_tenant_controller,
action='get_project_users',
conditions=dict(method=['GET']))
# User Operations
mapper.connect(
'/users',
controller=user_controller,
action='get_users',
conditions=dict(method=['GET']))
mapper.connect(
'/users',
controller=user_controller,
action='create_user',
conditions=dict(method=['POST']))
# NOTE(termie): not in diablo
mapper.connect(
'/users/{user_id}',
controller=user_controller,
action='update_user',
conditions=dict(method=['PUT']))
mapper.connect(
'/users/{user_id}',
controller=user_controller,
action='delete_user',
conditions=dict(method=['DELETE']))
# COMPAT(diablo): the copy with no OS-KSADM is from diablo
mapper.connect(
'/users/{user_id}/password',
controller=user_controller,
action='set_user_password',
conditions=dict(method=['PUT']))
mapper.connect(
'/users/{user_id}/OS-KSADM/password',
controller=user_controller,
action='set_user_password',
conditions=dict(method=['PUT']))
# COMPAT(diablo): the copy with no OS-KSADM is from diablo
mapper.connect(
'/users/{user_id}/tenant',
controller=user_controller,
action='update_user',
conditions=dict(method=['PUT']))
mapper.connect(
'/users/{user_id}/OS-KSADM/tenant',
controller=user_controller,
action='update_user',
conditions=dict(method=['PUT']))
# COMPAT(diablo): the copy with no OS-KSADM is from diablo
mapper.connect(
'/users/{user_id}/enabled',
controller=user_controller,
action='set_user_enabled',
conditions=dict(method=['PUT']))
mapper.connect(
'/users/{user_id}/OS-KSADM/enabled',
controller=user_controller,
action='set_user_enabled',
conditions=dict(method=['PUT']))
# User Roles
mapper.connect(
'/users/{user_id}/roles/OS-KSADM/{role_id}',
controller=assignment_role_controller,
action='add_role_to_user',
conditions=dict(method=['PUT']))
mapper.connect(
'/users/{user_id}/roles/OS-KSADM/{role_id}',
controller=assignment_role_controller,
action='remove_role_from_user',
conditions=dict(method=['DELETE']))
# COMPAT(diablo): User Roles
mapper.connect(
'/users/{user_id}/roleRefs',
controller=assignment_role_controller,
action='get_role_refs',
conditions=dict(method=['GET']))
mapper.connect(
'/users/{user_id}/roleRefs',
controller=assignment_role_controller,
action='create_role_ref',
conditions=dict(method=['POST']))
mapper.connect(
'/users/{user_id}/roleRefs/{role_ref_id}',
controller=assignment_role_controller,
action='delete_role_ref',
conditions=dict(method=['DELETE']))
# User-Tenant Roles
mapper.connect(
'/tenants/{tenant_id}/users/{user_id}/roles/OS-KSADM/{role_id}',
controller=assignment_role_controller,
action='add_role_to_user',
conditions=dict(method=['PUT']))
mapper.connect(
'/tenants/{tenant_id}/users/{user_id}/roles/OS-KSADM/{role_id}',
controller=assignment_role_controller,
action='remove_role_from_user',
conditions=dict(method=['DELETE']))
# Service Operations
mapper.connect(
'/OS-KSADM/services',
controller=service_controller,
action='get_services',
conditions=dict(method=['GET']))
mapper.connect(
'/OS-KSADM/services',
controller=service_controller,
action='create_service',
conditions=dict(method=['POST']))
mapper.connect(
'/OS-KSADM/services/{service_id}',
controller=service_controller,
action='delete_service',
conditions=dict(method=['DELETE']))
mapper.connect(
'/OS-KSADM/services/{service_id}',
controller=service_controller,
action='get_service',
conditions=dict(method=['GET']))
# Endpoint Templates
mapper.connect(
'/endpoints',
controller=endpoint_controller,
action='get_endpoints',
conditions=dict(method=['GET']))
mapper.connect(
'/endpoints',
controller=endpoint_controller,
action='create_endpoint',
conditions=dict(method=['POST']))
mapper.connect(
'/endpoints/{endpoint_id}',
controller=endpoint_controller,
action='delete_endpoint',
conditions=dict(method=['DELETE']))
# Role Operations
mapper.connect(
'/OS-KSADM/roles',
controller=role_controller,
action='create_role',
conditions=dict(method=['POST']))
mapper.connect(
'/OS-KSADM/roles',
controller=role_controller,
action='get_roles',
conditions=dict(method=['GET']))
mapper.connect(
'/OS-KSADM/roles/{role_id}',
controller=role_controller,
action='get_role',
conditions=dict(method=['GET']))
mapper.connect(
'/OS-KSADM/roles/{role_id}',
controller=role_controller,
action='delete_role',
conditions=dict(method=['DELETE']))
class CrudExtension(wsgi.Middleware):
def __init__(self, application):
super(CrudExtension, self).__init__(application)
msg = _("Remove admin_crud_extension from the paste pipeline, the "
"admin_crud extension is now always available. Update"
"the [pipeline:admin_api] section in keystone-paste.ini "
"accordingly, as it will be removed in the O release.")
versionutils.report_deprecated_feature(LOG, msg)

View File

@ -12,123 +12,21 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import uuid
from oslo_log import log
from oslo_log import versionutils
from keystone.common import dependency
from keystone.common import extension
from keystone.common import wsgi
from keystone import exception
from keystone import identity
from keystone.models import token_model
from keystone.i18n import _
LOG = log.getLogger(__name__)
extension.register_public_extension(
'OS-KSCRUD', {
'name': 'OpenStack Keystone User CRUD',
'namespace': 'http://docs.openstack.org/identity/api/ext/'
'OS-KSCRUD/v1.0',
'alias': 'OS-KSCRUD',
'updated': '2013-07-07T12:00:0-00:00',
'description': 'OpenStack extensions to Keystone v2.0 API '
'enabling User Operations.',
'links': [
{
'rel': 'describedby',
'type': 'text/html',
'href': 'http://developer.openstack.org/'
'api-ref-identity-v2-ext.html',
}
]})
@dependency.requires('catalog_api', 'identity_api', 'resource_api',
'token_provider_api')
class UserController(identity.controllers.User):
def set_user_password(self, context, user_id, user):
token_id = context.get('token_id')
original_password = user.get('original_password')
token_data = self.token_provider_api.validate_token(token_id)
token_ref = token_model.KeystoneToken(token_id=token_id,
token_data=token_data)
if token_ref.user_id != user_id:
raise exception.Forbidden('Token belongs to another user')
if original_password is None:
raise exception.ValidationError(target='user',
attribute='original password')
try:
user_ref = self.identity_api.authenticate(
context,
user_id=token_ref.user_id,
password=original_password)
if not user_ref.get('enabled', True):
# NOTE(dolph): why can't you set a disabled user's password?
raise exception.Unauthorized('User is disabled')
except AssertionError:
raise exception.Unauthorized()
update_dict = {'password': user['password'], 'id': user_id}
admin_context = copy.copy(context)
admin_context['is_admin'] = True
super(UserController, self).set_user_password(admin_context,
user_id,
update_dict)
# Issue a new token based upon the original token data. This will
# always be a V2.0 token.
# TODO(morganfainberg): Add a mechanism to issue a new token directly
# from a token model so that this code can go away. This is likely
# not the norm as most cases do not need to yank apart a token to
# issue a new one.
new_token_ref = {}
metadata_ref = {}
roles_ref = None
new_token_ref['user'] = user_ref
if token_ref.bind:
new_token_ref['bind'] = token_ref.bind
if token_ref.project_id:
new_token_ref['tenant'] = self.resource_api.get_project(
token_ref.project_id)
if token_ref.role_names:
roles_ref = [dict(name=value)
for value in token_ref.role_names]
if token_ref.role_ids:
metadata_ref['roles'] = token_ref.role_ids
if token_ref.trust_id:
metadata_ref['trust'] = {
'id': token_ref.trust_id,
'trustee_user_id': token_ref.trustee_user_id}
new_token_ref['metadata'] = metadata_ref
new_token_ref['id'] = uuid.uuid4().hex
catalog_ref = self.catalog_api.get_catalog(user_id,
token_ref.project_id)
new_token_id, new_token_data = self.token_provider_api.issue_v2_token(
token_ref=new_token_ref, roles_ref=roles_ref,
catalog_ref=catalog_ref)
LOG.debug('TOKEN_REF %s', new_token_data)
return new_token_data
class CrudExtension(wsgi.ExtensionRouter):
"""Provides a subset of CRUD operations for internal data types."""
def add_routes(self, mapper):
user_controller = UserController()
mapper.connect('/OS-KSCRUD/users/{user_id}',
controller=user_controller,
action='set_user_password',
conditions=dict(method=['PATCH']))
class CrudExtension(wsgi.Middleware):
def __init__(self, application):
super(CrudExtension, self).__init__(application)
msg = _("Remove user_crud_extension from the paste pipeline, the "
"user_crud extension is now always available. Update"
"the [pipeline:public_api] section in keystone-paste.ini "
"accordingly, as it will be removed in the O release.")
versionutils.report_deprecated_feature(LOG, msg)

View File

View File

@ -0,0 +1,240 @@
# 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.
from keystone import assignment
from keystone import catalog
from keystone.common import extension
from keystone.common import wsgi
from keystone import identity
from keystone import resource
extension.register_admin_extension(
'OS-KSADM', {
'name': 'OpenStack Keystone Admin',
'namespace': 'http://docs.openstack.org/identity/api/ext/'
'OS-KSADM/v1.0',
'alias': 'OS-KSADM',
'updated': '2013-07-11T17:14:00-00:00',
'description': 'OpenStack extensions to Keystone v2.0 API '
'enabling Administrative Operations.',
'links': [
{
'rel': 'describedby',
'type': 'text/html',
'href': 'http://developer.openstack.org/'
'api-ref-identity-v2-ext.html',
}
]})
class Router(wsgi.ComposableRouter):
"""Previously known as the OS-KSADM extension.
Provides a bunch of CRUD operations for internal data types.
"""
def add_routes(self, mapper):
tenant_controller = resource.controllers.Tenant()
assignment_tenant_controller = (
assignment.controllers.TenantAssignment())
user_controller = identity.controllers.User()
role_controller = assignment.controllers.Role()
assignment_role_controller = assignment.controllers.RoleAssignmentV2()
service_controller = catalog.controllers.Service()
endpoint_controller = catalog.controllers.Endpoint()
# Tenant Operations
mapper.connect(
'/tenants',
controller=tenant_controller,
action='create_project',
conditions=dict(method=['POST']))
mapper.connect(
'/tenants/{tenant_id}',
controller=tenant_controller,
action='update_project',
conditions=dict(method=['PUT', 'POST']))
mapper.connect(
'/tenants/{tenant_id}',
controller=tenant_controller,
action='delete_project',
conditions=dict(method=['DELETE']))
mapper.connect(
'/tenants/{tenant_id}/users',
controller=assignment_tenant_controller,
action='get_project_users',
conditions=dict(method=['GET']))
# User Operations
mapper.connect(
'/users',
controller=user_controller,
action='get_users',
conditions=dict(method=['GET']))
mapper.connect(
'/users',
controller=user_controller,
action='create_user',
conditions=dict(method=['POST']))
# NOTE(termie): not in diablo
mapper.connect(
'/users/{user_id}',
controller=user_controller,
action='update_user',
conditions=dict(method=['PUT']))
mapper.connect(
'/users/{user_id}',
controller=user_controller,
action='delete_user',
conditions=dict(method=['DELETE']))
# COMPAT(diablo): the copy with no OS-KSADM is from diablo
mapper.connect(
'/users/{user_id}/password',
controller=user_controller,
action='set_user_password',
conditions=dict(method=['PUT']))
mapper.connect(
'/users/{user_id}/OS-KSADM/password',
controller=user_controller,
action='set_user_password',
conditions=dict(method=['PUT']))
# COMPAT(diablo): the copy with no OS-KSADM is from diablo
mapper.connect(
'/users/{user_id}/tenant',
controller=user_controller,
action='update_user',
conditions=dict(method=['PUT']))
mapper.connect(
'/users/{user_id}/OS-KSADM/tenant',
controller=user_controller,
action='update_user',
conditions=dict(method=['PUT']))
# COMPAT(diablo): the copy with no OS-KSADM is from diablo
mapper.connect(
'/users/{user_id}/enabled',
controller=user_controller,
action='set_user_enabled',
conditions=dict(method=['PUT']))
mapper.connect(
'/users/{user_id}/OS-KSADM/enabled',
controller=user_controller,
action='set_user_enabled',
conditions=dict(method=['PUT']))
# User Roles
mapper.connect(
'/users/{user_id}/roles/OS-KSADM/{role_id}',
controller=assignment_role_controller,
action='add_role_to_user',
conditions=dict(method=['PUT']))
mapper.connect(
'/users/{user_id}/roles/OS-KSADM/{role_id}',
controller=assignment_role_controller,
action='remove_role_from_user',
conditions=dict(method=['DELETE']))
# COMPAT(diablo): User Roles
mapper.connect(
'/users/{user_id}/roleRefs',
controller=assignment_role_controller,
action='get_role_refs',
conditions=dict(method=['GET']))
mapper.connect(
'/users/{user_id}/roleRefs',
controller=assignment_role_controller,
action='create_role_ref',
conditions=dict(method=['POST']))
mapper.connect(
'/users/{user_id}/roleRefs/{role_ref_id}',
controller=assignment_role_controller,
action='delete_role_ref',
conditions=dict(method=['DELETE']))
# User-Tenant Roles
mapper.connect(
'/tenants/{tenant_id}/users/{user_id}/roles/OS-KSADM/{role_id}',
controller=assignment_role_controller,
action='add_role_to_user',
conditions=dict(method=['PUT']))
mapper.connect(
'/tenants/{tenant_id}/users/{user_id}/roles/OS-KSADM/{role_id}',
controller=assignment_role_controller,
action='remove_role_from_user',
conditions=dict(method=['DELETE']))
# Service Operations
mapper.connect(
'/OS-KSADM/services',
controller=service_controller,
action='get_services',
conditions=dict(method=['GET']))
mapper.connect(
'/OS-KSADM/services',
controller=service_controller,
action='create_service',
conditions=dict(method=['POST']))
mapper.connect(
'/OS-KSADM/services/{service_id}',
controller=service_controller,
action='delete_service',
conditions=dict(method=['DELETE']))
mapper.connect(
'/OS-KSADM/services/{service_id}',
controller=service_controller,
action='get_service',
conditions=dict(method=['GET']))
# Endpoint Templates
mapper.connect(
'/endpoints',
controller=endpoint_controller,
action='get_endpoints',
conditions=dict(method=['GET']))
mapper.connect(
'/endpoints',
controller=endpoint_controller,
action='create_endpoint',
conditions=dict(method=['POST']))
mapper.connect(
'/endpoints/{endpoint_id}',
controller=endpoint_controller,
action='delete_endpoint',
conditions=dict(method=['DELETE']))
# Role Operations
mapper.connect(
'/OS-KSADM/roles',
controller=role_controller,
action='create_role',
conditions=dict(method=['POST']))
mapper.connect(
'/OS-KSADM/roles',
controller=role_controller,
action='get_roles',
conditions=dict(method=['GET']))
mapper.connect(
'/OS-KSADM/roles/{role_id}',
controller=role_controller,
action='get_role',
conditions=dict(method=['GET']))
mapper.connect(
'/OS-KSADM/roles/{role_id}',
controller=role_controller,
action='delete_role',
conditions=dict(method=['DELETE']))

View File

@ -0,0 +1,134 @@
# Copyright 2012 Red Hat, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import uuid
from oslo_log import log
from keystone.common import dependency
from keystone.common import extension
from keystone.common import wsgi
from keystone import exception
from keystone import identity
from keystone.models import token_model
LOG = log.getLogger(__name__)
extension.register_public_extension(
'OS-KSCRUD', {
'name': 'OpenStack Keystone User CRUD',
'namespace': 'http://docs.openstack.org/identity/api/ext/'
'OS-KSCRUD/v1.0',
'alias': 'OS-KSCRUD',
'updated': '2013-07-07T12:00:0-00:00',
'description': 'OpenStack extensions to Keystone v2.0 API '
'enabling User Operations.',
'links': [
{
'rel': 'describedby',
'type': 'text/html',
'href': 'http://developer.openstack.org/'
'api-ref-identity-v2-ext.html',
}
]})
@dependency.requires('catalog_api', 'identity_api', 'resource_api',
'token_provider_api')
class UserController(identity.controllers.User):
def set_user_password(self, context, user_id, user):
token_id = context.get('token_id')
original_password = user.get('original_password')
token_data = self.token_provider_api.validate_token(token_id)
token_ref = token_model.KeystoneToken(token_id=token_id,
token_data=token_data)
if token_ref.user_id != user_id:
raise exception.Forbidden('Token belongs to another user')
if original_password is None:
raise exception.ValidationError(target='user',
attribute='original password')
try:
user_ref = self.identity_api.authenticate(
context,
user_id=token_ref.user_id,
password=original_password)
if not user_ref.get('enabled', True):
# NOTE(dolph): why can't you set a disabled user's password?
raise exception.Unauthorized('User is disabled')
except AssertionError:
raise exception.Unauthorized()
update_dict = {'password': user['password'], 'id': user_id}
admin_context = copy.copy(context)
admin_context['is_admin'] = True
super(UserController, self).set_user_password(admin_context,
user_id,
update_dict)
# Issue a new token based upon the original token data. This will
# always be a V2.0 token.
# TODO(morganfainberg): Add a mechanism to issue a new token directly
# from a token model so that this code can go away. This is likely
# not the norm as most cases do not need to yank apart a token to
# issue a new one.
new_token_ref = {}
metadata_ref = {}
roles_ref = None
new_token_ref['user'] = user_ref
if token_ref.bind:
new_token_ref['bind'] = token_ref.bind
if token_ref.project_id:
new_token_ref['tenant'] = self.resource_api.get_project(
token_ref.project_id)
if token_ref.role_names:
roles_ref = [dict(name=value)
for value in token_ref.role_names]
if token_ref.role_ids:
metadata_ref['roles'] = token_ref.role_ids
if token_ref.trust_id:
metadata_ref['trust'] = {
'id': token_ref.trust_id,
'trustee_user_id': token_ref.trustee_user_id}
new_token_ref['metadata'] = metadata_ref
new_token_ref['id'] = uuid.uuid4().hex
catalog_ref = self.catalog_api.get_catalog(user_id,
token_ref.project_id)
new_token_id, new_token_data = self.token_provider_api.issue_v2_token(
token_ref=new_token_ref, roles_ref=roles_ref,
catalog_ref=catalog_ref)
LOG.debug('TOKEN_REF %s', new_token_data)
return new_token_data
class Router(wsgi.ComposableRouter):
"""Provides a subset of CRUD operations for internal data types."""
def add_routes(self, mapper):
user_controller = UserController()
mapper.connect('/OS-KSCRUD/users/{user_id}',
controller=user_controller,
action='set_user_password',
conditions=dict(method=['PATCH']))

View File

@ -36,6 +36,8 @@ from keystone.revoke import routers as revoke_routers
from keystone.token import _simple_cert as simple_cert_ext
from keystone.token import routers as token_routers
from keystone.trust import routers as trust_routers
from keystone.v2_crud import admin_crud
from keystone.v2_crud import user_crud
from keystone.version import controllers
from keystone.version import routers
@ -85,6 +87,7 @@ def public_app_factory(global_conf, **local_conf):
return wsgi.ComposingRouter(routes.Mapper(),
[assignment_routers.Public(),
token_routers.Router(),
user_crud.Router(),
routers.VersionV2('public'),
routers.Extension(False)])
@ -98,6 +101,7 @@ def admin_app_factory(global_conf, **local_conf):
assignment_routers.Admin(),
token_routers.Router(),
resource_routers.Admin(),
admin_crud.Router(),
routers.VersionV2('admin'),
routers.Extension()])

View File

@ -8,6 +8,13 @@ upgrade:
and ``[filter:revoke_extension]``. See the sample `keystone-paste.ini
<https://git.openstack.org/cgit/openstack/keystone/tree/etc/keystone-paste.ini>`_
file for guidance.
- >
The ``keystone-paste.ini`` file must be updated to remove extension filters,
and their use in ``[pipeline:public_api]`` and ``[pipeline:admin_api]`` pipelines.
Remove the following filters: ``[filter:user_crud_extension]``,
``[filter:crud_extension]``. See the sample `keystone-paste.ini
<https://git.openstack.org/cgit/openstack/keystone/tree/etc/keystone-paste.ini>`_
file for guidance.
other:
- >
[`blueprint move-extensions <https://blueprints.launchpad.net/keystone/+spec/move-extensions>`_]