Add user management REST API
adding user management for vendors. Change-Id: I8a0bcf0478dfd2dc676e243a088d229c4c3165d7 Addresses-Spec: https://review.openstack.org/#/c/277313/
This commit is contained in:
committed by
Paul Van Eck
parent
ab8ba00554
commit
c5e0c1139b
@@ -19,6 +19,7 @@ from refstack.api.controllers import auth
|
|||||||
from refstack.api.controllers import capabilities
|
from refstack.api.controllers import capabilities
|
||||||
from refstack.api.controllers import results
|
from refstack.api.controllers import results
|
||||||
from refstack.api.controllers import user
|
from refstack.api.controllers import user
|
||||||
|
from refstack.api.controllers import vendors
|
||||||
|
|
||||||
|
|
||||||
class V1Controller(object):
|
class V1Controller(object):
|
||||||
@@ -28,3 +29,4 @@ class V1Controller(object):
|
|||||||
capabilities = capabilities.CapabilitiesController()
|
capabilities = capabilities.CapabilitiesController()
|
||||||
auth = auth.AuthController()
|
auth = auth.AuthController()
|
||||||
profile = user.ProfileController()
|
profile = user.ProfileController()
|
||||||
|
vendors = vendors.VendorsController()
|
||||||
|
|||||||
82
refstack/api/controllers/vendors.py
Normal file
82
refstack/api/controllers/vendors.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Copyright (c) 2015 Mirantis, Inc.
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Vendors controller."""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import six
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log
|
||||||
|
import pecan
|
||||||
|
from pecan import rest
|
||||||
|
from pecan.secure import secure
|
||||||
|
|
||||||
|
from refstack.api import utils as api_utils
|
||||||
|
from refstack import db
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class UsersController(rest.RestController):
|
||||||
|
"""/v1/vendors/<vendor_id>/users handler."""
|
||||||
|
|
||||||
|
@secure(api_utils.is_authenticated)
|
||||||
|
@pecan.expose('json')
|
||||||
|
def get(self, vendor_id):
|
||||||
|
"""Return list of users in the vendor's group."""
|
||||||
|
if not (api_utils.check_user_is_foundation_admin()
|
||||||
|
or api_utils.check_user_is_vendor_admin(vendor_id)):
|
||||||
|
return None
|
||||||
|
|
||||||
|
org_users = db.get_organization_users(vendor_id)
|
||||||
|
return [x for x in six.itervalues(org_users)]
|
||||||
|
|
||||||
|
@secure(api_utils.is_authenticated)
|
||||||
|
@pecan.expose('json')
|
||||||
|
def put(self, vendor_id, openid):
|
||||||
|
"""Add user to vendor group."""
|
||||||
|
openid = base64.b64decode(openid)
|
||||||
|
|
||||||
|
if not (api_utils.check_user_is_foundation_admin()
|
||||||
|
or api_utils.check_user_is_vendor_admin(vendor_id)):
|
||||||
|
pecan.abort(403, 'Forbidden.')
|
||||||
|
|
||||||
|
vendor = db.get_organization(vendor_id)
|
||||||
|
creator = api_utils.get_user_id()
|
||||||
|
db.add_user_to_group(openid, vendor['group_id'], creator)
|
||||||
|
pecan.response.status = 204
|
||||||
|
|
||||||
|
@secure(api_utils.is_authenticated)
|
||||||
|
@pecan.expose('json')
|
||||||
|
def delete(self, vendor_id, openid):
|
||||||
|
"""Remove user from vendor group."""
|
||||||
|
openid = base64.b64decode(openid)
|
||||||
|
|
||||||
|
if not (api_utils.check_user_is_foundation_admin()
|
||||||
|
or api_utils.check_user_is_vendor_admin(vendor_id)):
|
||||||
|
pecan.abort(403, 'Forbidden.')
|
||||||
|
|
||||||
|
vendor = db.get_organization(vendor_id)
|
||||||
|
db.remove_user_from_group(openid, vendor['group_id'])
|
||||||
|
pecan.response.status = 204
|
||||||
|
|
||||||
|
|
||||||
|
class VendorsController(rest.RestController):
|
||||||
|
"""/v1/vendors handler."""
|
||||||
|
|
||||||
|
users = UsersController()
|
||||||
@@ -311,3 +311,10 @@ def check_user_is_foundation_admin():
|
|||||||
user = get_user_id()
|
user = get_user_id()
|
||||||
org_users = db.get_foundation_users()
|
org_users = db.get_foundation_users()
|
||||||
return user in org_users
|
return user in org_users
|
||||||
|
|
||||||
|
|
||||||
|
def check_user_is_vendor_admin(vendor_id):
|
||||||
|
"""Check is user in vendor group or not."""
|
||||||
|
user = get_user_id()
|
||||||
|
org_users = db.get_organization_users(vendor_id)
|
||||||
|
return user in org_users
|
||||||
|
|||||||
@@ -215,3 +215,13 @@ def delete_product(id):
|
|||||||
def get_foundation_users():
|
def get_foundation_users():
|
||||||
"""Get users' openid-s that belong to group of foundation."""
|
"""Get users' openid-s that belong to group of foundation."""
|
||||||
return IMPL.get_foundation_users()
|
return IMPL.get_foundation_users()
|
||||||
|
|
||||||
|
|
||||||
|
def get_organization_users(organization_id):
|
||||||
|
"""Get users with info that belong to group of organization."""
|
||||||
|
return IMPL.get_organization_users(organization_id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_organizations():
|
||||||
|
"""Get all organizations."""
|
||||||
|
return IMPL.get_organizations()
|
||||||
|
|||||||
@@ -498,3 +498,30 @@ def get_foundation_users():
|
|||||||
users = (session.query(models.UserToGroup.user_openid).
|
users = (session.query(models.UserToGroup.user_openid).
|
||||||
filter_by(group_id=group_id))
|
filter_by(group_id=group_id))
|
||||||
return [user.user_openid for user in users]
|
return [user.user_openid for user in users]
|
||||||
|
|
||||||
|
|
||||||
|
def get_organization_users(organization_id):
|
||||||
|
"""Get users that belong to group of organization."""
|
||||||
|
session = get_session()
|
||||||
|
organization = (session.query(models.Organization.group_id)
|
||||||
|
.filter_by(id=organization_id).first())
|
||||||
|
if organization is None:
|
||||||
|
raise NotFound('Organization with id %s is not found'
|
||||||
|
% organization_id)
|
||||||
|
group_id = organization.group_id
|
||||||
|
users = (session.query(models.UserToGroup, models.User)
|
||||||
|
.join(models.User,
|
||||||
|
models.User.openid == models.UserToGroup.user_openid)
|
||||||
|
.filter(models.UserToGroup.group_id == group_id))
|
||||||
|
keys = ['openid', 'fullname', 'email']
|
||||||
|
return {item[1].openid: _to_dict(item[1], allowed_keys=keys)
|
||||||
|
for item in users}
|
||||||
|
|
||||||
|
|
||||||
|
def get_organizations():
|
||||||
|
"""Get all organizations."""
|
||||||
|
session = get_session()
|
||||||
|
items = (
|
||||||
|
session.query(models.Organization)
|
||||||
|
.order_by(models.Organization.created_at.desc()).all())
|
||||||
|
return _to_dict(items)
|
||||||
|
|||||||
@@ -30,8 +30,9 @@ from refstack.api import exceptions as api_exc
|
|||||||
from refstack.api.controllers import auth
|
from refstack.api.controllers import auth
|
||||||
from refstack.api.controllers import capabilities
|
from refstack.api.controllers import capabilities
|
||||||
from refstack.api.controllers import results
|
from refstack.api.controllers import results
|
||||||
from refstack.api.controllers import validation
|
|
||||||
from refstack.api.controllers import user
|
from refstack.api.controllers import user
|
||||||
|
from refstack.api.controllers import validation
|
||||||
|
from refstack.api.controllers import vendors
|
||||||
from refstack.tests import unit as base
|
from refstack.tests import unit as base
|
||||||
|
|
||||||
|
|
||||||
@@ -57,7 +58,7 @@ class BaseControllerTestCase(base.RefstackBaseTestCase):
|
|||||||
self.setup_mock('refstack.api.utils.get_user_role')
|
self.setup_mock('refstack.api.utils.get_user_role')
|
||||||
self.mock_is_authenticated = \
|
self.mock_is_authenticated = \
|
||||||
self.setup_mock('refstack.api.utils.is_authenticated',
|
self.setup_mock('refstack.api.utils.is_authenticated',
|
||||||
return_value=True)
|
return_value=True, spec=self.setUp)
|
||||||
|
|
||||||
|
|
||||||
class RootControllerTestCase(BaseControllerTestCase):
|
class RootControllerTestCase(BaseControllerTestCase):
|
||||||
@@ -641,3 +642,73 @@ class PublicKeysControllerTestCase(BaseControllerTestCase):
|
|||||||
|
|
||||||
self.assertRaises(webob.exc.HTTPError,
|
self.assertRaises(webob.exc.HTTPError,
|
||||||
self.controller.delete, 'other_key_id')
|
self.controller.delete, 'other_key_id')
|
||||||
|
|
||||||
|
|
||||||
|
class VendorUsersControllerTestCase(BaseControllerTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(VendorUsersControllerTestCase, self).setUp()
|
||||||
|
self.controller = vendors.UsersController()
|
||||||
|
|
||||||
|
@mock.patch('refstack.db.get_organization_users')
|
||||||
|
@mock.patch('refstack.api.utils.check_user_is_foundation_admin')
|
||||||
|
@mock.patch('refstack.api.utils.check_user_is_vendor_admin')
|
||||||
|
def test_get(self, mock_vendor, mock_foundation, mock_db_get_org_users):
|
||||||
|
mock_vendor.return_value = True
|
||||||
|
mock_foundation.return_value = False
|
||||||
|
mock_db_get_org_users.return_value = {
|
||||||
|
'foobar': {
|
||||||
|
'openid': 'foobar',
|
||||||
|
'fullname': 'Foo Bar',
|
||||||
|
'email': 'foo@bar.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expected = [{'openid': 'foobar',
|
||||||
|
'fullname': 'Foo Bar',
|
||||||
|
'email': 'foo@bar.com'}]
|
||||||
|
self.assertEqual(expected, self.controller.get('some-org'))
|
||||||
|
|
||||||
|
mock_vendor.return_value = False
|
||||||
|
self.assertIsNone(self.controller.get('some-org'))
|
||||||
|
|
||||||
|
mock_foundation.return_value = True
|
||||||
|
self.assertEqual(expected, self.controller.get('some-org'))
|
||||||
|
|
||||||
|
@mock.patch('refstack.db.add_user_to_group')
|
||||||
|
@mock.patch('refstack.db.get_organization')
|
||||||
|
@mock.patch('refstack.api.utils.check_user_is_foundation_admin')
|
||||||
|
@mock.patch('refstack.api.utils.check_user_is_vendor_admin')
|
||||||
|
@mock.patch('refstack.api.utils.get_user_id')
|
||||||
|
def test_put(self, mock_get_user, mock_vendor, mock_foundation,
|
||||||
|
mock_db_org, mock_add):
|
||||||
|
# This is 'foo' in Base64
|
||||||
|
encoded_openid = 'Zm9v'
|
||||||
|
mock_vendor.return_value = True
|
||||||
|
mock_foundation.return_value = False
|
||||||
|
mock_db_org.return_value = {'group_id': 'abc'}
|
||||||
|
mock_get_user.return_value = 'fake-id'
|
||||||
|
|
||||||
|
self.controller.put('fake-vendor', encoded_openid)
|
||||||
|
mock_add.assert_called_once_with(b'foo', 'abc', 'fake-id')
|
||||||
|
|
||||||
|
mock_vendor.return_value = False
|
||||||
|
self.assertRaises(webob.exc.HTTPError,
|
||||||
|
self.controller.put, 'fake-vendor', encoded_openid)
|
||||||
|
|
||||||
|
@mock.patch('refstack.db.remove_user_from_group')
|
||||||
|
@mock.patch('refstack.db.get_organization')
|
||||||
|
@mock.patch('refstack.api.utils.check_user_is_foundation_admin')
|
||||||
|
@mock.patch('refstack.api.utils.check_user_is_vendor_admin')
|
||||||
|
def test_delete(self, mock_vendor, mock_foundation, mock_db_org,
|
||||||
|
mock_remove):
|
||||||
|
# This is 'foo' in Base64
|
||||||
|
encoded_openid = 'Zm9v'
|
||||||
|
mock_vendor.return_value = True
|
||||||
|
mock_foundation.return_value = False
|
||||||
|
mock_db_org.return_value = {'group_id': 'abc'}
|
||||||
|
self.controller.delete('fake-vendor', encoded_openid)
|
||||||
|
mock_remove.assert_called_with(b'foo', 'abc')
|
||||||
|
|
||||||
|
mock_vendor.return_value = False
|
||||||
|
self.assertRaises(webob.exc.HTTPError, self.controller.delete,
|
||||||
|
'fake-vendor', encoded_openid)
|
||||||
|
|||||||
@@ -478,3 +478,15 @@ class APIUtilsTestCase(base.BaseTestCase):
|
|||||||
401, 'Authentication is failed. '
|
401, 'Authentication is failed. '
|
||||||
'Please permit access to your name.'
|
'Please permit access to your name.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@mock.patch('refstack.db.get_organization_users')
|
||||||
|
@mock.patch.object(api_utils, 'get_user_id', return_value='fake_id')
|
||||||
|
def test_check_user_is_vendor_admin(self, mock_user, mock_db):
|
||||||
|
mock_user.return_value = 'some-user'
|
||||||
|
mock_db.return_value = ['some-user', 'another-user']
|
||||||
|
result = api_utils.check_user_is_vendor_admin('some-vendor')
|
||||||
|
self.assertTrue(result)
|
||||||
|
|
||||||
|
mock_db.return_value = ['another-user']
|
||||||
|
result = api_utils.check_user_is_vendor_admin('some-vendor')
|
||||||
|
self.assertFalse(result)
|
||||||
|
|||||||
@@ -766,3 +766,50 @@ class DBBackendTestCase(base.BaseTestCase):
|
|||||||
mock.call(id='product_id'),
|
mock.call(id='product_id'),
|
||||||
mock.call().delete(synchronize_session=False)))
|
mock.call().delete(synchronize_session=False)))
|
||||||
session.begin.assert_called_once_with()
|
session.begin.assert_called_once_with()
|
||||||
|
|
||||||
|
@mock.patch.object(api, 'get_session',
|
||||||
|
return_value=mock.Mock(name='session'),)
|
||||||
|
@mock.patch('refstack.db.sqlalchemy.api.models')
|
||||||
|
def test_get_organization_users(self, mock_models, mock_get_session):
|
||||||
|
organization_id = 12345
|
||||||
|
session = mock_get_session.return_value
|
||||||
|
query = session.query.return_value
|
||||||
|
filtered = query.filter_by.return_value
|
||||||
|
filtered.first.return_value.group_id = 'foo'
|
||||||
|
|
||||||
|
join = query.join.return_value
|
||||||
|
|
||||||
|
fake_user = models.User()
|
||||||
|
fake_user.openid = 'foobar'
|
||||||
|
fake_user.fullname = 'Foo Bar'
|
||||||
|
fake_user.email = 'foo@bar.com'
|
||||||
|
join.filter.return_value = [(mock.Mock(), fake_user)]
|
||||||
|
|
||||||
|
result = api.get_organization_users(organization_id)
|
||||||
|
expected = {'foobar': {'openid': 'foobar',
|
||||||
|
'fullname': 'Foo Bar',
|
||||||
|
'email': 'foo@bar.com'}}
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
session.query.assert_any_call(mock_models.Organization.group_id)
|
||||||
|
query.filter_by.assert_called_once_with(id=organization_id)
|
||||||
|
session.query.assert_any_call(mock_models.UserToGroup,
|
||||||
|
mock_models.User)
|
||||||
|
|
||||||
|
@mock.patch.object(api, 'get_session',
|
||||||
|
return_value=mock.Mock(name='session'),)
|
||||||
|
@mock.patch('refstack.db.sqlalchemy.models.Organization')
|
||||||
|
@mock.patch.object(api, '_to_dict', side_effect=lambda x: x)
|
||||||
|
def test_organizations_get(self, mock_to_dict, mock_model,
|
||||||
|
mock_get_session):
|
||||||
|
session = mock_get_session.return_value
|
||||||
|
query = session.query.return_value
|
||||||
|
ordered = query.order_by.return_value
|
||||||
|
organizations = ordered.all.return_value
|
||||||
|
|
||||||
|
result = api.get_organizations()
|
||||||
|
self.assertEqual(organizations, result)
|
||||||
|
|
||||||
|
session.query.assert_called_once_with(mock_model)
|
||||||
|
query.order_by.assert_called_once_with(mock_model.created_at.desc())
|
||||||
|
ordered.all.assert_called_once_with()
|
||||||
|
|||||||
Reference in New Issue
Block a user