Add API support for domain config
Building on previous patches, this adds the public API support for domain configurations being stored in the database. Partially Implements: blueprint domain-config-ext Change-Id: Idcd37a55a1179ef9be088323414cd76e7bfcd6bb
This commit is contained in:
1 parent
6f6d3cecad
commit
92c676df7d
6 files changed
+326
-2
No files matched your search
+6
-1
@@ -172,5 +172,10 @@
|
||||
"identity:check_policy_association_for_region_and_service": "rule:admin_required",
|
||||
"identity:delete_policy_association_for_region_and_service": "rule:admin_required",
|
||||
"identity:get_policy_for_endpoint": "rule:admin_required",
|
||||
"identity:list_endpoints_for_policy": "rule:admin_required"
|
||||
"identity:list_endpoints_for_policy": "rule:admin_required",
|
||||
|
||||
"identity:create_domain_config": "rule:admin_required",
|
||||
"identity:get_domain_config": "rule:admin_required",
|
||||
"identity:update_domain_config": "rule:admin_required",
|
||||
"identity:delete_domain_config": "rule:admin_required"
|
||||
}
|
||||
@@ -185,5 +185,10 @@
|
||||
"identity:check_policy_association_for_region_and_service": "rule:cloud_admin",
|
||||
"identity:delete_policy_association_for_region_and_service": "rule:cloud_admin",
|
||||
"identity:get_policy_for_endpoint": "rule:cloud_admin",
|
||||
"identity:list_endpoints_for_policy": "rule:cloud_admin"
|
||||
"identity:list_endpoints_for_policy": "rule:cloud_admin",
|
||||
|
||||
"identity:create_domain_config": "rule:cloud_admin",
|
||||
"identity:get_domain_config": "rule:cloud_admin",
|
||||
"identity:update_domain_config": "rule:cloud_admin",
|
||||
"identity:delete_domain_config": "rule:cloud_admin"
|
||||
}
|
||||
@@ -23,6 +23,7 @@ from oslo_log import log
|
||||
from keystone.common import controller
|
||||
from keystone.common import dependency
|
||||
from keystone.common import validation
|
||||
from keystone.common import wsgi
|
||||
from keystone import exception
|
||||
from keystone.i18n import _
|
||||
from keystone import notifications
|
||||
@@ -144,6 +145,48 @@ class DomainV3(controller.V3Controller):
|
||||
return self.resource_api.delete_domain(domain_id, initiator)
|
||||
|
||||
|
||||
@dependency.requires('domain_config_api')
|
||||
class DomainConfigV3(controller.V3Controller):
|
||||
member_name = 'config'
|
||||
|
||||
@controller.protected()
|
||||
def create_domain_config(self, context, domain_id, config):
|
||||
original_config = (
|
||||
self.domain_config_api.get_config_with_sensitive_info(domain_id))
|
||||
ref = self.domain_config_api.create_config(domain_id, config)
|
||||
if original_config:
|
||||
# Return status code 200, since config already existed
|
||||
return wsgi.render_response(body={self.member_name: ref})
|
||||
else:
|
||||
return wsgi.render_response(body={self.member_name: ref},
|
||||
status=('201', 'Created'))
|
||||
|
||||
@controller.protected()
|
||||
def get_domain_config(self, context, domain_id, group=None, option=None):
|
||||
ref = self.domain_config_api.get_config(domain_id, group, option)
|
||||
return {self.member_name: ref}
|
||||
|
||||
@controller.protected()
|
||||
def update_domain_config(
|
||||
self, context, domain_id, config, group, option):
|
||||
ref = self.domain_config_api.update_config(
|
||||
domain_id, config, group, option)
|
||||
return wsgi.render_response(body={self.member_name: ref})
|
||||
|
||||
def update_domain_config_group(self, context, domain_id, group, config):
|
||||
return self.update_domain_config(
|
||||
context, domain_id, config, group, option=None)
|
||||
|
||||
def update_domain_config_only(self, context, domain_id, config):
|
||||
return self.update_domain_config(
|
||||
context, domain_id, config, group=None, option=None)
|
||||
|
||||
@controller.protected()
|
||||
def delete_domain_config(
|
||||
self, context, domain_id, group=None, option=None):
|
||||
self.domain_config_api.delete_config(domain_id, group, option)
|
||||
|
||||
|
||||
@dependency.requires('resource_api')
|
||||
class ProjectV3(controller.V3Controller):
|
||||
collection_name = 'projects'
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
"""WSGI Routers for the Resource service."""
|
||||
|
||||
from keystone.common import json_home
|
||||
from keystone.common import router
|
||||
from keystone.common import wsgi
|
||||
from keystone.resource import controllers
|
||||
@@ -42,6 +43,48 @@ class Routers(wsgi.RoutersBase):
|
||||
'domains', 'domain',
|
||||
resource_descriptions=self.v3_resources))
|
||||
|
||||
config_controller = controllers.DomainConfigV3()
|
||||
|
||||
self._add_resource(
|
||||
mapper, config_controller,
|
||||
path='/domains/{domain_id}/config',
|
||||
get_head_action='get_domain_config',
|
||||
put_action='create_domain_config',
|
||||
patch_action='update_domain_config_only',
|
||||
delete_action='delete_domain_config',
|
||||
rel=json_home.build_v3_resource_relation('domain_config'),
|
||||
path_vars={
|
||||
'domain_id': json_home.Parameters.DOMAIN_ID
|
||||
})
|
||||
|
||||
confg_group_param = (
|
||||
json_home.build_v3_parameter_relation('config_group'))
|
||||
self._add_resource(
|
||||
mapper, config_controller,
|
||||
path='/domains/{domain_id}/config/{group}',
|
||||
get_head_action='get_domain_config',
|
||||
patch_action='update_domain_config_group',
|
||||
delete_action='delete_domain_config',
|
||||
rel=json_home.build_v3_resource_relation('domain_config_group'),
|
||||
path_vars={
|
||||
'domain_id': json_home.Parameters.DOMAIN_ID,
|
||||
'group': confg_group_param
|
||||
})
|
||||
|
||||
self._add_resource(
|
||||
mapper, config_controller,
|
||||
path='/domains/{domain_id}/config/{group}/{option}',
|
||||
get_head_action='get_domain_config',
|
||||
patch_action='update_domain_config',
|
||||
delete_action='delete_domain_config',
|
||||
rel=json_home.build_v3_resource_relation('domain_config_option'),
|
||||
path_vars={
|
||||
'domain_id': json_home.Parameters.DOMAIN_ID,
|
||||
'group': confg_group_param,
|
||||
'option': json_home.build_v3_parameter_relation(
|
||||
'config_option')
|
||||
})
|
||||
|
||||
routers.append(
|
||||
router.Router(controllers.ProjectV3(),
|
||||
'projects', 'project',
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
# 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 copy
|
||||
import uuid
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from keystone import exception
|
||||
from keystone.tests.unit import test_v3
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class DomainConfigTestCase(test_v3.RestfulTestCase):
|
||||
"""Test domain config support."""
|
||||
|
||||
def setUp(self):
|
||||
super(DomainConfigTestCase, self).setUp()
|
||||
|
||||
self.domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||
self.resource_api.create_domain(self.domain['id'], self.domain)
|
||||
self.config = {'ldap': {'url': uuid.uuid4().hex,
|
||||
'user_tree_dn': uuid.uuid4().hex},
|
||||
'identity': {'driver': uuid.uuid4().hex}}
|
||||
|
||||
def test_create_config(self):
|
||||
"""Call ``PUT /domains/{domain_id}/config``."""
|
||||
url = '/domains/%(domain_id)s/config' % {
|
||||
'domain_id': self.domain['id']}
|
||||
r = self.put(url, body={'config': self.config},
|
||||
expected_status=201)
|
||||
res = self.domain_config_api.get_config(self.domain['id'])
|
||||
self.assertEqual(self.config, r.result['config'])
|
||||
self.assertEqual(self.config, res)
|
||||
|
||||
def test_create_config_twice(self):
|
||||
"""Check multiple creates don't throw error"""
|
||||
self.put('/domains/%(domain_id)s/config' % {
|
||||
'domain_id': self.domain['id']},
|
||||
body={'config': self.config},
|
||||
expected_status=201)
|
||||
self.put('/domains/%(domain_id)s/config' % {
|
||||
'domain_id': self.domain['id']},
|
||||
body={'config': self.config},
|
||||
expected_status=200)
|
||||
|
||||
def test_delete_config(self):
|
||||
"""Call ``DELETE /domains{domain_id}/config``."""
|
||||
self.domain_config_api.create_config(self.domain['id'], self.config)
|
||||
self.delete('/domains/%(domain_id)s/config' % {
|
||||
'domain_id': self.domain['id']})
|
||||
self.get('/domains/%(domain_id)s/config' % {
|
||||
'domain_id': self.domain['id']},
|
||||
expected_status=exception.DomainConfigNotFound.code)
|
||||
|
||||
def test_delete_config_by_group(self):
|
||||
"""Call ``DELETE /domains{domain_id}/config/{group}``."""
|
||||
self.domain_config_api.create_config(self.domain['id'], self.config)
|
||||
self.delete('/domains/%(domain_id)s/config/ldap' % {
|
||||
'domain_id': self.domain['id']})
|
||||
res = self.domain_config_api.get_config(self.domain['id'])
|
||||
self.assertNotIn('ldap', res)
|
||||
|
||||
def test_get_head_config(self):
|
||||
"""Call ``GET & HEAD for /domains{domain_id}/config``."""
|
||||
self.domain_config_api.create_config(self.domain['id'], self.config)
|
||||
url = '/domains/%(domain_id)s/config' % {
|
||||
'domain_id': self.domain['id']}
|
||||
r = self.get(url)
|
||||
self.assertEqual(self.config, r.result['config'])
|
||||
self.head(url, expected_status=200)
|
||||
|
||||
def test_get_config_by_group(self):
|
||||
"""Call ``GET & HEAD /domains{domain_id}/config/{group}``."""
|
||||
self.domain_config_api.create_config(self.domain['id'], self.config)
|
||||
url = '/domains/%(domain_id)s/config/ldap' % {
|
||||
'domain_id': self.domain['id']}
|
||||
r = self.get(url)
|
||||
self.assertEqual({'ldap': self.config['ldap']}, r.result['config'])
|
||||
self.head(url, expected_status=200)
|
||||
|
||||
def test_get_config_by_option(self):
|
||||
"""Call ``GET & HEAD /domains{domain_id}/config/{group}/{option}``."""
|
||||
self.domain_config_api.create_config(self.domain['id'], self.config)
|
||||
url = '/domains/%(domain_id)s/config/ldap/url' % {
|
||||
'domain_id': self.domain['id']}
|
||||
r = self.get(url)
|
||||
self.assertEqual({'url': self.config['ldap']['url']},
|
||||
r.result['config'])
|
||||
self.head(url, expected_status=200)
|
||||
|
||||
def test_get_non_existant_config(self):
|
||||
"""Call ``GET /domains{domain_id}/config when no config defined``."""
|
||||
self.get('/domains/%(domain_id)s/config' % {
|
||||
'domain_id': self.domain['id']}, expected_status=404)
|
||||
|
||||
def test_get_non_existant_config_group(self):
|
||||
"""Call ``GET /domains{domain_id}/config/{group_not_exist}``."""
|
||||
config = {'ldap': {'url': uuid.uuid4().hex}}
|
||||
self.domain_config_api.create_config(self.domain['id'], config)
|
||||
self.get('/domains/%(domain_id)s/config/identity' % {
|
||||
'domain_id': self.domain['id']}, expected_status=404)
|
||||
|
||||
def test_get_non_existant_config_option(self):
|
||||
"""Call ``GET /domains{domain_id}/config/group/{option_not_exist}``."""
|
||||
config = {'ldap': {'url': uuid.uuid4().hex}}
|
||||
self.domain_config_api.create_config(self.domain['id'], config)
|
||||
self.get('/domains/%(domain_id)s/config/ldap/user_tree_dn' % {
|
||||
'domain_id': self.domain['id']}, expected_status=404)
|
||||
|
||||
def test_update_config(self):
|
||||
"""Call ``PATCH /domains/{domain_id}/config``."""
|
||||
self.domain_config_api.create_config(self.domain['id'], self.config)
|
||||
new_config = {'ldap': {'url': uuid.uuid4().hex},
|
||||
'identity': {'driver': uuid.uuid4().hex}}
|
||||
r = self.patch('/domains/%(domain_id)s/config' % {
|
||||
'domain_id': self.domain['id']},
|
||||
body={'config': new_config})
|
||||
res = self.domain_config_api.get_config(self.domain['id'])
|
||||
expected_config = copy.deepcopy(self.config)
|
||||
expected_config['ldap']['url'] = new_config['ldap']['url']
|
||||
expected_config['identity']['driver'] = (
|
||||
new_config['identity']['driver'])
|
||||
self.assertEqual(expected_config, r.result['config'])
|
||||
self.assertEqual(expected_config, res)
|
||||
|
||||
def test_update_config_group(self):
|
||||
"""Call ``PATCH /domains/{domain_id}/config/{group}``."""
|
||||
self.domain_config_api.create_config(self.domain['id'], self.config)
|
||||
new_config = {'ldap': {'url': uuid.uuid4().hex,
|
||||
'user_filter': uuid.uuid4().hex}}
|
||||
r = self.patch('/domains/%(domain_id)s/config/ldap' % {
|
||||
'domain_id': self.domain['id']},
|
||||
body={'config': new_config})
|
||||
res = self.domain_config_api.get_config(self.domain['id'])
|
||||
expected_config = copy.deepcopy(self.config)
|
||||
expected_config['ldap']['url'] = new_config['ldap']['url']
|
||||
expected_config['ldap']['user_filter'] = (
|
||||
new_config['ldap']['user_filter'])
|
||||
self.assertEqual(expected_config, r.result['config'])
|
||||
self.assertEqual(expected_config, res)
|
||||
|
||||
def test_update_config_invalid_group(self):
|
||||
"""Call ``PATCH /domains/{domain_id}/config/{invalid_group}``."""
|
||||
self.domain_config_api.create_config(self.domain['id'], self.config)
|
||||
|
||||
# Trying to update a group that is neither whitelisted or sensitive
|
||||
# should result in Forbidden.
|
||||
invalid_group = uuid.uuid4().hex
|
||||
new_config = {invalid_group: {'url': uuid.uuid4().hex,
|
||||
'user_filter': uuid.uuid4().hex}}
|
||||
self.patch('/domains/%(domain_id)s/config/%(invalid_group)s' % {
|
||||
'domain_id': self.domain['id'], 'invalid_group': invalid_group},
|
||||
body={'config': new_config},
|
||||
expected_status=403)
|
||||
# Trying to update a valid group, but one that is not in the current
|
||||
# config should result in NotFound
|
||||
config = {'ldap': {'suffix': uuid.uuid4().hex}}
|
||||
self.domain_config_api.create_config(self.domain['id'], config)
|
||||
new_config = {'identity': {'driver': uuid.uuid4().hex}}
|
||||
self.patch('/domains/%(domain_id)s/config/identity' % {
|
||||
'domain_id': self.domain['id']},
|
||||
body={'config': new_config},
|
||||
expected_status=404)
|
||||
|
||||
def test_update_config_option(self):
|
||||
"""Call ``PATCH /domains/{domain_id}/config/{group}/{option}``."""
|
||||
self.domain_config_api.create_config(self.domain['id'], self.config)
|
||||
new_config = {'url': uuid.uuid4().hex}
|
||||
r = self.patch('/domains/%(domain_id)s/config/ldap/url' % {
|
||||
'domain_id': self.domain['id']},
|
||||
body={'config': new_config})
|
||||
res = self.domain_config_api.get_config(self.domain['id'])
|
||||
expected_config = copy.deepcopy(self.config)
|
||||
expected_config['ldap']['url'] = new_config['url']
|
||||
self.assertEqual(expected_config, r.result['config'])
|
||||
self.assertEqual(expected_config, res)
|
||||
|
||||
def test_update_config_invalid_option(self):
|
||||
"""Call ``PATCH /domains/{domain_id}/config/{group}/{invalid}``."""
|
||||
self.domain_config_api.create_config(self.domain['id'], self.config)
|
||||
invalid_option = uuid.uuid4().hex
|
||||
new_config = {'ldap': {invalid_option: uuid.uuid4().hex}}
|
||||
# Trying to update an option that is neither whitelisted or sensitive
|
||||
# should result in Forbidden.
|
||||
self.patch(
|
||||
'/domains/%(domain_id)s/config/ldap/%(invalid_option)s' % {
|
||||
'domain_id': self.domain['id'],
|
||||
'invalid_option': invalid_option},
|
||||
body={'config': new_config},
|
||||
expected_status=403)
|
||||
# Trying to update a valid option, but one that is not in the current
|
||||
# config should result in NotFound
|
||||
new_config = {'suffix': uuid.uuid4().hex}
|
||||
self.patch(
|
||||
'/domains/%(domain_id)s/config/ldap/suffix' % {
|
||||
'domain_id': self.domain['id']},
|
||||
body={'config': new_config},
|
||||
expected_status=404)
|
||||
@@ -490,6 +490,24 @@ V3_JSON_HOME_RESOURCES_INHERIT_DISABLED = {
|
||||
'href-template': BASE_EP_FILTER + '/projects',
|
||||
'href-vars': {'endpoint_group_id':
|
||||
ENDPOINT_GROUP_ID_PARAMETER_RELATION, }},
|
||||
json_home.build_v3_resource_relation('domain_config'): {
|
||||
'href-template':
|
||||
'/domains/{domain_id}/config',
|
||||
'href-vars': {
|
||||
'domain_id': json_home.Parameters.DOMAIN_ID}},
|
||||
json_home.build_v3_resource_relation('domain_config_group'): {
|
||||
'href-template':
|
||||
'/domains/{domain_id}/config/{group}',
|
||||
'href-vars': {
|
||||
'domain_id': json_home.Parameters.DOMAIN_ID,
|
||||
'group': json_home.build_v3_parameter_relation('config_group')}},
|
||||
json_home.build_v3_resource_relation('domain_config_option'): {
|
||||
'href-template':
|
||||
'/domains/{domain_id}/config/{group}/{option}',
|
||||
'href-vars': {
|
||||
'domain_id': json_home.Parameters.DOMAIN_ID,
|
||||
'group': json_home.build_v3_parameter_relation('config_group'),
|
||||
'option': json_home.build_v3_parameter_relation('config_option')}},
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user