Remove v2.0 identity APIs

bp removed-as-of-queens

Change-Id: Icfd519f527de0e3945a858ce7f87ddadef766faa
This commit is contained in:
Lance Bragstad 2017-08-31 18:47:26 +00:00
parent c83d139273
commit 75f24c628b
11 changed files with 4 additions and 1339 deletions

View File

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

View File

@ -8,6 +8,5 @@
.. include:: admin-tenants.inc
.. include:: admin-tokens.inc
.. include:: admin-users.inc
.. include:: admin-versions.inc
.. include:: admin-certificates.inc

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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