From 5682d2a27b252c1aca294eb8a0749ff3fbb13b26 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 23 Dec 2011 10:20:37 -0500 Subject: Convering /users to admin extension Relates to blueprint separate-nova-adminapi Change-Id: I0ef13569586be022ba533c4e850793b37b098fea --- nova/api/openstack/v2/__init__.py | 5 - nova/api/openstack/v2/contrib/users.py | 154 ++++++++++++++++++++++ nova/api/openstack/v2/users.py | 139 ------------------- nova/tests/api/openstack/v2/contrib/test_users.py | 154 ++++++++++++++++++++++ nova/tests/api/openstack/v2/test_extensions.py | 1 + nova/tests/api/openstack/v2/test_users.py | 154 ---------------------- 6 files changed, 309 insertions(+), 298 deletions(-) create mode 100644 nova/api/openstack/v2/contrib/users.py delete mode 100644 nova/api/openstack/v2/users.py create mode 100644 nova/tests/api/openstack/v2/contrib/test_users.py delete mode 100644 nova/tests/api/openstack/v2/test_users.py diff --git a/nova/api/openstack/v2/__init__.py b/nova/api/openstack/v2/__init__.py index 1d3830b05..1dadd7bef 100644 --- a/nova/api/openstack/v2/__init__.py +++ b/nova/api/openstack/v2/__init__.py @@ -33,7 +33,6 @@ from nova.api.openstack.v2 import ips from nova.api.openstack.v2 import limits from nova.api.openstack.v2 import servers from nova.api.openstack.v2 import server_metadata -from nova.api.openstack.v2 import users from nova.api.openstack.v2 import versions from nova.api.openstack.v2 import zones from nova.api.openstack import wsgi @@ -134,10 +133,6 @@ class APIRouter(base_wsgi.Router): if FLAGS.allow_admin_api: LOG.debug(_("Including admin operations in API.")) - mapper.resource("user", "users", - controller=users.create_resource(), - collection={'detail': 'GET'}) - mapper.resource("zone", "zones", controller=zones.create_resource(), collection={'detail': 'GET', diff --git a/nova/api/openstack/v2/contrib/users.py b/nova/api/openstack/v2/contrib/users.py new file mode 100644 index 000000000..b9bdd75e4 --- /dev/null +++ b/nova/api/openstack/v2/contrib/users.py @@ -0,0 +1,154 @@ +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# 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 webob import exc + +from nova.api.openstack import common +from nova.api.openstack.v2 import extensions +from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil +from nova.auth import manager +from nova import exception +from nova import flags +from nova import log as logging + + +FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.api.openstack.users') + + +def _translate_keys(user): + return dict(id=user.id, + name=user.name, + access=user.access, + secret=user.secret, + admin=user.admin) + + +class Controller(object): + + def __init__(self): + self.manager = manager.AuthManager() + + def _check_admin(self, context): + """We cannot depend on the db layer to check for admin access + for the auth manager, so we do it here""" + if not context.is_admin: + raise exception.AdminRequired() + + def index(self, req): + """Return all users in brief""" + users = self.manager.get_users() + users = common.limited(users, req) + users = [_translate_keys(user) for user in users] + return dict(users=users) + + def detail(self, req): + """Return all users in detail""" + return self.index(req) + + def show(self, req, id): + """Return data about the given user id""" + + #NOTE(justinsb): The drivers are a little inconsistent in how they + # deal with "NotFound" - some throw, some return None. + try: + user = self.manager.get_user(id) + except exception.NotFound: + user = None + + if user is None: + raise exc.HTTPNotFound() + + return dict(user=_translate_keys(user)) + + def delete(self, req, id): + self._check_admin(req.environ['nova.context']) + self.manager.delete_user(id) + return {} + + def create(self, req, body): + self._check_admin(req.environ['nova.context']) + is_admin = body['user'].get('admin') in ('T', 'True', True) + name = body['user'].get('name') + access = body['user'].get('access') + secret = body['user'].get('secret') + user = self.manager.create_user(name, access, secret, is_admin) + return dict(user=_translate_keys(user)) + + def update(self, req, id, body): + self._check_admin(req.environ['nova.context']) + is_admin = body['user'].get('admin') + if is_admin is not None: + is_admin = is_admin in ('T', 'True', True) + access = body['user'].get('access') + secret = body['user'].get('secret') + self.manager.modify_user(id, access, secret, is_admin) + return dict(user=_translate_keys(self.manager.get_user(id))) + + +def make_user(elem): + elem.set('id') + elem.set('name') + elem.set('access') + elem.set('secret') + elem.set('admin') + + +class UserTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('user', selector='user') + make_user(root) + return xmlutil.MasterTemplate(root, 1) + + +class UsersTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('users') + elem = xmlutil.SubTemplateElement(root, 'user', selector='users') + make_user(elem) + return xmlutil.MasterTemplate(root, 1) + + +class UserXMLSerializer(xmlutil.XMLTemplateSerializer): + def index(self): + return UsersTemplate() + + def default(self): + return UserTemplate() + + +class Users(extensions.ExtensionDescriptor): + """Allow admins to acces user information""" + + name = "Users" + alias = "os-users" + namespace = "http://docs.openstack.org/compute/ext/users/api/v1.1" + updated = "2011-08-08T00:00:00+00:00" + admin_only = True + + def get_resources(self): + body_serializers = { + 'application/xml': UserXMLSerializer(), + } + serializer = wsgi.ResponseSerializer(body_serializers) + + coll_actions = {'detail': 'GET'} + res = extensions.ResourceExtension('users', + Controller(), + serializer=serializer, + collection_actions=coll_actions) + + return [res] diff --git a/nova/api/openstack/v2/users.py b/nova/api/openstack/v2/users.py deleted file mode 100644 index 9dba79e03..000000000 --- a/nova/api/openstack/v2/users.py +++ /dev/null @@ -1,139 +0,0 @@ -# Copyright 2011 OpenStack LLC. -# All Rights Reserved. -# -# 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 webob import exc - -from nova.api.openstack import common -from nova.api.openstack import wsgi -from nova.api.openstack import xmlutil -from nova.auth import manager -from nova import exception -from nova import flags -from nova import log as logging - - -FLAGS = flags.FLAGS -LOG = logging.getLogger('nova.api.openstack.users') - - -def _translate_keys(user): - return dict(id=user.id, - name=user.name, - access=user.access, - secret=user.secret, - admin=user.admin) - - -class Controller(object): - - def __init__(self): - self.manager = manager.AuthManager() - - def _check_admin(self, context): - """We cannot depend on the db layer to check for admin access - for the auth manager, so we do it here""" - if not context.is_admin: - raise exception.AdminRequired() - - def index(self, req): - """Return all users in brief""" - users = self.manager.get_users() - users = common.limited(users, req) - users = [_translate_keys(user) for user in users] - return dict(users=users) - - def detail(self, req): - """Return all users in detail""" - return self.index(req) - - def show(self, req, id): - """Return data about the given user id""" - - #NOTE(justinsb): The drivers are a little inconsistent in how they - # deal with "NotFound" - some throw, some return None. - try: - user = self.manager.get_user(id) - except exception.NotFound: - user = None - - if user is None: - raise exc.HTTPNotFound() - - return dict(user=_translate_keys(user)) - - def delete(self, req, id): - self._check_admin(req.environ['nova.context']) - self.manager.delete_user(id) - return {} - - def create(self, req, body): - self._check_admin(req.environ['nova.context']) - is_admin = body['user'].get('admin') in ('T', 'True', True) - name = body['user'].get('name') - access = body['user'].get('access') - secret = body['user'].get('secret') - user = self.manager.create_user(name, access, secret, is_admin) - return dict(user=_translate_keys(user)) - - def update(self, req, id, body): - self._check_admin(req.environ['nova.context']) - is_admin = body['user'].get('admin') - if is_admin is not None: - is_admin = is_admin in ('T', 'True', True) - access = body['user'].get('access') - secret = body['user'].get('secret') - self.manager.modify_user(id, access, secret, is_admin) - return dict(user=_translate_keys(self.manager.get_user(id))) - - -def make_user(elem): - elem.set('id') - elem.set('name') - elem.set('access') - elem.set('secret') - elem.set('admin') - - -class UserTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('user', selector='user') - make_user(root) - return xmlutil.MasterTemplate(root, 1) - - -class UsersTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('users') - elem = xmlutil.SubTemplateElement(root, 'user', selector='users') - make_user(elem) - return xmlutil.MasterTemplate(root, 1) - - -class UserXMLSerializer(xmlutil.XMLTemplateSerializer): - def index(self): - return UsersTemplate() - - def default(self): - return UserTemplate() - - -def create_resource(): - body_serializers = { - 'application/xml': UserXMLSerializer(), - } - - serializer = wsgi.ResponseSerializer(body_serializers) - - return wsgi.Resource(Controller(), serializer=serializer) diff --git a/nova/tests/api/openstack/v2/contrib/test_users.py b/nova/tests/api/openstack/v2/contrib/test_users.py new file mode 100644 index 000000000..24a14e250 --- /dev/null +++ b/nova/tests/api/openstack/v2/contrib/test_users.py @@ -0,0 +1,154 @@ +# Copyright 2010 OpenStack LLC. +# All Rights Reserved. +# +# 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 lxml import etree + +from nova.api.openstack.v2.contrib import users +from nova.auth.manager import User, Project +from nova import test +from nova.tests.api.openstack import fakes +from nova import utils + + +def fake_init(self): + self.manager = fakes.FakeAuthManager() + + +def fake_admin_check(self, req): + return True + + +class UsersTest(test.TestCase): + def setUp(self): + super(UsersTest, self).setUp() + self.flags(verbose=True, allow_admin_api=True) + self.stubs.Set(users.Controller, '__init__', + fake_init) + self.stubs.Set(users.Controller, '_check_admin', + fake_admin_check) + fakes.FakeAuthManager.clear_fakes() + fakes.FakeAuthManager.projects = dict(testacct=Project('testacct', + 'testacct', + 'id1', + 'test', + [])) + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_networking(self.stubs) + fakes.stub_out_rate_limiting(self.stubs) + fakes.stub_out_auth(self.stubs) + + fakemgr = fakes.FakeAuthManager() + fakemgr.add_user(User('id1', 'guy1', 'acc1', 'secret1', False)) + fakemgr.add_user(User('id2', 'guy2', 'acc2', 'secret2', True)) + + self.controller = users.Controller() + + def test_get_user_list(self): + req = fakes.HTTPRequest.blank('/v2/fake/users') + res_dict = self.controller.index(req) + + self.assertEqual(len(res_dict['users']), 2) + + def test_get_user_by_id(self): + req = fakes.HTTPRequest.blank('/v2/fake/users/id2') + res_dict = self.controller.show(req, 'id2') + + self.assertEqual(res_dict['user']['id'], 'id2') + self.assertEqual(res_dict['user']['name'], 'guy2') + self.assertEqual(res_dict['user']['secret'], 'secret2') + self.assertEqual(res_dict['user']['admin'], True) + + def test_user_delete(self): + req = fakes.HTTPRequest.blank('/v2/fake/users/id1') + self.controller.delete(req, 'id1') + + self.assertTrue('id1' not in [u.id for u in + fakes.FakeAuthManager.auth_data]) + + def test_user_create(self): + secret = utils.generate_password() + body = dict(user=dict(name='test_guy', + access='acc3', + secret=secret, + admin=True)) + req = fakes.HTTPRequest.blank('/v2/fake/users') + res_dict = self.controller.create(req, body) + + # NOTE(justinsb): This is a questionable assertion in general + # fake sets id=name, but others might not... + self.assertEqual(res_dict['user']['id'], 'test_guy') + + self.assertEqual(res_dict['user']['name'], 'test_guy') + self.assertEqual(res_dict['user']['access'], 'acc3') + self.assertEqual(res_dict['user']['secret'], secret) + self.assertEqual(res_dict['user']['admin'], True) + self.assertTrue('test_guy' in [u.id for u in + fakes.FakeAuthManager.auth_data]) + self.assertEqual(len(fakes.FakeAuthManager.auth_data), 3) + + def test_user_update(self): + new_secret = utils.generate_password() + body = dict(user=dict(name='guy2', + access='acc2', + secret=new_secret)) + + req = fakes.HTTPRequest.blank('/v2/fake/users/id2') + res_dict = self.controller.update(req, 'id2', body) + + self.assertEqual(res_dict['user']['id'], 'id2') + self.assertEqual(res_dict['user']['name'], 'guy2') + self.assertEqual(res_dict['user']['access'], 'acc2') + self.assertEqual(res_dict['user']['secret'], new_secret) + self.assertEqual(res_dict['user']['admin'], True) + + +class TestUsersXMLSerializer(test.TestCase): + + serializer = users.UserXMLSerializer() + + def test_index(self): + fixture = {'users': [{'id': 'id1', + 'name': 'guy1', + 'secret': 'secret1', + 'admin': False}, + {'id': 'id2', + 'name': 'guy2', + 'secret': 'secret2', + 'admin': True}]} + + output = self.serializer.serialize(fixture, 'index') + res_tree = etree.XML(output) + + self.assertEqual(res_tree.tag, 'users') + self.assertEqual(len(res_tree), 2) + self.assertEqual(res_tree[0].tag, 'user') + self.assertEqual(res_tree[0].get('id'), 'id1') + self.assertEqual(res_tree[1].tag, 'user') + self.assertEqual(res_tree[1].get('id'), 'id2') + + def test_show(self): + fixture = {'user': {'id': 'id2', + 'name': 'guy2', + 'secret': 'secret2', + 'admin': True}} + + output = self.serializer.serialize(fixture, 'show') + res_tree = etree.XML(output) + + self.assertEqual(res_tree.tag, 'user') + self.assertEqual(res_tree.get('id'), 'id2') + self.assertEqual(res_tree.get('name'), 'guy2') + self.assertEqual(res_tree.get('secret'), 'secret2') + self.assertEqual(res_tree.get('admin'), 'True') diff --git a/nova/tests/api/openstack/v2/test_extensions.py b/nova/tests/api/openstack/v2/test_extensions.py index 7b9a46a93..ad017e665 100644 --- a/nova/tests/api/openstack/v2/test_extensions.py +++ b/nova/tests/api/openstack/v2/test_extensions.py @@ -118,6 +118,7 @@ class ExtensionControllerTest(ExtensionTestCase): "ServerActionList", "ServerDiagnostics", "SimpleTenantUsage", + "Users", "VSAs", "VirtualInterfaces", "Volumes", diff --git a/nova/tests/api/openstack/v2/test_users.py b/nova/tests/api/openstack/v2/test_users.py deleted file mode 100644 index b6af802f8..000000000 --- a/nova/tests/api/openstack/v2/test_users.py +++ /dev/null @@ -1,154 +0,0 @@ -# Copyright 2010 OpenStack LLC. -# All Rights Reserved. -# -# 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 lxml import etree - -from nova import test -from nova import utils -from nova.api.openstack.v2 import users -from nova.auth.manager import User, Project -from nova.tests.api.openstack import fakes - - -def fake_init(self): - self.manager = fakes.FakeAuthManager() - - -def fake_admin_check(self, req): - return True - - -class UsersTest(test.TestCase): - def setUp(self): - super(UsersTest, self).setUp() - self.flags(verbose=True, allow_admin_api=True) - self.stubs.Set(users.Controller, '__init__', - fake_init) - self.stubs.Set(users.Controller, '_check_admin', - fake_admin_check) - fakes.FakeAuthManager.clear_fakes() - fakes.FakeAuthManager.projects = dict(testacct=Project('testacct', - 'testacct', - 'id1', - 'test', - [])) - fakes.FakeAuthDatabase.data = {} - fakes.stub_out_networking(self.stubs) - fakes.stub_out_rate_limiting(self.stubs) - fakes.stub_out_auth(self.stubs) - - fakemgr = fakes.FakeAuthManager() - fakemgr.add_user(User('id1', 'guy1', 'acc1', 'secret1', False)) - fakemgr.add_user(User('id2', 'guy2', 'acc2', 'secret2', True)) - - self.controller = users.Controller() - - def test_get_user_list(self): - req = fakes.HTTPRequest.blank('/v2/fake/users') - res_dict = self.controller.index(req) - - self.assertEqual(len(res_dict['users']), 2) - - def test_get_user_by_id(self): - req = fakes.HTTPRequest.blank('/v2/fake/users/id2') - res_dict = self.controller.show(req, 'id2') - - self.assertEqual(res_dict['user']['id'], 'id2') - self.assertEqual(res_dict['user']['name'], 'guy2') - self.assertEqual(res_dict['user']['secret'], 'secret2') - self.assertEqual(res_dict['user']['admin'], True) - - def test_user_delete(self): - req = fakes.HTTPRequest.blank('/v2/fake/users/id1') - self.controller.delete(req, 'id1') - - self.assertTrue('id1' not in [u.id for u in - fakes.FakeAuthManager.auth_data]) - - def test_user_create(self): - secret = utils.generate_password() - body = dict(user=dict(name='test_guy', - access='acc3', - secret=secret, - admin=True)) - req = fakes.HTTPRequest.blank('/v2/fake/users') - res_dict = self.controller.create(req, body) - - # NOTE(justinsb): This is a questionable assertion in general - # fake sets id=name, but others might not... - self.assertEqual(res_dict['user']['id'], 'test_guy') - - self.assertEqual(res_dict['user']['name'], 'test_guy') - self.assertEqual(res_dict['user']['access'], 'acc3') - self.assertEqual(res_dict['user']['secret'], secret) - self.assertEqual(res_dict['user']['admin'], True) - self.assertTrue('test_guy' in [u.id for u in - fakes.FakeAuthManager.auth_data]) - self.assertEqual(len(fakes.FakeAuthManager.auth_data), 3) - - def test_user_update(self): - new_secret = utils.generate_password() - body = dict(user=dict(name='guy2', - access='acc2', - secret=new_secret)) - - req = fakes.HTTPRequest.blank('/v2/fake/users/id2') - res_dict = self.controller.update(req, 'id2', body) - - self.assertEqual(res_dict['user']['id'], 'id2') - self.assertEqual(res_dict['user']['name'], 'guy2') - self.assertEqual(res_dict['user']['access'], 'acc2') - self.assertEqual(res_dict['user']['secret'], new_secret) - self.assertEqual(res_dict['user']['admin'], True) - - -class TestUsersXMLSerializer(test.TestCase): - - serializer = users.UserXMLSerializer() - - def test_index(self): - fixture = {'users': [{'id': 'id1', - 'name': 'guy1', - 'secret': 'secret1', - 'admin': False}, - {'id': 'id2', - 'name': 'guy2', - 'secret': 'secret2', - 'admin': True}]} - - output = self.serializer.serialize(fixture, 'index') - res_tree = etree.XML(output) - - self.assertEqual(res_tree.tag, 'users') - self.assertEqual(len(res_tree), 2) - self.assertEqual(res_tree[0].tag, 'user') - self.assertEqual(res_tree[0].get('id'), 'id1') - self.assertEqual(res_tree[1].tag, 'user') - self.assertEqual(res_tree[1].get('id'), 'id2') - - def test_show(self): - fixture = {'user': {'id': 'id2', - 'name': 'guy2', - 'secret': 'secret2', - 'admin': True}} - - output = self.serializer.serialize(fixture, 'show') - res_tree = etree.XML(output) - - self.assertEqual(res_tree.tag, 'user') - self.assertEqual(res_tree.get('id'), 'id2') - self.assertEqual(res_tree.get('name'), 'guy2') - self.assertEqual(res_tree.get('secret'), 'secret2') - self.assertEqual(res_tree.get('admin'), 'True') -- cgit