Remove v2.0 identity APIs
bp removed-as-of-queens Change-Id: Icfd519f527de0e3945a858ce7f87ddadef766faa
This commit is contained in:
parent
c83d139273
commit
75f24c628b
@ -1,231 +0,0 @@
|
||||
.. -*- rst -*-
|
||||
|
||||
=====
|
||||
Users
|
||||
=====
|
||||
|
||||
|
||||
List user global roles
|
||||
======================
|
||||
|
||||
.. rest_method:: GET /v2.0/users/{userId}/roles
|
||||
|
||||
Lists global roles for a user. Excludes tenant roles.
|
||||
|
||||
Normal response codes: 200,203
|
||||
Error response codes: 413,405,404,403,401,400,503
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- userId: user_id_path
|
||||
|
||||
Response Parameters
|
||||
-------------------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- roles: roles
|
||||
- roles_links: roles_links
|
||||
- description: role_description
|
||||
- name: role_name
|
||||
- id: role_id
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
||||
.. literalinclude:: samples/admin/roles-list-response.json
|
||||
:language: javascript
|
||||
|
||||
|
||||
Create user (admin endpoint)
|
||||
============================
|
||||
|
||||
.. rest_method:: POST /v2.0/users
|
||||
|
||||
Creates a user.
|
||||
|
||||
Normal response codes: 201
|
||||
Error response codes: 413,415,405,404,403,401,400,503,409
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- user: user
|
||||
- tenantId: tenant_id_path
|
||||
- password: user_password
|
||||
- enabled: user_enabled
|
||||
- email: user_email
|
||||
- name: user_name
|
||||
- username: username_request
|
||||
|
||||
Request Example
|
||||
---------------
|
||||
|
||||
.. literalinclude:: samples/admin/user-create-request.json
|
||||
:language: javascript
|
||||
|
||||
Response Parameters
|
||||
-------------------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- user: user
|
||||
- enabled: user_enabled
|
||||
- email: user_email
|
||||
- name: user_name
|
||||
- username: username
|
||||
- id: user_id
|
||||
|
||||
|
||||
List users (admin endpoint)
|
||||
===========================
|
||||
|
||||
.. rest_method:: GET /v2.0/users
|
||||
|
||||
Lists all users.
|
||||
|
||||
To show detailed information about a user by name, include the
|
||||
``name`` query parameter in the request.
|
||||
|
||||
Normal response codes: 200,203
|
||||
Error response codes: 413,405,404,403,401,400,503
|
||||
|
||||
Response Parameters
|
||||
-------------------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- users: users
|
||||
- enabled: user_enabled
|
||||
- id: user_id
|
||||
- email: user_email
|
||||
- name: user_name
|
||||
- username: username
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
||||
.. literalinclude:: samples/admin/users-list-response.json
|
||||
:language: javascript
|
||||
|
||||
|
||||
Update user (admin endpoint)
|
||||
============================
|
||||
|
||||
.. rest_method:: PUT /v2.0/users/{userId}
|
||||
|
||||
Updates a user.
|
||||
|
||||
Normal response codes: 201
|
||||
Error response codes: 413,415,405,404,403,401,400,503,409
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- userId: user_id_path
|
||||
- user: user
|
||||
- enabled: user_enabled
|
||||
- email: user_email
|
||||
- name: user_name
|
||||
|
||||
Request Example
|
||||
---------------
|
||||
|
||||
.. literalinclude:: samples/admin/user-update-request.json
|
||||
:language: javascript
|
||||
|
||||
Response Parameters
|
||||
-------------------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- user: user
|
||||
- enabled: user_enabled
|
||||
- email: user_email
|
||||
- name: user_name
|
||||
- username: username
|
||||
- id: user_id
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
||||
.. literalinclude:: samples/admin/user-update-response.json
|
||||
:language: javascript
|
||||
|
||||
|
||||
Delete user (admin endpoint)
|
||||
============================
|
||||
|
||||
.. rest_method:: DELETE /v2.0/users/{userId}
|
||||
|
||||
Deletes a user.
|
||||
|
||||
Normal response codes: 204
|
||||
Error response codes: 413,405,404,403,401,400,503
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- userId: user_id_path
|
||||
|
||||
|
||||
Show user details (admin endpoint)
|
||||
==================================
|
||||
|
||||
.. rest_method:: GET /v2.0/users/{userId}
|
||||
|
||||
Shows details for a user, by ID.
|
||||
|
||||
The `openstack user show <https://docs.openstack.org/cli-
|
||||
reference/openstack.html#openstack-user-show>`_ command supports
|
||||
showing user details by name or ID. However, the command actually
|
||||
looks up the user ID for a user name and queries the user by ID.
|
||||
|
||||
As a workaround, complete these steps to show details for a user by
|
||||
name:
|
||||
|
||||
- `List all users <https://developer.openstack.org/api-ref-identity-
|
||||
admin-v2.html#admin-listUsers>`_.
|
||||
|
||||
- In the response, find the user name for which you want to show
|
||||
details and note its corresponding user ID.
|
||||
|
||||
- `Show details for user <https://developer.openstack.org/api-ref-
|
||||
identity-admin-v2.html#admin-showUser>`_.
|
||||
|
||||
Normal response codes: 200,203
|
||||
Error response codes: 413,405,404,403,401,400,503
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- userId: user_id
|
||||
|
||||
Response Parameters
|
||||
-------------------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- user: user
|
||||
- enabled: user_enabled
|
||||
- email: user_email
|
||||
- name: user_name
|
||||
- username: username
|
||||
- id: user_id
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
||||
.. literalinclude:: samples/admin/user-show-response.json
|
||||
:language: javascript
|
@ -8,6 +8,5 @@
|
||||
|
||||
.. include:: admin-tenants.inc
|
||||
.. include:: admin-tokens.inc
|
||||
.. include:: admin-users.inc
|
||||
.. include:: admin-versions.inc
|
||||
.. include:: admin-certificates.inc
|
||||
|
@ -29,165 +29,6 @@ CONF = keystone.conf.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
@dependency.requires('assignment_api', 'identity_api', 'resource_api')
|
||||
class User(controller.V2Controller):
|
||||
|
||||
@controller.v2_deprecated
|
||||
def get_user(self, request, user_id):
|
||||
self.assert_admin(request)
|
||||
ref = self.identity_api.get_user(user_id)
|
||||
return {'user': self.v3_to_v2_user(ref)}
|
||||
|
||||
@controller.v2_deprecated
|
||||
def get_users(self, request):
|
||||
# NOTE(termie): i can't imagine that this really wants all the data
|
||||
# about every single user in the system...
|
||||
if 'name' in request.params:
|
||||
return self.get_user_by_name(request, request.params['name'])
|
||||
|
||||
self.assert_admin(request)
|
||||
user_list = self.identity_api.list_users(
|
||||
CONF.identity.default_domain_id)
|
||||
return {'users': self.v3_to_v2_user(user_list)}
|
||||
|
||||
@controller.v2_deprecated
|
||||
def get_user_by_name(self, request, user_name):
|
||||
self.assert_admin(request)
|
||||
ref = self.identity_api.get_user_by_name(
|
||||
user_name, CONF.identity.default_domain_id)
|
||||
return {'user': self.v3_to_v2_user(ref)}
|
||||
|
||||
# CRUD extension
|
||||
@controller.v2_deprecated
|
||||
def create_user(self, request, user):
|
||||
validation.lazy_validate(schema.user_create_v2, user)
|
||||
user = self._normalize_OSKSADM_password_on_request(user)
|
||||
user = self.normalize_username_in_request(user)
|
||||
user = self._normalize_dict(user)
|
||||
self.assert_admin(request)
|
||||
|
||||
default_project_id = user.pop('tenantId', None)
|
||||
if default_project_id is not None:
|
||||
# Check to see if the project is valid before moving on.
|
||||
self.resource_api.get_project(default_project_id)
|
||||
user['default_project_id'] = default_project_id
|
||||
|
||||
self.resource_api.ensure_default_domain_exists()
|
||||
|
||||
# The manager layer will generate the unique ID for users
|
||||
user_ref = self._normalize_domain_id(request, user.copy())
|
||||
new_user_ref = self.v3_to_v2_user(
|
||||
self.identity_api.create_user(
|
||||
user_ref, initiator=request.audit_initiator
|
||||
)
|
||||
)
|
||||
|
||||
if default_project_id is not None:
|
||||
self.assignment_api.add_user_to_project(default_project_id,
|
||||
new_user_ref['id'])
|
||||
return {'user': new_user_ref}
|
||||
|
||||
@controller.v2_deprecated
|
||||
def update_user(self, request, user_id, user):
|
||||
# NOTE(termie): this is really more of a patch than a put
|
||||
validation.lazy_validate(schema.user_update_v2, user)
|
||||
user = self.normalize_username_in_request(user)
|
||||
self.assert_admin(request)
|
||||
|
||||
default_project_id = user.pop('tenantId', None)
|
||||
if default_project_id is not None:
|
||||
user['default_project_id'] = default_project_id
|
||||
|
||||
old_user_ref = self.v3_to_v2_user(
|
||||
self.identity_api.get_user(user_id))
|
||||
|
||||
# Check whether a tenant is being added or changed for the user.
|
||||
# Catch the case where the tenant is being changed for a user and also
|
||||
# where a user previously had no tenant but a tenant is now being
|
||||
# added for the user.
|
||||
if (('tenantId' in old_user_ref and
|
||||
old_user_ref['tenantId'] != default_project_id and
|
||||
default_project_id is not None) or
|
||||
('tenantId' not in old_user_ref and
|
||||
default_project_id is not None)):
|
||||
# Make sure the new project actually exists before we perform the
|
||||
# user update.
|
||||
self.resource_api.get_project(default_project_id)
|
||||
|
||||
user_ref = self.identity_api.update_user(
|
||||
user_id, user, initiator=request.audit_initiator
|
||||
)
|
||||
user_ref = self.v3_to_v2_user(user_ref)
|
||||
|
||||
# If 'tenantId' is in either ref, we might need to add or remove the
|
||||
# user from a project.
|
||||
if 'tenantId' in user_ref or 'tenantId' in old_user_ref:
|
||||
if user_ref['tenantId'] != old_user_ref.get('tenantId'):
|
||||
if old_user_ref.get('tenantId'):
|
||||
try:
|
||||
member_role_id = CONF.member_role_id
|
||||
self.assignment_api.remove_role_from_user_and_project(
|
||||
user_id, old_user_ref['tenantId'], member_role_id)
|
||||
except exception.NotFound:
|
||||
# NOTE(morganfainberg): This is not a critical error it
|
||||
# just means that the user cannot be removed from the
|
||||
# old tenant. This could occur if roles aren't found
|
||||
# or if the project is invalid or if there are no roles
|
||||
# for the user on that project.
|
||||
msg = 'Unable to remove user %(user)s from %(tenant)s.'
|
||||
LOG.warning(msg, {'user': user_id,
|
||||
'tenant': old_user_ref['tenantId']})
|
||||
|
||||
if user_ref['tenantId']:
|
||||
try:
|
||||
self.assignment_api.add_user_to_project(
|
||||
user_ref['tenantId'], user_id)
|
||||
except exception.Conflict: # nosec
|
||||
# We are already a member of that tenant
|
||||
pass
|
||||
except exception.NotFound:
|
||||
# NOTE(morganfainberg): Log this and move on. This is
|
||||
# not the end of the world if we can't add the user to
|
||||
# the appropriate tenant. Most of the time this means
|
||||
# that the project is invalid or roles are some how
|
||||
# incorrect. This shouldn't prevent the return of the
|
||||
# new ref.
|
||||
msg = 'Unable to add user %(user)s to %(tenant)s.'
|
||||
LOG.warning(msg, {'user': user_id,
|
||||
'tenant': user_ref['tenantId']})
|
||||
|
||||
return {'user': user_ref}
|
||||
|
||||
@controller.v2_deprecated
|
||||
def delete_user(self, request, user_id):
|
||||
self.assert_admin(request)
|
||||
self.identity_api.delete_user(
|
||||
user_id, initiator=request.audit_initiator
|
||||
)
|
||||
|
||||
@controller.v2_deprecated
|
||||
def set_user_enabled(self, request, user_id, user):
|
||||
validation.lazy_validate(schema.enable_user_v2, user)
|
||||
return self.update_user(request, user_id, user)
|
||||
|
||||
@controller.v2_deprecated
|
||||
def set_user_password(self, request, user_id, user):
|
||||
user = self._normalize_OSKSADM_password_on_request(user)
|
||||
return self.update_user(request, user_id, user)
|
||||
|
||||
@staticmethod
|
||||
def _normalize_OSKSADM_password_on_request(ref):
|
||||
"""Set the password from the OS-KSADM Admin Extension.
|
||||
|
||||
The OS-KSADM Admin Extension documentation says that
|
||||
`OS-KSADM:password` can be used in place of `password`.
|
||||
|
||||
"""
|
||||
if 'OS-KSADM:password' in ref:
|
||||
ref['password'] = ref.pop('OS-KSADM:password')
|
||||
return ref
|
||||
|
||||
|
||||
@dependency.requires('identity_api')
|
||||
class UserV3(controller.V3Controller):
|
||||
collection_name = 'users'
|
||||
|
@ -19,16 +19,6 @@ from keystone.common import wsgi
|
||||
from keystone.identity import controllers
|
||||
|
||||
|
||||
class Admin(wsgi.ComposableRouter):
|
||||
def add_routes(self, mapper):
|
||||
# User Operations
|
||||
user_controller = controllers.User()
|
||||
mapper.connect('/users/{user_id}',
|
||||
controller=user_controller,
|
||||
action='get_user',
|
||||
conditions=dict(method=['GET']))
|
||||
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
|
@ -884,41 +884,6 @@ class CADFNotificationsForEntities(NotificationsForEntities):
|
||||
cadftaxonomy.SECURITY_DOMAIN)
|
||||
|
||||
|
||||
class V2Notifications(BaseNotificationTest):
|
||||
|
||||
def setUp(self):
|
||||
super(V2Notifications, self).setUp()
|
||||
self.config_fixture.config(notification_format='cadf')
|
||||
|
||||
def test_user(self):
|
||||
token = self.get_scoped_token()
|
||||
resp = self.admin_request(
|
||||
method='POST',
|
||||
path='/v2.0/users',
|
||||
body={
|
||||
'user': {
|
||||
'name': uuid.uuid4().hex,
|
||||
'password': uuid.uuid4().hex,
|
||||
'enabled': True,
|
||||
},
|
||||
},
|
||||
token=token,
|
||||
)
|
||||
user_id = resp.result.get('user').get('id')
|
||||
self._assert_initiator_data_is_set(CREATED_OPERATION,
|
||||
'user',
|
||||
cadftaxonomy.SECURITY_ACCOUNT_USER)
|
||||
# test for delete user
|
||||
self.admin_request(
|
||||
method='DELETE',
|
||||
path='/v2.0/users/%s' % user_id,
|
||||
token=token,
|
||||
)
|
||||
self._assert_initiator_data_is_set(DELETED_OPERATION,
|
||||
'user',
|
||||
cadftaxonomy.SECURITY_ACCOUNT_USER)
|
||||
|
||||
|
||||
class TestEventCallbacks(test_v3.RestfulTestCase):
|
||||
|
||||
class FakeManager(object):
|
||||
|
@ -1,65 +0,0 @@
|
||||
# Copyright 2016 IBM Corp.
|
||||
#
|
||||
# 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 uuid
|
||||
|
||||
|
||||
import keystone.conf
|
||||
from keystone import exception
|
||||
from keystone.identity import controllers
|
||||
from keystone.tests import unit
|
||||
from keystone.tests.unit.ksfixtures import database
|
||||
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
|
||||
|
||||
class UserTestCaseNoDefaultDomain(unit.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(UserTestCaseNoDefaultDomain, self).setUp()
|
||||
self.useFixture(database.Database())
|
||||
self.load_backends()
|
||||
self.user_controller = controllers.User()
|
||||
|
||||
def test_setup(self):
|
||||
# Other tests in this class assume there's no default domain, so make
|
||||
# sure the setUp worked as expected.
|
||||
self.assertRaises(
|
||||
exception.DomainNotFound,
|
||||
self.resource_api.get_domain, CONF.identity.default_domain_id)
|
||||
|
||||
def test_get_users(self):
|
||||
# When list_users is done and there's no default domain, the result is
|
||||
# an empty list.
|
||||
res = self.user_controller.get_users(self.make_request(is_admin=True))
|
||||
self.assertEqual([], res['users'])
|
||||
|
||||
def test_get_user_by_name(self):
|
||||
# When get_user_by_name is done and there's no default domain, the
|
||||
# result is 404 Not Found
|
||||
user_name = uuid.uuid4().hex
|
||||
self.assertRaises(
|
||||
exception.UserNotFound,
|
||||
self.user_controller.get_user_by_name,
|
||||
self.make_request(is_admin=True), user_name)
|
||||
|
||||
def test_create_user(self):
|
||||
# When a user is created using the v2 controller and there's no default
|
||||
# domain, it doesn't fail with can't find domain (a default domain is
|
||||
# created)
|
||||
user = {'name': uuid.uuid4().hex}
|
||||
self.user_controller.create_user(self.make_request(is_admin=True),
|
||||
user)
|
||||
# If the above doesn't fail then this is successful.
|
@ -1,62 +0,0 @@
|
||||
# Copyright 2013 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.
|
||||
|
||||
import os
|
||||
|
||||
from six.moves import http_client
|
||||
import webtest
|
||||
|
||||
from keystone.tests import unit
|
||||
from keystone.tests.unit.ksfixtures import database
|
||||
|
||||
|
||||
class TestNoAdminTokenAuth(unit.TestCase):
|
||||
def setUp(self):
|
||||
super(TestNoAdminTokenAuth, self).setUp()
|
||||
self.useFixture(database.Database())
|
||||
self.load_backends()
|
||||
|
||||
self._generate_paste_config()
|
||||
|
||||
self.admin_app = webtest.TestApp(
|
||||
self.loadapp(unit.dirs.tmp('no_admin_token_auth'), name='admin'),
|
||||
extra_environ=dict(REMOTE_ADDR='127.0.0.1'))
|
||||
self.addCleanup(setattr, self, 'admin_app', None)
|
||||
|
||||
def _generate_paste_config(self):
|
||||
# Generate a file, based on keystone-paste.ini, that doesn't include
|
||||
# admin_token_auth in the pipeline
|
||||
|
||||
with open(unit.dirs.etc('keystone-paste.ini'), 'r') as f:
|
||||
contents = f.read()
|
||||
|
||||
new_contents = contents.replace(' admin_token_auth ', ' ')
|
||||
|
||||
filename = unit.dirs.tmp('no_admin_token_auth-paste.ini')
|
||||
with open(filename, 'w') as f:
|
||||
f.write(new_contents)
|
||||
self.addCleanup(os.remove, filename)
|
||||
|
||||
def test_request_no_admin_token_auth(self):
|
||||
# This test verifies that if the admin_token_auth middleware isn't
|
||||
# in the paste pipeline that users can still make requests.
|
||||
|
||||
# Note(blk-u): Picked /v2.0/tenants because it's an operation that
|
||||
# requires is_admin in the context, any operation that requires
|
||||
# is_admin would work for this test.
|
||||
REQ_PATH = '/v2.0/users'
|
||||
|
||||
# If the following does not raise, then the test is successful.
|
||||
self.admin_app.get(REQ_PATH, headers={'X-Auth-Token': 'NotAdminToken'},
|
||||
status=http_client.UNAUTHORIZED)
|
@ -240,105 +240,6 @@ class CoreApiTests(object):
|
||||
token=token)
|
||||
self.assertValidEndpointListResponse(r)
|
||||
|
||||
def test_get_user(self):
|
||||
token = self.get_scoped_token()
|
||||
r = self.admin_request(
|
||||
path='/v2.0/users/%(user_id)s' % {
|
||||
'user_id': self.user_foo['id'],
|
||||
},
|
||||
token=token)
|
||||
self.assertValidUserResponse(r)
|
||||
|
||||
def test_get_user_by_name(self):
|
||||
token = self.get_scoped_token()
|
||||
r = self.admin_request(
|
||||
path='/v2.0/users?name=%(user_name)s' % {
|
||||
'user_name': self.user_foo['name'],
|
||||
},
|
||||
token=token)
|
||||
self.assertValidUserResponse(r)
|
||||
|
||||
def test_create_update_user_invalid_enabled_type(self):
|
||||
# Enforce usage of boolean for 'enabled' field
|
||||
token = self.get_scoped_token()
|
||||
|
||||
# Test CREATE request
|
||||
r = self.admin_request(
|
||||
method='POST',
|
||||
path='/v2.0/users',
|
||||
body={
|
||||
'user': {
|
||||
'name': uuid.uuid4().hex,
|
||||
'password': uuid.uuid4().hex,
|
||||
'enabled': "False",
|
||||
},
|
||||
},
|
||||
token=token,
|
||||
expected_status=http_client.BAD_REQUEST)
|
||||
self.assertValidErrorResponse(r)
|
||||
|
||||
r = self.admin_request(
|
||||
method='POST',
|
||||
path='/v2.0/users',
|
||||
body={
|
||||
'user': {
|
||||
'name': uuid.uuid4().hex,
|
||||
'password': uuid.uuid4().hex,
|
||||
# In JSON, 0|1 are not booleans
|
||||
'enabled': 0,
|
||||
},
|
||||
},
|
||||
token=token,
|
||||
expected_status=http_client.BAD_REQUEST)
|
||||
self.assertValidErrorResponse(r)
|
||||
|
||||
# Test UPDATE request
|
||||
path = '/v2.0/users/%(user_id)s' % {
|
||||
'user_id': self.user_foo['id'],
|
||||
}
|
||||
|
||||
r = self.admin_request(
|
||||
method='PUT',
|
||||
path=path,
|
||||
body={
|
||||
'user': {
|
||||
'enabled': "False",
|
||||
},
|
||||
},
|
||||
token=token,
|
||||
expected_status=http_client.BAD_REQUEST)
|
||||
self.assertValidErrorResponse(r)
|
||||
|
||||
r = self.admin_request(
|
||||
method='PUT',
|
||||
path=path,
|
||||
body={
|
||||
'user': {
|
||||
# In JSON, 0|1 are not booleans
|
||||
'enabled': 1,
|
||||
},
|
||||
},
|
||||
token=token,
|
||||
expected_status=http_client.BAD_REQUEST)
|
||||
self.assertValidErrorResponse(r)
|
||||
|
||||
def test_create_update_user_valid_enabled_type(self):
|
||||
# Enforce usage of boolean for 'enabled' field
|
||||
token = self.get_scoped_token()
|
||||
|
||||
# Test CREATE request
|
||||
self.admin_request(method='POST',
|
||||
path='/v2.0/users',
|
||||
body={
|
||||
'user': {
|
||||
'name': uuid.uuid4().hex,
|
||||
'password': uuid.uuid4().hex,
|
||||
'enabled': False,
|
||||
},
|
||||
},
|
||||
token=token,
|
||||
expected_status=http_client.OK)
|
||||
|
||||
def test_error_response(self):
|
||||
"""Trigger assertValidErrorResponse by convention."""
|
||||
self.public_request(path='/v2.0/tenants',
|
||||
@ -389,71 +290,6 @@ class CoreApiTests(object):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def test_update_user_with_invalid_tenant(self):
|
||||
token = self.get_scoped_token()
|
||||
|
||||
# Create a new user
|
||||
r = self.admin_request(
|
||||
method='POST',
|
||||
path='/v2.0/users',
|
||||
body={
|
||||
'user': {
|
||||
'name': 'test_invalid_tenant',
|
||||
'password': uuid.uuid4().hex,
|
||||
'tenantId': self.tenant_bar['id'],
|
||||
'enabled': True,
|
||||
},
|
||||
},
|
||||
token=token,
|
||||
expected_status=http_client.OK)
|
||||
user_id = self._get_user_id(r.result)
|
||||
|
||||
# Update user with an invalid tenant
|
||||
r = self.admin_request(
|
||||
method='PUT',
|
||||
path='/v2.0/users/%(user_id)s' % {
|
||||
'user_id': user_id,
|
||||
},
|
||||
body={
|
||||
'user': {
|
||||
'tenantId': 'abcde12345heha',
|
||||
},
|
||||
},
|
||||
token=token,
|
||||
expected_status=http_client.NOT_FOUND)
|
||||
|
||||
def test_update_user_with_invalid_tenant_no_prev_tenant(self):
|
||||
token = self.get_scoped_token()
|
||||
|
||||
# Create a new user
|
||||
r = self.admin_request(
|
||||
method='POST',
|
||||
path='/v2.0/users',
|
||||
body={
|
||||
'user': {
|
||||
'name': 'test_invalid_tenant',
|
||||
'password': uuid.uuid4().hex,
|
||||
'enabled': True,
|
||||
},
|
||||
},
|
||||
token=token,
|
||||
expected_status=http_client.OK)
|
||||
user_id = self._get_user_id(r.result)
|
||||
|
||||
# Update user with an invalid tenant
|
||||
r = self.admin_request(
|
||||
method='PUT',
|
||||
path='/v2.0/users/%(user_id)s' % {
|
||||
'user_id': user_id,
|
||||
},
|
||||
body={
|
||||
'user': {
|
||||
'tenantId': 'abcde12345heha',
|
||||
},
|
||||
},
|
||||
token=token,
|
||||
expected_status=http_client.NOT_FOUND)
|
||||
|
||||
def test_authenticating_a_user_with_no_password(self):
|
||||
token = self.get_scoped_token()
|
||||
|
||||
@ -462,7 +298,7 @@ class CoreApiTests(object):
|
||||
# create the user
|
||||
self.admin_request(
|
||||
method='POST',
|
||||
path='/v2.0/users',
|
||||
path='/v3/users',
|
||||
body={
|
||||
'user': {
|
||||
'name': username,
|
||||
@ -503,280 +339,6 @@ class CoreApiTests(object):
|
||||
r.headers.get('WWW-Authenticate'))
|
||||
|
||||
|
||||
class LegacyV2UsernameTests(object):
|
||||
"""Test to show the broken username behavior in V2.
|
||||
|
||||
The V2 API is documented to use `username` instead of `name`. The
|
||||
API forced used to use name and left the username to fall into the
|
||||
`extra` field.
|
||||
|
||||
These tests ensure this behavior works so fixes to `username`/`name`
|
||||
will be backward compatible.
|
||||
"""
|
||||
|
||||
def create_user(self, **user_attrs):
|
||||
"""Create a users and returns the response object.
|
||||
|
||||
:param user_attrs: attributes added to the request body (optional)
|
||||
"""
|
||||
token = self.get_scoped_token()
|
||||
body = {
|
||||
'user': {
|
||||
'name': uuid.uuid4().hex,
|
||||
'enabled': True,
|
||||
},
|
||||
}
|
||||
body['user'].update(user_attrs)
|
||||
|
||||
return self.admin_request(
|
||||
method='POST',
|
||||
path='/v2.0/users',
|
||||
token=token,
|
||||
body=body,
|
||||
expected_status=http_client.OK)
|
||||
|
||||
def test_create_with_extra_username(self):
|
||||
"""The response for creating a user will contain the extra fields."""
|
||||
fake_username = uuid.uuid4().hex
|
||||
r = self.create_user(username=fake_username)
|
||||
|
||||
self.assertValidUserResponse(r)
|
||||
|
||||
user = self.get_user_from_response(r)
|
||||
self.assertEqual(fake_username, user.get('username'))
|
||||
|
||||
def test_get_returns_username_from_extra(self):
|
||||
"""The response for getting a user will contain the extra fields."""
|
||||
token = self.get_scoped_token()
|
||||
|
||||
fake_username = uuid.uuid4().hex
|
||||
r = self.create_user(username=fake_username)
|
||||
|
||||
id_ = self.get_user_attribute_from_response(r, 'id')
|
||||
r = self.admin_request(path='/v2.0/users/%s' % id_, token=token)
|
||||
|
||||
self.assertValidUserResponse(r)
|
||||
|
||||
user = self.get_user_from_response(r)
|
||||
self.assertEqual(fake_username, user.get('username'))
|
||||
|
||||
def test_update_returns_new_username_when_adding_username(self):
|
||||
"""The response for updating a user will contain the extra fields.
|
||||
|
||||
This is specifically testing for updating a username when a value
|
||||
was not previously set.
|
||||
"""
|
||||
token = self.get_scoped_token()
|
||||
|
||||
r = self.create_user()
|
||||
|
||||
id_ = self.get_user_attribute_from_response(r, 'id')
|
||||
name = self.get_user_attribute_from_response(r, 'name')
|
||||
enabled = self.get_user_attribute_from_response(r, 'enabled')
|
||||
r = self.admin_request(
|
||||
method='PUT',
|
||||
path='/v2.0/users/%s' % id_,
|
||||
token=token,
|
||||
body={
|
||||
'user': {
|
||||
'name': name,
|
||||
'username': 'new_username',
|
||||
'enabled': enabled,
|
||||
},
|
||||
},
|
||||
expected_status=http_client.OK)
|
||||
|
||||
self.assertValidUserResponse(r)
|
||||
|
||||
user = self.get_user_from_response(r)
|
||||
self.assertEqual('new_username', user.get('username'))
|
||||
|
||||
def test_update_returns_new_username_when_updating_username(self):
|
||||
"""The response for updating a user will contain the extra fields.
|
||||
|
||||
This tests updating a username that was previously set.
|
||||
"""
|
||||
token = self.get_scoped_token()
|
||||
|
||||
r = self.create_user(username='original_username')
|
||||
|
||||
id_ = self.get_user_attribute_from_response(r, 'id')
|
||||
name = self.get_user_attribute_from_response(r, 'name')
|
||||
enabled = self.get_user_attribute_from_response(r, 'enabled')
|
||||
r = self.admin_request(
|
||||
method='PUT',
|
||||
path='/v2.0/users/%s' % id_,
|
||||
token=token,
|
||||
body={
|
||||
'user': {
|
||||
'name': name,
|
||||
'username': 'new_username',
|
||||
'enabled': enabled,
|
||||
},
|
||||
},
|
||||
expected_status=http_client.OK)
|
||||
|
||||
self.assertValidUserResponse(r)
|
||||
|
||||
user = self.get_user_from_response(r)
|
||||
self.assertEqual('new_username', user.get('username'))
|
||||
|
||||
def test_username_is_always_returned_create(self):
|
||||
"""Username is set as the value of name if no username is provided.
|
||||
|
||||
This matches the v2.0 spec where we really should be using username
|
||||
and not name.
|
||||
"""
|
||||
r = self.create_user()
|
||||
|
||||
self.assertValidUserResponse(r)
|
||||
|
||||
user = self.get_user_from_response(r)
|
||||
self.assertEqual(user.get('name'), user.get('username'))
|
||||
|
||||
def test_username_is_always_returned_get(self):
|
||||
"""Username is set as the value of name if no username is provided.
|
||||
|
||||
This matches the v2.0 spec where we really should be using username
|
||||
and not name.
|
||||
"""
|
||||
token = self.get_scoped_token()
|
||||
|
||||
r = self.create_user()
|
||||
|
||||
id_ = self.get_user_attribute_from_response(r, 'id')
|
||||
r = self.admin_request(path='/v2.0/users/%s' % id_, token=token)
|
||||
|
||||
self.assertValidUserResponse(r)
|
||||
|
||||
user = self.get_user_from_response(r)
|
||||
self.assertEqual(user.get('name'), user.get('username'))
|
||||
|
||||
def test_username_is_always_returned_get_by_name(self):
|
||||
"""Username is set as the value of name if no username is provided.
|
||||
|
||||
This matches the v2.0 spec where we really should be using username
|
||||
and not name.
|
||||
"""
|
||||
token = self.get_scoped_token()
|
||||
|
||||
r = self.create_user()
|
||||
|
||||
name = self.get_user_attribute_from_response(r, 'name')
|
||||
r = self.admin_request(path='/v2.0/users?name=%s' % name, token=token)
|
||||
|
||||
self.assertValidUserResponse(r)
|
||||
|
||||
user = self.get_user_from_response(r)
|
||||
self.assertEqual(user.get('name'), user.get('username'))
|
||||
|
||||
def test_username_is_always_returned_update_no_username_provided(self):
|
||||
"""Username is set as the value of name if no username is provided.
|
||||
|
||||
This matches the v2.0 spec where we really should be using username
|
||||
and not name.
|
||||
"""
|
||||
token = self.get_scoped_token()
|
||||
|
||||
r = self.create_user()
|
||||
|
||||
id_ = self.get_user_attribute_from_response(r, 'id')
|
||||
name = self.get_user_attribute_from_response(r, 'name')
|
||||
enabled = self.get_user_attribute_from_response(r, 'enabled')
|
||||
r = self.admin_request(
|
||||
method='PUT',
|
||||
path='/v2.0/users/%s' % id_,
|
||||
token=token,
|
||||
body={
|
||||
'user': {
|
||||
'name': name,
|
||||
'enabled': enabled,
|
||||
},
|
||||
},
|
||||
expected_status=http_client.OK)
|
||||
|
||||
self.assertValidUserResponse(r)
|
||||
|
||||
user = self.get_user_from_response(r)
|
||||
self.assertEqual(user.get('name'), user.get('username'))
|
||||
|
||||
def test_updated_username_is_returned(self):
|
||||
"""Username is set as the value of name if no username is provided.
|
||||
|
||||
This matches the v2.0 spec where we really should be using username
|
||||
and not name.
|
||||
"""
|
||||
token = self.get_scoped_token()
|
||||
|
||||
r = self.create_user()
|
||||
|
||||
id_ = self.get_user_attribute_from_response(r, 'id')
|
||||
name = self.get_user_attribute_from_response(r, 'name')
|
||||
enabled = self.get_user_attribute_from_response(r, 'enabled')
|
||||
r = self.admin_request(
|
||||
method='PUT',
|
||||
path='/v2.0/users/%s' % id_,
|
||||
token=token,
|
||||
body={
|
||||
'user': {
|
||||
'name': name,
|
||||
'enabled': enabled,
|
||||
},
|
||||
},
|
||||
expected_status=http_client.OK)
|
||||
|
||||
self.assertValidUserResponse(r)
|
||||
|
||||
user = self.get_user_from_response(r)
|
||||
self.assertEqual(user.get('name'), user.get('username'))
|
||||
|
||||
def test_username_can_be_used_instead_of_name_create(self):
|
||||
token = self.get_scoped_token()
|
||||
|
||||
r = self.admin_request(
|
||||
method='POST',
|
||||
path='/v2.0/users',
|
||||
token=token,
|
||||
body={
|
||||
'user': {
|
||||
'username': uuid.uuid4().hex,
|
||||
'enabled': True,
|
||||
},
|
||||
},
|
||||
expected_status=http_client.OK)
|
||||
|
||||
self.assertValidUserResponse(r)
|
||||
|
||||
user = self.get_user_from_response(r)
|
||||
self.assertEqual(user.get('name'), user.get('username'))
|
||||
|
||||
def test_username_can_be_used_instead_of_name_update(self):
|
||||
token = self.get_scoped_token()
|
||||
|
||||
r = self.create_user()
|
||||
|
||||
id_ = self.get_user_attribute_from_response(r, 'id')
|
||||
new_username = uuid.uuid4().hex
|
||||
enabled = self.get_user_attribute_from_response(r, 'enabled')
|
||||
r = self.admin_request(
|
||||
method='PUT',
|
||||
path='/v2.0/users/%s' % id_,
|
||||
token=token,
|
||||
body={
|
||||
'user': {
|
||||
'username': new_username,
|
||||
'enabled': enabled,
|
||||
},
|
||||
},
|
||||
expected_status=http_client.OK)
|
||||
|
||||
self.assertValidUserResponse(r)
|
||||
|
||||
user = self.get_user_from_response(r)
|
||||
self.assertEqual(new_username, user.get('name'))
|
||||
self.assertEqual(user.get('name'), user.get('username'))
|
||||
|
||||
|
||||
class RestfulTestCase(rest.RestfulTestCase):
|
||||
|
||||
def setUp(self):
|
||||
@ -980,166 +542,15 @@ class V2TestCase(object):
|
||||
def assertValidRevocationListResponse(self, response):
|
||||
self.assertIsNotNone(response.result['signed'])
|
||||
|
||||
def test_create_update_user_invalid_enabled_type(self):
|
||||
# Enforce usage of boolean for 'enabled' field
|
||||
token = self.get_scoped_token()
|
||||
|
||||
# Test CREATE request
|
||||
r = self.admin_request(
|
||||
method='POST',
|
||||
path='/v2.0/users',
|
||||
body={
|
||||
'user': {
|
||||
'name': uuid.uuid4().hex,
|
||||
'password': uuid.uuid4().hex,
|
||||
# In JSON, "true|false" are not boolean
|
||||
'enabled': "true",
|
||||
},
|
||||
},
|
||||
token=token,
|
||||
expected_status=http_client.BAD_REQUEST)
|
||||
self.assertValidErrorResponse(r)
|
||||
|
||||
# Test UPDATE request
|
||||
r = self.admin_request(
|
||||
method='PUT',
|
||||
path='/v2.0/users/%(user_id)s' % {
|
||||
'user_id': self.user_foo['id'],
|
||||
},
|
||||
body={
|
||||
'user': {
|
||||
# In JSON, "true|false" are not boolean
|
||||
'enabled': "true",
|
||||
},
|
||||
},
|
||||
token=token,
|
||||
expected_status=http_client.BAD_REQUEST)
|
||||
self.assertValidErrorResponse(r)
|
||||
|
||||
def test_authenticating_a_user_with_an_OSKSADM_password(self):
|
||||
token = self.get_scoped_token()
|
||||
|
||||
username = uuid.uuid4().hex
|
||||
password = uuid.uuid4().hex
|
||||
|
||||
# create the user
|
||||
r = self.admin_request(
|
||||
method='POST',
|
||||
path='/v2.0/users',
|
||||
body={
|
||||
'user': {
|
||||
'name': username,
|
||||
'OS-KSADM:password': password,
|
||||
'enabled': True,
|
||||
},
|
||||
},
|
||||
token=token)
|
||||
|
||||
# successfully authenticate
|
||||
self.public_request(
|
||||
method='POST',
|
||||
path='/v2.0/tokens',
|
||||
body={
|
||||
'auth': {
|
||||
'passwordCredentials': {
|
||||
'username': username,
|
||||
'password': password,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected_status=http_client.OK)
|
||||
|
||||
# ensure password doesn't leak
|
||||
user_id = r.result['user']['id']
|
||||
r = self.admin_request(
|
||||
method='GET',
|
||||
path='/v2.0/users/%s' % user_id,
|
||||
token=token,
|
||||
expected_status=http_client.OK)
|
||||
self.assertNotIn('OS-KSADM:password', r.result['user'])
|
||||
|
||||
def test_updating_a_user_with_an_OSKSADM_password(self):
|
||||
token = self.get_scoped_token()
|
||||
|
||||
user_id = self.user_foo['id']
|
||||
password = uuid.uuid4().hex
|
||||
|
||||
# update the user
|
||||
self.admin_request(
|
||||
method='PUT',
|
||||
path='/v2.0/users/%s/OS-KSADM/password' % user_id,
|
||||
body={
|
||||
'user': {
|
||||
'password': password,
|
||||
},
|
||||
},
|
||||
token=token,
|
||||
expected_status=http_client.OK)
|
||||
|
||||
# successfully authenticate
|
||||
self.public_request(
|
||||
method='POST',
|
||||
path='/v2.0/tokens',
|
||||
body={
|
||||
'auth': {
|
||||
'passwordCredentials': {
|
||||
'username': self.user_foo['name'],
|
||||
'password': password,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected_status=http_client.OK)
|
||||
|
||||
def test_enable_or_disable_user(self):
|
||||
token = self.get_scoped_token()
|
||||
user_id = self.user_badguy['id']
|
||||
|
||||
self.assertFalse(self.user_badguy['enabled'])
|
||||
|
||||
def _admin_request(body, status):
|
||||
resp = self.admin_request(
|
||||
method='PUT',
|
||||
path='/v2.0/users/%s/OS-KSADM/enabled' % user_id,
|
||||
token=token,
|
||||
body=body,
|
||||
expected_status=status)
|
||||
return resp
|
||||
|
||||
# Enable the user.
|
||||
body = {'user': {'enabled': True}}
|
||||
resp = _admin_request(body, http_client.OK)
|
||||
self.assertTrue(resp.json['user']['enabled'])
|
||||
|
||||
# Disable the user.
|
||||
body = {'user': {'enabled': False}}
|
||||
resp = _admin_request(body, http_client.OK)
|
||||
self.assertFalse(resp.json['user']['enabled'])
|
||||
|
||||
# Attributes other than `enabled` should still work due to bug 1607751
|
||||
body = {
|
||||
'user': {
|
||||
'description': uuid.uuid4().hex,
|
||||
'name': uuid.uuid4().hex,
|
||||
'enabled': True
|
||||
}
|
||||
}
|
||||
_admin_request(body, http_client.OK)
|
||||
|
||||
# `enabled` is boolean, type other than boolean is not allowed.
|
||||
body = {'user': {'enabled': uuid.uuid4().hex}}
|
||||
_admin_request(body, http_client.BAD_REQUEST)
|
||||
|
||||
|
||||
class V2TestCaseUUID(V2TestCase, RestfulTestCase, CoreApiTests,
|
||||
LegacyV2UsernameTests):
|
||||
class V2TestCaseUUID(V2TestCase, RestfulTestCase, CoreApiTests):
|
||||
|
||||
def config_overrides(self):
|
||||
super(V2TestCaseUUID, self).config_overrides()
|
||||
self.config_fixture.config(group='token', provider='uuid')
|
||||
|
||||
|
||||
class V2TestCaseFernet(V2TestCase, RestfulTestCase, CoreApiTests,
|
||||
LegacyV2UsernameTests):
|
||||
class V2TestCaseFernet(V2TestCase, RestfulTestCase, CoreApiTests):
|
||||
|
||||
def config_overrides(self):
|
||||
super(V2TestCaseFernet, self).config_overrides()
|
||||
|
@ -15,7 +15,6 @@
|
||||
from keystone import assignment
|
||||
from keystone.common import extension
|
||||
from keystone.common import wsgi
|
||||
from keystone import identity
|
||||
|
||||
|
||||
extension.register_admin_extension(
|
||||
@ -47,7 +46,6 @@ class Router(wsgi.ComposableRouter):
|
||||
def add_routes(self, mapper):
|
||||
assignment_tenant_controller = (
|
||||
assignment.controllers.TenantAssignment())
|
||||
user_controller = identity.controllers.User()
|
||||
|
||||
# Tenant Operations
|
||||
mapper.connect(
|
||||
@ -55,62 +53,3 @@ class Router(wsgi.ComposableRouter):
|
||||
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']))
|
||||
|
@ -1,119 +0,0 @@
|
||||
# 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.
|
||||
|
||||
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.i18n import _
|
||||
from keystone import identity
|
||||
from keystone.models import token_model
|
||||
from keystone.token import controllers as token_controllers
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
extension.register_public_extension(
|
||||
'OS-KSCRUD', {
|
||||
'name': 'OpenStack Keystone User CRUD',
|
||||
'namespace': 'https://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': 'https://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, request, user_id, user):
|
||||
token_id = request.context_dict.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(
|
||||
request,
|
||||
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(
|
||||
_('v2.0 password change call failed '
|
||||
'due to rejected authentication'))
|
||||
|
||||
update_dict = {'password': user['password'], 'id': user_id}
|
||||
|
||||
old_admin = request.context.is_admin
|
||||
request.context.is_admin = True
|
||||
|
||||
super(UserController, self).set_user_password(request,
|
||||
user_id,
|
||||
update_dict)
|
||||
|
||||
request.context.is_admin = old_admin
|
||||
|
||||
# Issue a new token based upon the original token data. This will
|
||||
# always be a V2.0 token.
|
||||
|
||||
# NOTE(lbragstad): Since we just updated the password and presisted a
|
||||
# revocation event for the user changing the password, it is necessary
|
||||
# to wait one second before authenticating. This ensures we are in the
|
||||
# threshold of a new second before getting a new token.
|
||||
import time
|
||||
time.sleep(1)
|
||||
|
||||
new_token_id, new_token_data = self.token_provider_api.issue_token(
|
||||
token_ref.user_id, token_ref.methods,
|
||||
project_id=token_ref.project_id,
|
||||
parent_audit_id=token_ref.audit_chain_id)
|
||||
v2_helper = token_controllers.V2TokenDataHelper()
|
||||
v2_token_data = v2_helper.v3_to_v2_token(new_token_data, new_token_id)
|
||||
LOG.debug('TOKEN_REF %s', new_token_data)
|
||||
return v2_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']))
|
@ -36,7 +36,6 @@ 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,7 +84,6 @@ 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)])
|
||||
|
||||
@ -95,8 +93,7 @@ def public_app_factory(global_conf, **local_conf):
|
||||
def admin_app_factory(global_conf, **local_conf):
|
||||
controllers.register_version('v2.0')
|
||||
return wsgi.ComposingRouter(routes.Mapper(),
|
||||
[identity_routers.Admin(),
|
||||
token_routers.Router(),
|
||||
[token_routers.Router(),
|
||||
admin_crud.Router(),
|
||||
routers.VersionV2('admin'),
|
||||
routers.Extension()])
|
||||
|
Loading…
Reference in New Issue
Block a user