mgr/dashboard: Replace RGW proxy controller

Fixes: http://tracker.ceph.com/issues/24436

To fully support the role based authentication/authorization system it is necessary to replace the RGW proxy controller by separate controllers for RGW user and bucket.

Signed-off-by: Volker Theile <vtheile@suse.com>
This commit is contained in:
Volker Theile 2018-06-19 13:19:10 +02:00
parent 80a51c9714
commit a98bca6a2f
11 changed files with 858 additions and 384 deletions

View File

@ -7,51 +7,90 @@ logger = logging.getLogger(__name__)
from .helper import DashboardTestCase, authenticate
class RgwControllerTest(DashboardTestCase):
@authenticate
def test_rgw_daemon_list(self):
data = self._get('/api/rgw/daemon')
self.assertStatus(200)
class RgwTestCase(DashboardTestCase):
self.assertEqual(len(data), 1)
data = data[0]
self.assertIn('id', data)
self.assertIn('version', data)
self.assertIn('server_hostname', data)
maxDiff = None
create_test_user = False
@authenticate
def test_rgw_daemon_get(self):
data = self._get('/api/rgw/daemon')
self.assertStatus(200)
data = self._get('/api/rgw/daemon/{}'.format(data[0]['id']))
self.assertStatus(200)
self.assertIn('rgw_metadata', data)
self.assertIn('rgw_id', data)
self.assertIn('rgw_status', data)
self.assertTrue(data['rgw_metadata'])
class RgwApiUserTest(DashboardTestCase):
@classmethod
def setUpClass(cls):
super(RgwApiUserTest, cls).setUpClass()
super(RgwTestCase, cls).setUpClass()
# Create the administrator account.
cls._radosgw_admin_cmd([
'user', 'create', '--uid=admin', '--display-name=admin',
'--system', '--access-key=admin', '--secret=admin'
'user', 'create', '--uid', 'admin', '--display-name', 'admin',
'--system', '--access-key', 'admin', '--secret', 'admin'
])
# Update the dashboard configuration.
cls._ceph_cmd(['dashboard', 'set-rgw-api-secret-key', 'admin'])
cls._ceph_cmd(['dashboard', 'set-rgw-api-access-key', 'admin'])
# Create a test user?
if cls.create_test_user:
cls._radosgw_admin_cmd([
'user', 'create', '--uid', 'teuth-test-user',
'--display-name', 'teuth-test-user'
])
cls._radosgw_admin_cmd([
'caps', 'add', '--uid', 'teuth-test-user',
'--caps', 'metadata=write'
])
cls._radosgw_admin_cmd([
'subuser', 'create', '--uid', 'teuth-test-user',
'--subuser', 'teuth-test-subuser', '--access',
'full', '--key-type', 's3', '--access-key',
'xyz123'
])
cls._radosgw_admin_cmd([
'subuser', 'create', '--uid', 'teuth-test-user',
'--subuser', 'teuth-test-subuser2', '--access',
'full', '--key-type', 'swift'
])
@classmethod
def tearDownClass(cls):
if cls.create_test_user:
cls._radosgw_admin_cmd(['user', 'rm', '--uid=teuth-test-user'])
super(DashboardTestCase, cls).tearDownClass()
def get_rgw_user(self, uid):
return self._get('/api/rgw/user/{}'.format(uid))
def find_in_list(self, key, value, data):
"""
Helper function to find an object with the specified key/value
in a list.
:param key: The name of the key.
:param value: The value to search for.
:param data: The list to process.
:return: Returns the found object or None.
"""
return next(iter(filter(lambda x: x[key] == value, data)), None)
class RgwApiCredentialsTest(RgwTestCase):
def setUp(self):
# Restart the Dashboard module to ensure that the connection to the
# RGW Admin Ops API is re-established with the new credentials.
self._ceph_cmd(['mgr', 'module', 'disable', 'dashboard'])
self._ceph_cmd(['mgr', 'module', 'enable', 'dashboard', '--force'])
# Set the default credentials.
self._ceph_cmd(['dashboard', 'set-rgw-api-user-id', ''])
self._ceph_cmd(['dashboard', 'set-rgw-api-secret-key', 'admin'])
self._ceph_cmd(['dashboard', 'set-rgw-api-access-key', 'admin'])
@authenticate
def test_no_access_secret_key(self):
self._ceph_cmd(['dashboard', 'set-rgw-api-secret-key', ''])
self._ceph_cmd(['dashboard', 'set-rgw-api-access-key', ''])
resp = self._get('/api/rgw/user')
self.assertStatus(500)
self.assertIn('detail', resp)
self.assertIn('component', resp)
self.assertIn('No RGW credentials found', resp['detail'])
self.assertEquals(resp['component'], 'rgw')
@authenticate
def test_success(self):
self._ceph_cmd(['dashboard', 'set-rgw-api-user-id', ''])
data = self._get('/api/rgw/status')
self.assertStatus(200)
self.assertIn('available', data)
@ -59,7 +98,7 @@ class RgwApiUserTest(DashboardTestCase):
self.assertTrue(data['available'])
@authenticate
def test_failure(self):
def test_invalid_user_id(self):
self._ceph_cmd(['dashboard', 'set-rgw-api-user-id', 'xyz'])
data = self._get('/api/rgw/status')
self.assertStatus(200)
@ -70,32 +109,115 @@ class RgwApiUserTest(DashboardTestCase):
data['message'])
class RgwProxyExceptionsTest(DashboardTestCase):
class RgwBucketTest(RgwTestCase):
@classmethod
def setUpClass(cls):
super(RgwProxyExceptionsTest, cls).setUpClass()
cls._ceph_cmd(['dashboard', 'set-rgw-api-secret-key', ''])
cls._ceph_cmd(['dashboard', 'set-rgw-api-access-key', ''])
cls.create_test_user = True
super(RgwBucketTest, cls).setUpClass()
@authenticate
def test_no_credentials_exception(self):
resp = self._get('/api/rgw/proxy/status')
self.assertStatus(500)
self.assertIn('detail', resp)
def test_all(self):
# Create a new bucket.
self._post(
'/api/rgw/bucket',
params={
'bucket': 'teuth-test-bucket',
'uid': 'admin'
})
self.assertStatus(201)
data = self.jsonBody()
self.assertIn('bucket_info', data)
data = data['bucket_info']
self.assertIn('bucket', data)
self.assertIn('quota', data)
self.assertIn('creation_time', data)
self.assertIn('name', data['bucket'])
self.assertIn('bucket_id', data['bucket'])
self.assertEquals(data['bucket']['name'], 'teuth-test-bucket')
# List all buckets.
data = self._get('/api/rgw/bucket')
self.assertStatus(200)
self.assertEqual(len(data), 1)
self.assertIn('teuth-test-bucket', data)
# Get the bucket.
data = self._get('/api/rgw/bucket/teuth-test-bucket')
self.assertStatus(200)
self.assertIn('id', data)
self.assertIn('bucket', data)
self.assertIn('bucket_quota', data)
self.assertIn('owner', data)
self.assertEquals(data['bucket'], 'teuth-test-bucket')
self.assertEquals(data['owner'], 'admin')
# Update the bucket.
self._put(
'/api/rgw/bucket/teuth-test-bucket',
params={
'bucket_id': data['id'],
'uid': 'teuth-test-user'
})
self.assertStatus(200)
data = self._get('/api/rgw/bucket/teuth-test-bucket')
self.assertStatus(200)
self.assertEquals(data['owner'], 'teuth-test-user')
# Delete the bucket.
self._delete('/api/rgw/bucket/teuth-test-bucket')
self.assertStatus(204)
data = self._get('/api/rgw/bucket')
self.assertStatus(200)
self.assertEqual(len(data), 0)
class RgwProxyTest(DashboardTestCase):
@classmethod
def setUpClass(cls):
super(RgwProxyTest, cls).setUpClass()
cls._radosgw_admin_cmd([
class RgwDaemonTest(DashboardTestCase):
@authenticate
def test_list(self):
data = self._get('/api/rgw/daemon')
self.assertStatus(200)
self.assertEqual(len(data), 1)
data = data[0]
self.assertIn('id', data)
self.assertIn('version', data)
self.assertIn('server_hostname', data)
@authenticate
def test_get(self):
data = self._get('/api/rgw/daemon')
self.assertStatus(200)
data = self._get('/api/rgw/daemon/{}'.format(data[0]['id']))
self.assertStatus(200)
self.assertIn('rgw_metadata', data)
self.assertIn('rgw_id', data)
self.assertIn('rgw_status', data)
self.assertTrue(data['rgw_metadata'])
@authenticate
def test_status(self):
self._radosgw_admin_cmd([
'user', 'create', '--uid=admin', '--display-name=admin',
'--system', '--access-key=admin', '--secret=admin'
])
cls._ceph_cmd(['dashboard', 'set-rgw-api-secret-key', 'admin'])
cls._ceph_cmd(['dashboard', 'set-rgw-api-access-key', 'admin'])
self._ceph_cmd(['dashboard', 'set-rgw-api-user-id', 'admin'])
self._ceph_cmd(['dashboard', 'set-rgw-api-secret-key', 'admin'])
self._ceph_cmd(['dashboard', 'set-rgw-api-access-key', 'admin'])
data = self._get('/api/rgw/status')
self.assertStatus(200)
self.assertIn('available', data)
self.assertIn('message', data)
self.assertTrue(data['available'])
class RgwUserTest(RgwTestCase):
@classmethod
def setUpClass(cls):
super(RgwUserTest, cls).setUpClass()
def _assert_user_data(self, data):
self.assertIn('caps', data)
@ -110,71 +232,308 @@ class RgwProxyTest(DashboardTestCase):
self.assertIn('tenant', data)
self.assertIn('user_id', data)
def _test_put(self):
self._put(
'/api/rgw/proxy/user',
params={
'uid': 'teuth-test-user',
'display-name': 'display name',
})
data = self._resp.json()
@authenticate
def test_get(self):
data = self.get_rgw_user('admin')
self.assertStatus(200)
self._assert_user_data(data)
self.assertEquals(data['user_id'], 'admin')
@authenticate
def test_list(self):
data = self._get('/api/rgw/user')
self.assertStatus(200)
self.assertGreaterEqual(len(data), 1)
self.assertIn('admin', data)
data = self._get(
'/api/rgw/proxy/user', params={'uid': 'teuth-test-user'})
self.assertStatus(200)
self.assertEqual(data['user_id'], 'teuth-test-user')
def _test_get(self):
data = self._get(
'/api/rgw/proxy/user', params={'uid': 'teuth-test-user'})
@authenticate
def test_create_update_delete(self):
# Create a new user.
self._post('/api/rgw/user', params={
'uid': 'teuth-test-user',
'display_name': 'display name'
})
self.assertStatus(201)
data = self.jsonBody()
self._assert_user_data(data)
self.assertEquals(data['user_id'], 'teuth-test-user')
self.assertEquals(data['display_name'], 'display name')
# Get the user.
data = self.get_rgw_user('teuth-test-user')
self.assertStatus(200)
self._assert_user_data(data)
self.assertEquals(data['user_id'], 'teuth-test-user')
def _test_post(self):
"""Updates the user"""
self._post(
'/api/rgw/proxy/user',
# Update the user.
self._put(
'/api/rgw/user/teuth-test-user',
params={
'uid': 'teuth-test-user',
'display-name': 'new name'
'display_name': 'new name'
})
self.assertStatus(200)
self._assert_user_data(self._resp.json())
self.assertEqual(self._resp.json()['display_name'], 'new name')
data = self.jsonBody()
self._assert_user_data(data)
self.assertEqual(data['display_name'], 'new name')
def _test_delete(self):
self._delete('/api/rgw/proxy/user', params={'uid': 'teuth-test-user'})
self.assertStatus(200)
self._delete('/api/rgw/proxy/user', params={'uid': 'teuth-test-user'})
# Delete the user.
self._delete('/api/rgw/user/teuth-test-user')
self.assertStatus(204)
self.get_rgw_user('teuth-test-user')
self.assertStatus(500)
resp = self._resp.json()
resp = self.jsonBody()
self.assertIn('detail', resp)
self.assertIn('failed request with status code 404', resp['detail'])
self.assertIn('"Code":"NoSuchUser"', resp['detail'])
self.assertIn('"HostId"', resp['detail'])
self.assertIn('"RequestId"', resp['detail'])
class RgwUserCapabilityTest(RgwTestCase):
@classmethod
def setUpClass(cls):
cls.create_test_user = True
super(RgwUserCapabilityTest, cls).setUpClass()
@authenticate
def test_rgw_proxy(self):
"""Test basic request types"""
self.maxDiff = None
def test_set(self):
self._post(
'/api/rgw/user/teuth-test-user/capability',
params={
'type': 'usage',
'perm': 'read'
})
self.assertStatus(201)
data = self.jsonBody()
self.assertEqual(len(data), 1)
data = data[0]
self.assertEqual(data['type'], 'usage')
self.assertEqual(data['perm'], 'read')
# PUT - Create a user
self._test_put()
# Get the user data to validate the capabilities.
data = self.get_rgw_user('teuth-test-user')
self.assertStatus(200)
self.assertGreaterEqual(len(data['caps']), 1)
self.assertEqual(data['caps'][0]['type'], 'usage')
self.assertEqual(data['caps'][0]['perm'], 'read')
# GET - Get the user details
self._test_get()
@authenticate
def test_delete(self):
self._delete(
'/api/rgw/user/teuth-test-user/capability',
params={
'type': 'metadata',
'perm': 'write'
})
self.assertStatus(204)
# POST - Update the user details
self._test_post()
# Get the user data to validate the capabilities.
data = self.get_rgw_user('teuth-test-user')
self.assertStatus(200)
self.assertEqual(len(data['caps']), 0)
# DELETE - Delete the user
self._test_delete()
class RgwUserKeyTest(RgwTestCase):
@classmethod
def setUpClass(cls):
cls.create_test_user = True
super(RgwUserKeyTest, cls).setUpClass()
@authenticate
def test_create_s3(self):
self._post(
'/api/rgw/user/teuth-test-user/key',
params={
'key_type': 's3',
'generate_key': 'false',
'access_key': 'abc987',
'secret_key': 'aaabbbccc'
})
data = self.jsonBody()
self.assertStatus(201)
self.assertGreaterEqual(len(data), 3)
key = self.find_in_list('access_key', 'abc987', data)
self.assertIsInstance(key, object)
self.assertEqual(key['secret_key'], 'aaabbbccc')
@authenticate
def test_create_swift(self):
self._post(
'/api/rgw/user/teuth-test-user/key',
params={
'key_type': 'swift',
'subuser': 'teuth-test-subuser',
'generate_key': 'false',
'secret_key': 'xxxyyyzzz'
})
data = self.jsonBody()
self.assertStatus(201)
self.assertGreaterEqual(len(data), 2)
key = self.find_in_list('secret_key', 'xxxyyyzzz', data)
self.assertIsInstance(key, object)
@authenticate
def test_delete_s3(self):
self._delete(
'/api/rgw/user/teuth-test-user/key',
params={
'key_type': 's3',
'access_key': 'xyz123'
})
self.assertStatus(204)
@authenticate
def test_delete_swift(self):
self._delete(
'/api/rgw/user/teuth-test-user/key',
params={
'key_type': 'swift',
'subuser': 'teuth-test-user:teuth-test-subuser2'
})
self.assertStatus(204)
class RgwUserQuotaTest(RgwTestCase):
@classmethod
def setUpClass(cls):
cls.create_test_user = True
super(RgwUserQuotaTest, cls).setUpClass()
def _assert_quota(self, data):
self.assertIn('user_quota', data)
self.assertIn('max_objects', data['user_quota'])
self.assertIn('enabled', data['user_quota'])
self.assertIn('max_size_kb', data['user_quota'])
self.assertIn('max_size', data['user_quota'])
self.assertIn('bucket_quota', data)
self.assertIn('max_objects', data['bucket_quota'])
self.assertIn('enabled', data['bucket_quota'])
self.assertIn('max_size_kb', data['bucket_quota'])
self.assertIn('max_size', data['bucket_quota'])
@authenticate
def test_get_quota(self):
data = self._get('/api/rgw/user/teuth-test-user/quota')
self.assertStatus(200)
self._assert_quota(data)
@authenticate
def test_set_user_quota(self):
self._put(
'/api/rgw/user/teuth-test-user/quota',
params={
'quota_type': 'user',
'enabled': 'true',
'max_size_kb': 2048,
'max_objects': 101
})
self.assertStatus(200)
data = self._get('/api/rgw/user/teuth-test-user/quota')
self.assertStatus(200)
self._assert_quota(data)
self.assertEqual(data['user_quota']['max_objects'], 101)
self.assertTrue(data['user_quota']['enabled'])
self.assertEqual(data['user_quota']['max_size_kb'], 2048)
@authenticate
def test_set_bucket_quota(self):
self._put(
'/api/rgw/user/teuth-test-user/quota',
params={
'quota_type': 'bucket',
'enabled': 'false',
'max_size_kb': 4096,
'max_objects': 2000
})
self.assertStatus(200)
data = self._get('/api/rgw/user/teuth-test-user/quota')
self.assertStatus(200)
self._assert_quota(data)
self.assertEqual(data['bucket_quota']['max_objects'], 2000)
self.assertFalse(data['bucket_quota']['enabled'])
self.assertEqual(data['bucket_quota']['max_size_kb'], 4096)
class RgwUserSubuserTest(RgwTestCase):
@classmethod
def setUpClass(cls):
cls.create_test_user = True
super(RgwUserSubuserTest, cls).setUpClass()
@authenticate
def test_create_swift(self):
self._post(
'/api/rgw/user/teuth-test-user/subuser',
params={
'subuser': 'tux',
'access': 'readwrite',
'key_type': 'swift'
})
self.assertStatus(201)
data = self.jsonBody()
subuser = self.find_in_list('id', 'teuth-test-user:tux', data)
self.assertIsInstance(subuser, object)
self.assertEqual(subuser['permissions'], 'read-write')
# Get the user data to validate the keys.
data = self.get_rgw_user('teuth-test-user')
self.assertStatus(200)
key = self.find_in_list('user', 'teuth-test-user:tux', data['swift_keys'])
self.assertIsInstance(key, object)
@authenticate
def test_create_s3(self):
self._post(
'/api/rgw/user/teuth-test-user/subuser',
params={
'subuser': 'hugo',
'access': 'write',
'generate_secret': 'false',
'access_key': 'yyy',
'secret_key': 'xxx'
})
self.assertStatus(201)
data = self.jsonBody()
subuser = self.find_in_list('id', 'teuth-test-user:hugo', data)
self.assertIsInstance(subuser, object)
self.assertEqual(subuser['permissions'], 'write')
# Get the user data to validate the keys.
data = self.get_rgw_user('teuth-test-user')
self.assertStatus(200)
key = self.find_in_list('user', 'teuth-test-user:hugo', data['keys'])
self.assertIsInstance(key, object)
self.assertEqual(key['secret_key'], 'xxx')
@authenticate
def test_delete_w_purge(self):
self._delete(
'/api/rgw/user/teuth-test-user/subuser/teuth-test-subuser2')
self.assertStatus(204)
# Get the user data to check that the keys don't exist anymore.
data = self.get_rgw_user('teuth-test-user')
self.assertStatus(200)
key = self.find_in_list('user', 'teuth-test-user:teuth-test-subuser2',
data['swift_keys'])
self.assertIsNone(key)
@authenticate
def test_delete_wo_purge(self):
self._delete(
'/api/rgw/user/teuth-test-user/subuser/teuth-test-subuser',
params={'purge_keys': 'false'})
self.assertStatus(204)
# Get the user data to check whether they keys still exist.
data = self.get_rgw_user('teuth-test-user')
self.assertStatus(200)
key = self.find_in_list('user', 'teuth-test-user:teuth-test-subuser',
data['keys'])
self.assertIsInstance(key, object)

View File

@ -509,19 +509,19 @@ class BaseController(object):
content_length = int(cherrypy.request.headers['Content-Length'])
body = cherrypy.request.body.read(content_length)
if not body:
return func(*args, **kwargs)
try:
data = json.loads(body.decode('utf-8'))
except Exception as e:
raise cherrypy.HTTPError(400, 'Failed to decode JSON: {}'
.format(str(e)))
kwargs.update(data.items())
ret = func(*args, **kwargs)
ret = func(*args, **kwargs)
else:
try:
data = json.loads(body.decode('utf-8'))
except Exception as e:
raise cherrypy.HTTPError(400, 'Failed to decode JSON: {}'
.format(str(e)))
kwargs.update(data.items())
ret = func(*args, **kwargs)
if json_response:
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps(ret).encode('utf8')
ret = json.dumps(ret).encode('utf8')
return ret
return inner

View File

@ -4,12 +4,12 @@ from __future__ import absolute_import
import json
import cherrypy
from . import ApiController, BaseController, RESTController, AuthRequired, \
Endpoint, Proxy
from . import ApiController, BaseController, RESTController, AuthRequired, Endpoint
from .. import logger
from ..services.ceph_service import CephService
from ..services.rgw_client import RgwClient
from ..rest_client import RequestException
from ..exceptions import DashboardException
@ApiController('/rgw')
@ -73,7 +73,7 @@ class RgwDaemon(RESTController):
try:
status = json.loads(status['json'])
except ValueError:
logger.warning("%s had invalid status json", service['id'])
logger.warning('%s had invalid status json', service['id'])
status = {}
else:
logger.warning('%s has no key "json" in status', service['id'])
@ -83,40 +83,179 @@ class RgwDaemon(RESTController):
return daemon
@ApiController('/rgw/proxy')
@AuthRequired()
class RgwProxy(BaseController):
class RgwRESTController(RESTController):
@Proxy()
def __call__(self, path, **params):
def proxy(self, method, path, params=None, json_response=True):
try:
rgw_client = RgwClient.admin_instance()
method = cherrypy.request.method
data = None
if cherrypy.request.body.length:
data = cherrypy.request.body.read()
return rgw_client.proxy(method, path, params, data)
except RequestException as e:
# Always use status code 500 and NOT the status that may delivered
# by the exception. That's because we do not want to forward e.g.
# 401 or 404 that may trigger unwanted actions in the UI.
cherrypy.response.headers['Content-Type'] = 'application/json'
cherrypy.response.status = 500
return json.dumps({'detail': str(e)}).encode('utf-8')
instance = RgwClient.admin_instance()
result = instance.proxy(method, path, params, None)
if json_response and result != '':
result = json.loads(result.decode('utf-8'))
return result
except (DashboardException, RequestException) as e:
raise DashboardException(e, http_status_code=500, component='rgw')
@ApiController('/rgw/bucket')
@AuthRequired()
class RgwBucket(RESTController):
class RgwBucket(RgwRESTController):
def list(self):
return self.proxy('GET', 'bucket')
def get(self, bucket):
return self.proxy('GET', 'bucket', {'bucket': bucket})
def create(self, bucket, uid):
try:
rgw_client = RgwClient.instance(uid)
return rgw_client.create_bucket(bucket)
except RequestException as e:
cherrypy.response.headers['Content-Type'] = 'application/json'
cherrypy.response.status = 500
return {'detail': str(e)}
raise DashboardException(e, http_status_code=500, component='rgw')
def set(self, bucket, bucket_id, uid):
return self.proxy('PUT', 'bucket', {
'bucket': bucket,
'bucket-id': bucket_id,
'uid': uid
}, json_response=False)
def delete(self, bucket, purge_objects='true'):
return self.proxy('DELETE', 'bucket', {
'bucket': bucket,
'purge-objects': purge_objects
}, json_response=False)
@ApiController('/rgw/user')
@AuthRequired()
class RgwUser(RgwRESTController):
def list(self):
return self.proxy('GET', 'metadata/user')
def get(self, uid):
return self.proxy('GET', 'user', {'uid': uid})
def create(self, uid, display_name, email=None, max_buckets=None,
suspended=None, generate_key=None, access_key=None,
secret_key=None):
params = {'uid': uid}
if display_name is not None:
params['display-name'] = display_name
if email is not None:
params['email'] = email
if max_buckets is not None:
params['max-buckets'] = max_buckets
if suspended is not None:
params['suspended'] = suspended
if generate_key is not None:
params['generate-key'] = generate_key
if access_key is not None:
params['access-key'] = access_key
if secret_key is not None:
params['secret-key'] = secret_key
return self.proxy('PUT', 'user', params)
def set(self, uid, display_name=None, email=None, max_buckets=None,
suspended=None):
params = {'uid': uid}
if display_name is not None:
params['display-name'] = display_name
if email is not None:
params['email'] = email
if max_buckets is not None:
params['max-buckets'] = max_buckets
if suspended is not None:
params['suspended'] = suspended
return self.proxy('POST', 'user', params)
def delete(self, uid):
try:
instance = RgwClient.admin_instance()
# Ensure the user is not configured to access the RGW Object Gateway.
if instance.userid == uid:
raise DashboardException(msg='Unable to delete "{}" - this user '
'account is required for managing the '
'Object Gateway'.format(uid))
# Finally redirect request to the RGW proxy.
return self.proxy('DELETE', 'user', {'uid': uid}, json_response=False)
except (DashboardException, RequestException) as e:
raise DashboardException(e, component='rgw')
# pylint: disable=redefined-builtin
@RESTController.Resource(method='POST', path='/capability', status=201)
def create_cap(self, uid, type, perm):
return self.proxy('PUT', 'user?caps', {
'uid': uid,
'user-caps': '{}={}'.format(type, perm)
})
# pylint: disable=redefined-builtin
@RESTController.Resource(method='DELETE', path='/capability', status=204)
def delete_cap(self, uid, type, perm):
return self.proxy('DELETE', 'user?caps', {
'uid': uid,
'user-caps': '{}={}'.format(type, perm)
})
@RESTController.Resource(method='POST', path='/key', status=201)
def create_key(self, uid, key_type='s3', subuser=None, generate_key='true',
access_key=None, secret_key=None):
params = {'uid': uid, 'key-type': key_type, 'generate-key': generate_key}
if subuser is not None:
params['subuser'] = subuser
if access_key is not None:
params['access-key'] = access_key
if secret_key is not None:
params['secret-key'] = secret_key
return self.proxy('PUT', 'user?key', params)
@RESTController.Resource(method='DELETE', path='/key', status=204)
def delete_key(self, uid, key_type='s3', subuser=None, access_key=None):
params = {'uid': uid, 'key-type': key_type}
if subuser is not None:
params['subuser'] = subuser
if access_key is not None:
params['access-key'] = access_key
return self.proxy('DELETE', 'user?key', params, json_response=False)
@RESTController.Resource(method='GET', path='/quota')
def get_quota(self, uid):
return self.proxy('GET', 'user?quota', {'uid': uid})
@RESTController.Resource(method='PUT', path='/quota')
def set_quota(self, uid, quota_type, enabled, max_size_kb, max_objects):
return self.proxy('PUT', 'user?quota', {
'uid': uid,
'quota-type': quota_type,
'enabled': enabled,
'max-size-kb': max_size_kb,
'max-objects': max_objects
}, json_response=False)
@RESTController.Resource(method='POST', path='/subuser', status=201)
def create_subuser(self, uid, subuser, access, key_type='s3',
generate_secret='true', access_key=None,
secret_key=None):
return self.proxy('PUT', 'user', {
'uid': uid,
'subuser': subuser,
'key-type': key_type,
'access': access,
'generate-secret': generate_secret,
'access-key': access_key,
'secret-key': secret_key
})
@RESTController.Resource(method='DELETE', path='/subuser/{subuser}', status=204)
def delete_subuser(self, uid, subuser, purge_keys='true'):
"""
:param purge_keys: Set to False to do not purge the keys.
Note, this only works for s3 subusers.
"""
return self.proxy('DELETE', 'user', {
'uid': uid,
'subuser': subuser,
'purge-keys': purge_keys
}, json_response=False)

View File

@ -91,7 +91,7 @@ export class RgwBucketFormComponent implements OnInit {
if (this.editing) {
// Edit
const idCtl = this.bucketForm.get('id');
this.rgwBucketService.update(idCtl.value, bucketCtl.value, ownerCtl.value).subscribe(
this.rgwBucketService.update(bucketCtl.value, idCtl.value, ownerCtl.value).subscribe(
() => {
this.goToListView();
},

View File

@ -117,12 +117,18 @@
<label class="control-label col-sm-3"
for="max_buckets"
i18n>Max. buckets
<span class="required"></span>
</label>
<div class="col-sm-9">
<input id="max_buckets"
class="form-control"
type="number"
formControlName="max_buckets">
<span class="help-block"
*ngIf="(frm.submitted || userForm.controls.max_buckets.dirty) && userForm.controls.max_buckets.hasError('required')"
i18n>
This field is required.
</span>
<span class="help-block"
*ngIf="(frm.submitted || userForm.controls.max_buckets.dirty) && userForm.controls.max_buckets.hasError('min')"
i18n>

View File

@ -9,6 +9,7 @@ import { of as observableOf } from 'rxjs';
import { RgwUserService } from '../../../shared/api/rgw-user.service';
import { SharedModule } from '../../../shared/shared.module';
import { configureTestBed } from '../../../shared/unit-test-helper';
import { RgwUserS3Key } from '../models/rgw-user-s3-key';
import { RgwUserFormComponent } from './rgw-user-form.component';
describe('RgwUserFormComponent', () => {
@ -46,6 +47,53 @@ describe('RgwUserFormComponent', () => {
expect(component).toBeTruthy();
});
describe('s3 key management', () => {
let rgwUserService: RgwUserService;
beforeEach(() => {
rgwUserService = TestBed.get(RgwUserService);
spyOn(rgwUserService, 'addS3Key').and.stub();
});
it('should not update key', () => {
component.setS3Key(new RgwUserS3Key(), 3);
expect(component.s3Keys.length).toBe(0);
expect(rgwUserService.addS3Key).not.toHaveBeenCalled();
});
it('should set key', () => {
const key = new RgwUserS3Key();
key.user = 'test1:subuser2';
component.setS3Key(key);
expect(component.s3Keys.length).toBe(1);
expect(component.s3Keys[0].user).toBe('test1:subuser2');
expect(rgwUserService.addS3Key).toHaveBeenCalledWith(
'test1', {
subuser: 'subuser2',
generate_key: 'false',
access_key: undefined,
secret_key: undefined
}
);
});
it('should set key w/o subuser', () => {
const key = new RgwUserS3Key();
key.user = 'test1';
component.setS3Key(key);
expect(component.s3Keys.length).toBe(1);
expect(component.s3Keys[0].user).toBe('test1');
expect(rgwUserService.addS3Key).toHaveBeenCalledWith(
'test1', {
subuser: '',
generate_key: 'false',
access_key: undefined,
secret_key: undefined
}
);
});
});
describe('quotaMaxSizeValidator', () => {
it('should validate max size (1/7)', () => {
const resp = component.quotaMaxSizeValidator(new FormControl(''));

View File

@ -59,7 +59,7 @@ export class RgwUserFormComponent implements OnInit {
user_id: [null, [Validators.required], [this.userIdValidator()]],
display_name: [null, [Validators.required]],
email: [null, [CdValidators.email]],
max_buckets: [null, [Validators.min(0)]],
max_buckets: [1000, [Validators.required, Validators.min(0)]],
suspended: [false],
// S3 key
generate_key: [true],
@ -181,8 +181,8 @@ export class RgwUserFormComponent implements OnInit {
value[type + '_quota_max_size'] = quota.max_size;
}
if (quota.max_objects < 0) {
value[type + '_quota_max_size_unlimited'] = true;
value[type + '_quota_max_size'] = null;
value[type + '_quota_max_objects_unlimited'] = true;
value[type + '_quota_max_objects'] = null;
} else {
value[type + '_quota_max_objects_unlimited'] = false;
value[type + '_quota_max_objects'] = quota.max_objects;
@ -225,26 +225,27 @@ export class RgwUserFormComponent implements OnInit {
if (this.userForm.pristine) {
this.goToListView();
}
const uid = this.userForm.get('user_id').value;
if (this.editing) {
// Edit
if (this._isGeneralDirty()) {
const args = this._getApiPostArgs();
this.submitObservables.push(this.rgwUserService.post(args));
const args = this._getUpdateArgs();
this.submitObservables.push(this.rgwUserService.update(uid, args));
}
} else {
// Add
const args = this._getApiPutArgs();
this.submitObservables.push(this.rgwUserService.put(args));
const args = this._getCreateArgs();
this.submitObservables.push(this.rgwUserService.create(args));
}
// Check if user quota has been modified.
if (this._isUserQuotaDirty()) {
const userQuotaArgs = this._getApiUserQuotaArgs();
this.submitObservables.push(this.rgwUserService.putQuota(userQuotaArgs));
const userQuotaArgs = this._getUserQuotaArgs();
this.submitObservables.push(this.rgwUserService.updateQuota(uid, userQuotaArgs));
}
// Check if bucket quota has been modified.
if (this._isBucketQuotaDirty()) {
const bucketQuotaArgs = this._getApiBucketQuotaArgs();
this.submitObservables.push(this.rgwUserService.putQuota(bucketQuotaArgs));
const bucketQuotaArgs = this._getBucketQuotaArgs();
this.submitObservables.push(this.rgwUserService.updateQuota(uid, bucketQuotaArgs));
}
// Finally execute all observables.
observableForkJoin(this.submitObservables).subscribe(
@ -304,31 +305,29 @@ export class RgwUserFormComponent implements OnInit {
* Add/Update a subuser.
*/
setSubuser(subuser: RgwUserSubuser, index?: number) {
const mapPermissions = {
'full-control': 'full',
'read-write': 'readwrite'
};
const uid = this.userForm.get('user_id').value;
const args = {
subuser: subuser.id,
access:
subuser.permissions in mapPermissions
? mapPermissions[subuser.permissions]
: subuser.permissions,
key_type: 'swift',
secret_key: subuser.secret_key,
generate_secret: subuser.generate_secret ? 'true' : 'false'
};
this.submitObservables.push(this.rgwUserService.createSubuser(uid, args));
if (_.isNumber(index)) {
// Modify
// Create an observable to modify the subuser when the form is submitted.
this.submitObservables.push(
this.rgwUserService.addSubuser(
this.userForm.get('user_id').value,
subuser.id,
subuser.permissions,
subuser.secret_key,
subuser.generate_secret
)
);
this.subusers[index] = subuser;
} else {
// Add
// Create an observable to add the subuser when the form is submitted.
this.submitObservables.push(
this.rgwUserService.addSubuser(
this.userForm.get('user_id').value,
subuser.id,
subuser.permissions,
subuser.secret_key,
subuser.generate_secret
)
);
this.subusers.push(subuser);
// Add a Swift key. If the secret key is auto-generated, then visualize
// this to the user by displaying a notification instead of the key.
@ -418,16 +417,17 @@ export class RgwUserFormComponent implements OnInit {
// Nothing to do here at the moment.
} else {
// Add
// Split the key's user name into its user and subuser parts.
const userMatches = key.user.match(/([^:]+)(:(.+))?/);
// Create an observable to add the S3 key when the form is submitted.
this.submitObservables.push(
this.rgwUserService.addS3Key(
this.userForm.get('user_id').value,
key.user,
key.access_key,
key.secret_key,
key.generate_key
)
);
const uid = userMatches[1];
const args = {
subuser: userMatches[2] ? userMatches[3] : '',
generate_key: key.generate_key ? 'true' : 'false',
access_key: key.access_key,
secret_key: key.secret_key
};
this.submitObservables.push(this.rgwUserService.addS3Key(uid, args));
// If the access and the secret key are auto-generated, then visualize
// this to the user by displaying a notification instead of the key.
this.s3Keys.push({
@ -578,31 +578,28 @@ export class RgwUserFormComponent implements OnInit {
* Helper function to get the arguments of the API request when a new
* user is created.
*/
private _getApiPutArgs() {
private _getCreateArgs() {
const result = {
uid: this.userForm.get('user_id').value,
'display-name': this.userForm.get('display_name').value
display_name: this.userForm.get('display_name').value,
suspended: this.userForm.get('suspended').value,
email: '',
max_buckets: this.userForm.get('max_buckets').value,
generate_key: this.userForm.get('generate_key').value,
access_key: '',
secret_key: ''
};
const suspendedCtl = this.userForm.get('suspended');
if (suspendedCtl.value) {
_.extend(result, { suspended: suspendedCtl.value });
}
const emailCtl = this.userForm.get('email');
if (_.isString(emailCtl.value) && emailCtl.value.length > 0) {
_.extend(result, { email: emailCtl.value });
}
const maxBucketsCtl = this.userForm.get('max_buckets');
if (maxBucketsCtl.value > 0) {
_.extend(result, { 'max-buckets': maxBucketsCtl.value });
_.merge(result, { email: emailCtl.value });
}
const generateKeyCtl = this.userForm.get('generate_key');
if (!generateKeyCtl.value) {
_.extend(result, {
'access-key': this.userForm.get('access_key').value,
'secret-key': this.userForm.get('secret_key').value
_.merge(result, {
generate_key: false,
access_key: this.userForm.get('access_key').value,
secret_key: this.userForm.get('secret_key').value
});
} else {
_.extend(result, { 'generate-key': true });
}
return result;
}
@ -611,21 +608,12 @@ export class RgwUserFormComponent implements OnInit {
* Helper function to get the arguments for the API request when the user
* configuration has been modified.
*/
private _getApiPostArgs() {
const result = {
uid: this.userForm.get('user_id').value
};
const argsMap = {
'display-name': 'display_name',
email: 'email',
'max-buckets': 'max_buckets',
suspended: 'suspended'
};
for (const key of Object.keys(argsMap)) {
const ctl = this.userForm.get(argsMap[key]);
if (ctl.dirty) {
result[key] = ctl.value;
}
private _getUpdateArgs() {
const result = {};
const keys = ['display_name', 'email', 'max_buckets', 'suspended'];
for (const key of keys) {
const ctl = this.userForm.get(key);
result[key] = ctl.value;
}
return result;
}
@ -634,22 +622,21 @@ export class RgwUserFormComponent implements OnInit {
* Helper function to get the arguments for the API request when the user
* quota configuration has been modified.
*/
private _getApiUserQuotaArgs(): object {
private _getUserQuotaArgs(): object {
const result = {
uid: this.userForm.get('user_id').value,
'quota-type': 'user',
quota_type: 'user',
enabled: this.userForm.get('user_quota_enabled').value,
'max-size-kb': -1,
'max-objects': -1
max_size_kb: -1,
max_objects: -1
};
if (!this.userForm.get('user_quota_max_size_unlimited').value) {
// Convert the given value to bytes.
const bytes = new FormatterService().toBytes(this.userForm.get('user_quota_max_size').value);
// Finally convert the value to KiB.
result['max-size-kb'] = (bytes / 1024).toFixed(0) as any;
result['max_size_kb'] = (bytes / 1024).toFixed(0) as any;
}
if (!this.userForm.get('user_quota_max_objects_unlimited').value) {
result['max-objects'] = this.userForm.get('user_quota_max_objects').value;
result['max_objects'] = this.userForm.get('user_quota_max_objects').value;
}
return result;
}
@ -658,13 +645,12 @@ export class RgwUserFormComponent implements OnInit {
* Helper function to get the arguments for the API request when the bucket
* quota configuration has been modified.
*/
private _getApiBucketQuotaArgs(): object {
private _getBucketQuotaArgs(): object {
const result = {
uid: this.userForm.get('user_id').value,
'quota-type': 'bucket',
quota_type: 'bucket',
enabled: this.userForm.get('bucket_quota_enabled').value,
'max-size-kb': -1,
'max-objects': -1
max_size_kb: -1,
max_objects: -1
};
if (!this.userForm.get('bucket_quota_max_size_unlimited').value) {
// Convert the given value to bytes.
@ -672,10 +658,10 @@ export class RgwUserFormComponent implements OnInit {
this.userForm.get('bucket_quota_max_size').value
);
// Finally convert the value to KiB.
result['max-size-kb'] = (bytes / 1024).toFixed(0) as any;
result['max_size_kb'] = (bytes / 1024).toFixed(0) as any;
}
if (!this.userForm.get('bucket_quota_max_objects_unlimited').value) {
result['max-objects'] = this.userForm.get('bucket_quota_max_objects').value;
result['max_objects'] = this.userForm.get('bucket_quota_max_objects').value;
}
return result;
}

View File

@ -31,7 +31,7 @@ describe('RgwBucketService', () => {
service.list().subscribe((resp) => {
result = resp;
});
const req = httpTesting.expectOne('/api/rgw/proxy/bucket');
const req = httpTesting.expectOne('/api/rgw/bucket');
req.flush([]);
expect(req.request.method).toBe('GET');
expect(result).toEqual([]);
@ -42,13 +42,13 @@ describe('RgwBucketService', () => {
service.list().subscribe((resp) => {
result = resp;
});
let req = httpTesting.expectOne('/api/rgw/proxy/bucket');
let req = httpTesting.expectOne('/api/rgw/bucket');
req.flush(['foo', 'bar']);
req = httpTesting.expectOne('/api/rgw/proxy/bucket?bucket=foo');
req = httpTesting.expectOne('/api/rgw/bucket/foo');
req.flush({ name: 'foo' });
req = httpTesting.expectOne('/api/rgw/proxy/bucket?bucket=bar');
req = httpTesting.expectOne('/api/rgw/bucket/bar');
req.flush({ name: 'bar' });
expect(req.request.method).toBe('GET');
@ -57,35 +57,31 @@ describe('RgwBucketService', () => {
it('should call get', () => {
service.get('foo').subscribe();
const req = httpTesting.expectOne('/api/rgw/proxy/bucket?bucket=foo');
const req = httpTesting.expectOne('/api/rgw/bucket/foo');
expect(req.request.method).toBe('GET');
});
it('should call create', () => {
service.create('foo', 'bar').subscribe();
const req = httpTesting.expectOne('/api/rgw/bucket');
const req = httpTesting.expectOne('/api/rgw/bucket?bucket=foo&uid=bar');
expect(req.request.method).toBe('POST');
expect(req.request.body).toEqual({
bucket: 'foo',
uid: 'bar'
});
});
it('should call update', () => {
service.update('foo', 'bar', 'baz').subscribe();
const req = httpTesting.expectOne('/api/rgw/proxy/bucket?bucket=bar&bucket-id=foo&uid=baz');
const req = httpTesting.expectOne('/api/rgw/bucket/foo?bucket_id=bar&uid=baz');
expect(req.request.method).toBe('PUT');
});
it('should call delete, with purgeObjects = true', () => {
service.delete('foo').subscribe();
const req = httpTesting.expectOne('/api/rgw/proxy/bucket?bucket=foo&purge-objects=true');
const req = httpTesting.expectOne('/api/rgw/bucket/foo?purge_objects=true');
expect(req.request.method).toBe('DELETE');
});
it('should call delete, with purgeObjects = false', () => {
service.delete('foo', false).subscribe();
const req = httpTesting.expectOne('/api/rgw/proxy/bucket?bucket=foo&purge-objects=false');
const req = httpTesting.expectOne('/api/rgw/bucket/foo?purge_objects=false');
expect(req.request.method).toBe('DELETE');
});
@ -94,7 +90,7 @@ describe('RgwBucketService', () => {
service.exists('foo').subscribe((resp) => {
result = resp;
});
const req = httpTesting.expectOne('/api/rgw/proxy/bucket');
const req = httpTesting.expectOne('/api/rgw/bucket');
expect(req.request.method).toBe('GET');
req.flush(['foo', 'bar']);
expect(result).toBe(true);

View File

@ -11,7 +11,7 @@ import { ApiModule } from './api.module';
providedIn: ApiModule
})
export class RgwBucketService {
private url = '/api/rgw/proxy/bucket';
private url = '/api/rgw/bucket';
constructor(private http: HttpClient) {}
@ -26,10 +26,12 @@ export class RgwBucketService {
return observableForkJoin(
buckets.map((bucket: string) => {
return this.get(bucket);
}));
})
);
}
return observableOf([]);
}));
})
);
}
/**
@ -41,32 +43,27 @@ export class RgwBucketService {
}
get(bucket: string) {
let params = new HttpParams();
params = params.append('bucket', bucket);
return this.http.get(this.url, { params: params });
return this.http.get(`${this.url}/${bucket}`);
}
create(bucket: string, uid: string) {
const body = {
bucket: bucket,
uid: uid
};
return this.http.post('/api/rgw/bucket', body);
}
update(bucketId: string, bucket: string, uid: string) {
let params = new HttpParams();
params = params.append('bucket', bucket);
params = params.append('bucket-id', bucketId as string);
params = params.append('uid', uid);
return this.http.put(this.url, null, { params: params });
return this.http.post(this.url, null, { params: params });
}
update(bucket: string, bucketId: string, uid: string) {
let params = new HttpParams();
params = params.append('bucket_id', bucketId);
params = params.append('uid', uid);
return this.http.put(`${this.url}/${bucket}`, null, { params: params});
}
delete(bucket: string, purgeObjects = true) {
let params = new HttpParams();
params = params.append('bucket', bucket);
params = params.append('purge-objects', purgeObjects ? 'true' : 'false');
return this.http.delete(this.url, { params: params });
params = params.append('purge_objects', purgeObjects ? 'true' : 'false');
return this.http.delete(`${this.url}/${bucket}`, { params: params });
}
/**
@ -79,6 +76,7 @@ export class RgwBucketService {
mergeMap((resp: string[]) => {
const index = _.indexOf(resp, bucket);
return observableOf(-1 !== index);
}));
})
);
}
}

View File

@ -33,7 +33,7 @@ describe('RgwUserService', () => {
service.list().subscribe((resp) => {
result = resp;
});
const req = httpTesting.expectOne('/api/rgw/proxy/metadata/user');
const req = httpTesting.expectOne('/api/rgw/user');
expect(req.request.method).toBe('GET');
req.flush([]);
expect(result).toEqual([]);
@ -45,15 +45,15 @@ describe('RgwUserService', () => {
result = resp;
});
let req = httpTesting.expectOne('/api/rgw/proxy/metadata/user');
let req = httpTesting.expectOne('/api/rgw/user');
expect(req.request.method).toBe('GET');
req.flush(['foo', 'bar']);
req = httpTesting.expectOne('/api/rgw/proxy/user?uid=foo');
req = httpTesting.expectOne('/api/rgw/user/foo');
expect(req.request.method).toBe('GET');
req.flush({ name: 'foo' });
req = httpTesting.expectOne('/api/rgw/proxy/user?uid=bar');
req = httpTesting.expectOne('/api/rgw/user/bar');
expect(req.request.method).toBe('GET');
req.flush({ name: 'bar' });
@ -62,100 +62,79 @@ describe('RgwUserService', () => {
it('should call enumerate', () => {
service.enumerate().subscribe();
const req = httpTesting.expectOne('/api/rgw/proxy/metadata/user');
const req = httpTesting.expectOne('/api/rgw/user');
expect(req.request.method).toBe('GET');
});
it('should call get', () => {
service.get('foo').subscribe();
const req = httpTesting.expectOne('/api/rgw/proxy/user?uid=foo');
const req = httpTesting.expectOne('/api/rgw/user/foo');
expect(req.request.method).toBe('GET');
});
it('should call getQuota', () => {
service.getQuota('foo').subscribe();
const req = httpTesting.expectOne('/api/rgw/proxy/user?quota&uid=foo');
const req = httpTesting.expectOne('/api/rgw/user/foo/quota');
expect(req.request.method).toBe('GET');
});
it('should call put', () => {
service.put({ foo: 'bar' }).subscribe();
const req = httpTesting.expectOne('/api/rgw/proxy/user?foo=bar');
it('should call update', () => {
service.update('foo', { xxx: 'yyy' }).subscribe();
const req = httpTesting.expectOne('/api/rgw/user/foo?xxx=yyy');
expect(req.request.method).toBe('PUT');
});
it('should call putQuota', () => {
service.putQuota({ foo: 'bar' }).subscribe();
const req = httpTesting.expectOne('/api/rgw/proxy/user?quota&foo=bar');
it('should call updateQuota', () => {
service.updateQuota('foo', { xxx: 'yyy' }).subscribe();
const req = httpTesting.expectOne('/api/rgw/user/foo/quota?xxx=yyy');
expect(req.request.method).toBe('PUT');
});
it('should call post', () => {
service.post({ foo: 'bar' }).subscribe();
const req = httpTesting.expectOne('/api/rgw/proxy/user?foo=bar');
it('should call create', () => {
service.create({ foo: 'bar' }).subscribe();
const req = httpTesting.expectOne('/api/rgw/user?foo=bar');
expect(req.request.method).toBe('POST');
});
it('should call delete', () => {
service.delete('foo').subscribe();
const req = httpTesting.expectOne('/api/rgw/proxy/user?uid=foo');
const req = httpTesting.expectOne('/api/rgw/user/foo');
expect(req.request.method).toBe('DELETE');
});
it('should call addSubuser with unrecognized permission', () => {
service.addSubuser('foo', 'bar', 'baz', null, true).subscribe();
const req = httpTesting.expectOne(
'/api/rgw/proxy/user?uid=foo&subuser=bar&key-type=swift&access=baz&generate-secret=true'
);
expect(req.request.method).toBe('PUT');
});
it('should call addSubuser with mapped permission', () => {
service.addSubuser('foo', 'bar', 'full-control', 'baz', false).subscribe();
const req = httpTesting.expectOne(
'/api/rgw/proxy/user?uid=foo&subuser=bar&key-type=swift&access=full&secret-key=baz'
);
expect(req.request.method).toBe('PUT');
it('should call createSubuser', () => {
service.createSubuser('foo', { xxx: 'yyy' }).subscribe();
const req = httpTesting.expectOne('/api/rgw/user/foo/subuser?xxx=yyy');
expect(req.request.method).toBe('POST');
});
it('should call deleteSubuser', () => {
service.deleteSubuser('foo', 'bar').subscribe();
const req = httpTesting.expectOne('/api/rgw/proxy/user?uid=foo&subuser=bar&purge-keys=true');
const req = httpTesting.expectOne('/api/rgw/user/foo/subuser/bar');
expect(req.request.method).toBe('DELETE');
});
it('should call addCapability', () => {
service.addCapability('foo', 'bar', 'baz').subscribe();
const req = httpTesting.expectOne('/api/rgw/proxy/user?caps&uid=foo&user-caps=bar=baz');
expect(req.request.method).toBe('PUT');
const req = httpTesting.expectOne('/api/rgw/user/foo/capability?type=bar&perm=baz');
expect(req.request.method).toBe('POST');
});
it('should call deleteCapability', () => {
service.deleteCapability('foo', 'bar', 'baz').subscribe();
const req = httpTesting.expectOne('/api/rgw/proxy/user?caps&uid=foo&user-caps=bar=baz');
const req = httpTesting.expectOne('/api/rgw/user/foo/capability?type=bar&perm=baz');
expect(req.request.method).toBe('DELETE');
});
it('should call addS3Key, with generateKey true', () => {
service.addS3Key('1', 'foo', 'bar', 'baz', true).subscribe();
const req = httpTesting.expectOne(
'/api/rgw/proxy/user?key&uid=1&key-type=s3&generate-key=true&subuser=foo'
);
expect(req.request.method).toBe('PUT');
});
it('should call addS3Key, with generateKey false', () => {
service.addS3Key('1', 'foo', 'bar', 'baz', false).subscribe();
const req = httpTesting.expectOne(
'/api/rgw/proxy/user?key' +
'&uid=1&key-type=s3&generate-key=false&access-key=bar&secret-key=baz&subuser=foo'
);
expect(req.request.method).toBe('PUT');
it('should call addS3Key', () => {
service.addS3Key('foo', { xxx: 'yyy' }).subscribe();
const req = httpTesting.expectOne('/api/rgw/user/foo/key?key_type=s3&xxx=yyy');
expect(req.request.method).toBe('POST');
});
it('should call deleteS3Key', () => {
service.deleteS3Key('foo', 'bar').subscribe();
const req = httpTesting.expectOne('/api/rgw/proxy/user?key&uid=foo&key-type=s3&access-key=bar');
const req = httpTesting.expectOne('/api/rgw/user/foo/key?key_type=s3&access_key=bar');
expect(req.request.method).toBe('DELETE');
});

View File

@ -2,7 +2,7 @@ import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import * as _ from 'lodash';
import {forkJoin as observableForkJoin, of as observableOf } from 'rxjs';
import { forkJoin as observableForkJoin, of as observableOf } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import { ApiModule } from './api.module';
@ -11,7 +11,7 @@ import { ApiModule } from './api.module';
providedIn: ApiModule
})
export class RgwUserService {
private url = '/api/rgw/proxy/user';
private url = '/api/rgw/user';
constructor(private http: HttpClient) {}
@ -26,10 +26,12 @@ export class RgwUserService {
return observableForkJoin(
uids.map((uid: string) => {
return this.get(uid);
}));
})
);
}
return observableOf([]);
}));
})
);
}
/**
@ -37,38 +39,18 @@ export class RgwUserService {
* @return {Observable<string[]>}
*/
enumerate() {
return this.http.get('/api/rgw/proxy/metadata/user');
return this.http.get(this.url);
}
get(uid: string) {
let params = new HttpParams();
params = params.append('uid', uid);
return this.http.get(this.url, { params: params });
return this.http.get(`${this.url}/${uid}`);
}
getQuota(uid: string) {
let params = new HttpParams();
params = params.append('uid', uid);
return this.http.get(`${this.url}?quota`, { params: params });
return this.http.get(`${this.url}/${uid}/quota`);
}
put(args: object) {
let params = new HttpParams();
_.keys(args).forEach((key) => {
params = params.append(key, args[key]);
});
return this.http.put(this.url, null, { params: params });
}
putQuota(args: object) {
let params = new HttpParams();
_.keys(args).forEach((key) => {
params = params.append(key, args[key]);
});
return this.http.put(`${this.url}?quota`, null, { params: params });
}
post(args: object) {
create(args: object) {
let params = new HttpParams();
_.keys(args).forEach((key) => {
params = params.append(key, args[key]);
@ -76,86 +58,66 @@ export class RgwUserService {
return this.http.post(this.url, null, { params: params });
}
delete(uid: string) {
update(uid: string, args: object) {
let params = new HttpParams();
params = params.append('uid', uid);
return this.http.delete(this.url, { params: params });
_.keys(args).forEach((key) => {
params = params.append(key, args[key]);
});
return this.http.put(`${this.url}/${uid}`, null, { params: params });
}
addSubuser(
uid: string,
subuser: string,
permissions: string,
secretKey: string,
generateSecret: boolean
) {
const mapPermissions = {
'full-control': 'full',
'read-write': 'readwrite'
};
updateQuota(uid: string, args: object) {
let params = new HttpParams();
params = params.append('uid', uid);
params = params.append('subuser', subuser);
params = params.append('key-type', 'swift');
params = params.append(
'access',
permissions in mapPermissions ? mapPermissions[permissions] : permissions
);
if (generateSecret) {
params = params.append('generate-secret', 'true');
} else {
params = params.append('secret-key', secretKey);
}
return this.http.put(this.url, null, { params: params });
_.keys(args).forEach((key) => {
params = params.append(key, args[key]);
});
return this.http.put(`${this.url}/${uid}/quota`, null, { params: params });
}
delete(uid: string) {
return this.http.delete(`${this.url}/${uid}`);
}
createSubuser(uid: string, args: object) {
let params = new HttpParams();
_.keys(args).forEach((key) => {
params = params.append(key, args[key]);
});
return this.http.post(`${this.url}/${uid}/subuser`, null, { params: params });
}
deleteSubuser(uid: string, subuser: string) {
let params = new HttpParams();
params = params.append('uid', uid);
params = params.append('subuser', subuser);
params = params.append('purge-keys', 'true');
return this.http.delete(this.url, { params: params });
return this.http.delete(`${this.url}/${uid}/subuser/${subuser}`);
}
addCapability(uid: string, type: string, perm: string) {
let params = new HttpParams();
params = params.append('uid', uid);
params = params.append('user-caps', `${type}=${perm}`);
return this.http.put(`${this.url}?caps`, null, { params: params });
params = params.append('type', type);
params = params.append('perm', perm);
return this.http.post(`${this.url}/${uid}/capability`, null, { params: params });
}
deleteCapability(uid: string, type: string, perm: string) {
let params = new HttpParams();
params = params.append('uid', uid);
params = params.append('user-caps', `${type}=${perm}`);
return this.http.delete(`${this.url}?caps`, { params: params });
params = params.append('type', type);
params = params.append('perm', perm);
return this.http.delete(`${this.url}/${uid}/capability`, { params: params });
}
addS3Key(
uid: string,
subuser: string,
accessKey: string,
secretKey: string,
generateKey: boolean
) {
addS3Key(uid: string, args: object) {
let params = new HttpParams();
params = params.append('uid', uid);
params = params.append('key-type', 's3');
params = params.append('generate-key', generateKey ? 'true' : 'false');
if (!generateKey) {
params = params.append('access-key', accessKey);
params = params.append('secret-key', secretKey);
}
params = params.append('subuser', subuser);
return this.http.put(`${this.url}?key`, null, { params: params });
params = params.append('key_type', 's3');
_.keys(args).forEach((key) => {
params = params.append(key, args[key]);
});
return this.http.post(`${this.url}/${uid}/key`, null, { params: params });
}
deleteS3Key(uid: string, accessKey: string) {
let params = new HttpParams();
params = params.append('uid', uid);
params = params.append('key-type', 's3');
params = params.append('access-key', accessKey);
return this.http.delete(`${this.url}?key`, { params: params });
params = params.append('key_type', 's3');
params = params.append('access_key', accessKey);
return this.http.delete(`${this.url}/${uid}/key`, { params: params });
}
/**
@ -168,6 +130,7 @@ export class RgwUserService {
mergeMap((resp: string[]) => {
const index = _.indexOf(resp, uid);
return observableOf(-1 !== index);
}));
})
);
}
}