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:
Henry Nash committed 2015-03-08 18:22:42 +00:00
1 parent 6f6d3cecad
commit 92c676df7d
6 files changed
+326 -2

No files matched your search

+6 -1
View File
@@ -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"
}
+6 -1
View File
@@ -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"
}
+43
View File
@@ -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'
+43
View File
@@ -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)
+18
View File
@@ -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')}},
}