Merge "Implement password requirements API"
This commit is contained in:
commit
9ac29418e6
@ -193,6 +193,8 @@ identity:create_domain_config PUT /v3/domains/{doma
|
||||
identity:get_domain_config - GET /v3/domains/{domain_id}/config
|
||||
- GET /v3/domains/{domain_id}/config/{group}
|
||||
- GET /v3/domains/{domain_id}/config/{group}/{option}
|
||||
identity:get_security_compliance_domain_config - GET /v3/domains/{domain_id}/config/security_compliance
|
||||
- GET /v3/domains/{domain_id}/config/security_compliance/{option}
|
||||
identity:update_domain_config - PATCH /v3/domains/{domain_id}/config
|
||||
- PATCH /v3/domains/{domain_id}/config/{group}
|
||||
- PATCH /v3/domains/{domain_id}/config/{group}/{option}
|
||||
|
@ -192,6 +192,7 @@
|
||||
|
||||
"identity:create_domain_config": "rule:admin_required",
|
||||
"identity:get_domain_config": "rule:admin_required",
|
||||
"identity:get_security_compliance_domain_config": "",
|
||||
"identity:update_domain_config": "rule:admin_required",
|
||||
"identity:delete_domain_config": "rule:admin_required",
|
||||
"identity:get_domain_config_default": "rule:admin_required"
|
||||
|
@ -219,6 +219,7 @@
|
||||
|
||||
"identity:create_domain_config": "rule:cloud_admin",
|
||||
"identity:get_domain_config": "rule:cloud_admin",
|
||||
"identity:get_security_compliance_domain_config": "",
|
||||
"identity:update_domain_config": "rule:cloud_admin",
|
||||
"identity:delete_domain_config": "rule:cloud_admin",
|
||||
"identity:get_domain_config_default": "rule:cloud_admin"
|
||||
|
@ -185,6 +185,25 @@ class DomainConfigV3(controller.V3Controller):
|
||||
status=(http_client.CREATED,
|
||||
http_client.responses[http_client.CREATED]))
|
||||
|
||||
def get_domain_config_wrapper(self, request, domain_id, group=None,
|
||||
option=None):
|
||||
if group and group == 'security_compliance':
|
||||
return self.get_security_compliance_domain_config(
|
||||
request, domain_id, group=group, option=option
|
||||
)
|
||||
else:
|
||||
return self.get_domain_config(
|
||||
request, domain_id, group=group, option=option
|
||||
)
|
||||
|
||||
@controller.protected()
|
||||
def get_security_compliance_domain_config(self, request, domain_id,
|
||||
group=None, option=None):
|
||||
ref = self.domain_config_api.get_security_compliance_config(
|
||||
domain_id, group, option=option
|
||||
)
|
||||
return {self.member_name: ref}
|
||||
|
||||
@controller.protected()
|
||||
def get_domain_config(self, request, domain_id, group=None, option=None):
|
||||
self.resource_api.get_domain(domain_id)
|
||||
|
@ -1131,6 +1131,53 @@ class DomainConfigManager(manager.Manager):
|
||||
raise exception.DomainConfigNotFound(
|
||||
domain_id=domain_id, group_or_option=msg)
|
||||
|
||||
def get_security_compliance_config(self, domain_id, group, option=None):
|
||||
r"""Get full or partial security compliance config from configuration.
|
||||
|
||||
:param domain_id: the domain in question
|
||||
:param group: a specific group of options
|
||||
:param option: an optional specific option within the group
|
||||
|
||||
:returns: a dict of group dicts containing the whitelisted options,
|
||||
filtered by group and option specified
|
||||
:raises keystone.exception.InvalidDomainConfig: when the config
|
||||
and group/option parameters specify an option we do not
|
||||
support
|
||||
|
||||
An example response::
|
||||
|
||||
{
|
||||
'security_compliance': {
|
||||
'password_regex': '^(?=.*\d)(?=.*[a-zA-Z]).{7,}$'
|
||||
'password_regex_description':
|
||||
'A password must consist of at least 1 letter, '
|
||||
'1 digit, and have a minimum length of 7 characters'
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
if domain_id != CONF.identity.default_domain_id:
|
||||
msg = _('Reading security compliance information for any domain '
|
||||
'other than the default domain is not allowed or '
|
||||
'supported.')
|
||||
raise exception.InvalidDomainConfig(reason=msg)
|
||||
|
||||
config_list = []
|
||||
readable_options = ['password_regex', 'password_regex_description']
|
||||
if option and option not in readable_options:
|
||||
msg = _('Reading security compliance values other than '
|
||||
'password_regex and password_regex_description is not '
|
||||
'allowed.')
|
||||
raise exception.InvalidDomainConfig(reason=msg)
|
||||
elif option and option in readable_options:
|
||||
config_list.append(self._option_dict(group, option))
|
||||
elif not option:
|
||||
for op in readable_options:
|
||||
config_list.append(self._option_dict(group, op))
|
||||
# We already validated that the group is the security_compliance group
|
||||
# so we can move along and start validating the options
|
||||
return self._list_to_config(config_list, req_option=option)
|
||||
|
||||
def update_config(self, domain_id, config, group=None, option=None):
|
||||
"""Update config, or partial config, for a domain.
|
||||
|
||||
|
@ -62,7 +62,7 @@ class Routers(wsgi.RoutersBase):
|
||||
self._add_resource(
|
||||
mapper, config_controller,
|
||||
path='/domains/{domain_id}/config/{group}',
|
||||
get_head_action='get_domain_config',
|
||||
get_head_action='get_domain_config_wrapper',
|
||||
patch_action='update_domain_config_group',
|
||||
delete_action='delete_domain_config',
|
||||
rel=json_home.build_v3_resource_relation('domain_config_group'),
|
||||
@ -74,7 +74,7 @@ class Routers(wsgi.RoutersBase):
|
||||
self._add_resource(
|
||||
mapper, config_controller,
|
||||
path='/domains/{domain_id}/config/{group}/{option}',
|
||||
get_head_action='get_domain_config',
|
||||
get_head_action='get_domain_config_wrapper',
|
||||
patch_action='update_domain_config',
|
||||
delete_action='delete_domain_config',
|
||||
rel=json_home.build_v3_resource_relation('domain_config_option'),
|
||||
|
@ -457,3 +457,587 @@ class DomainConfigTestCase(test_v3.RestfulTestCase):
|
||||
# Now try a totally invalid option
|
||||
url = '/domains/config/ldap/%s/default' % uuid.uuid4().hex
|
||||
self.get(url, expected_status=http_client.FORBIDDEN)
|
||||
|
||||
|
||||
class SecurityRequirementsTestCase(test_v3.RestfulTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SecurityRequirementsTestCase, self).setUp()
|
||||
|
||||
# Create a user in the default domain
|
||||
self.non_admin_user = unit.create_user(
|
||||
self.identity_api,
|
||||
CONF.identity.default_domain_id
|
||||
)
|
||||
|
||||
# Create an admin in the default domain
|
||||
self.admin_user = unit.create_user(
|
||||
self.identity_api,
|
||||
CONF.identity.default_domain_id
|
||||
)
|
||||
|
||||
# Create a project in the default domain and a non-admin role
|
||||
self.project = unit.new_project_ref(
|
||||
domain_id=CONF.identity.default_domain_id
|
||||
)
|
||||
self.resource_api.create_project(self.project['id'], self.project)
|
||||
self.non_admin_role = unit.new_role_ref(name='not_admin')
|
||||
self.role_api.create_role(
|
||||
self.non_admin_role['id'],
|
||||
self.non_admin_role
|
||||
)
|
||||
|
||||
# Give the non-admin user a role on the project
|
||||
self.assignment_api.add_role_to_user_and_project(
|
||||
self.non_admin_user['id'],
|
||||
self.project['id'],
|
||||
self.role['id']
|
||||
)
|
||||
|
||||
# Give the user the admin role on the project, which is technically
|
||||
# `self.role` because RestfulTestCase sets that up for us.
|
||||
self.assignment_api.add_role_to_user_and_project(
|
||||
self.admin_user['id'],
|
||||
self.project['id'],
|
||||
self.role_id
|
||||
)
|
||||
|
||||
def _get_non_admin_token(self):
|
||||
non_admin_auth_data = self.build_authentication_request(
|
||||
user_id=self.non_admin_user['id'],
|
||||
password=self.non_admin_user['password'],
|
||||
project_id=self.project['id']
|
||||
)
|
||||
return self.get_requested_token(non_admin_auth_data)
|
||||
|
||||
def _get_admin_token(self):
|
||||
non_admin_auth_data = self.build_authentication_request(
|
||||
user_id=self.admin_user['id'],
|
||||
password=self.admin_user['password'],
|
||||
project_id=self.project['id']
|
||||
)
|
||||
return self.get_requested_token(non_admin_auth_data)
|
||||
|
||||
def test_get_security_compliance_config_for_default_domain(self):
|
||||
"""Ask for all security compliance configuration options.
|
||||
|
||||
Support for enforcing security compliance per domain currently doesn't
|
||||
exist. Make sure when we ask for security compliance information, it's
|
||||
only for the default domain and that it only returns whitelisted
|
||||
options.
|
||||
"""
|
||||
password_regex = uuid.uuid4().hex
|
||||
password_regex_description = uuid.uuid4().hex
|
||||
self.config_fixture.config(
|
||||
group='security_compliance',
|
||||
password_regex=password_regex
|
||||
)
|
||||
self.config_fixture.config(
|
||||
group='security_compliance',
|
||||
password_regex_description=password_regex_description
|
||||
)
|
||||
expected_response = {
|
||||
'security_compliance': {
|
||||
'password_regex': password_regex,
|
||||
'password_regex_description': password_regex_description
|
||||
}
|
||||
}
|
||||
url = (
|
||||
'/domains/%(domain_id)s/config/%(group)s' %
|
||||
{
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'group': 'security_compliance',
|
||||
}
|
||||
)
|
||||
|
||||
# Make sure regular users and administrators can get security
|
||||
# requirement information.
|
||||
regular_response = self.get(url, token=self._get_non_admin_token())
|
||||
self.assertEqual(regular_response.result['config'], expected_response)
|
||||
admin_response = self.get(url, token=self._get_admin_token())
|
||||
self.assertEqual(admin_response.result['config'], expected_response)
|
||||
|
||||
def test_get_security_compliance_config_for_non_default_domain_fails(self):
|
||||
"""Getting security compliance opts for other domains should fail.
|
||||
|
||||
Support for enforcing security compliance rules per domain currently
|
||||
does not exist, so exposing security compliance information for any
|
||||
domain other than the default domain should not be allowed.
|
||||
"""
|
||||
# Create a new domain that is not the default domain
|
||||
domain = unit.new_domain_ref()
|
||||
self.resource_api.create_domain(domain['id'], domain)
|
||||
|
||||
# Set the security compliance configuration options
|
||||
password_regex = uuid.uuid4().hex
|
||||
password_regex_description = uuid.uuid4().hex
|
||||
self.config_fixture.config(
|
||||
group='security_compliance',
|
||||
password_regex=password_regex
|
||||
)
|
||||
self.config_fixture.config(
|
||||
group='security_compliance',
|
||||
password_regex_description=password_regex_description
|
||||
)
|
||||
url = (
|
||||
'/domains/%(domain_id)s/config/%(group)s' %
|
||||
{
|
||||
'domain_id': domain['id'],
|
||||
'group': 'security_compliance',
|
||||
}
|
||||
)
|
||||
|
||||
# Make sure regular users and administrators are forbidden from doing
|
||||
# this.
|
||||
self.get(
|
||||
url,
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_non_admin_token()
|
||||
)
|
||||
self.get(
|
||||
url,
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_admin_token()
|
||||
)
|
||||
|
||||
def test_get_non_whitelisted_security_compliance_opt_fails(self):
|
||||
"""We only support exposing a subset of security compliance options.
|
||||
|
||||
Given that security compliance information is sensitive in nature, we
|
||||
should make sure that only the options we want to expose are readable
|
||||
via the API.
|
||||
"""
|
||||
# Set a security compliance configuration that isn't whitelisted
|
||||
self.config_fixture.config(
|
||||
group='security_compliance',
|
||||
lockout_failure_attempts=1
|
||||
)
|
||||
url = (
|
||||
'/domains/%(domain_id)s/config/%(group)s/%(option)s' %
|
||||
{
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'group': 'security_compliance',
|
||||
'option': 'lockout_failure_attempts'
|
||||
}
|
||||
)
|
||||
|
||||
# Make sure regular users and administrators are unable to ask for
|
||||
# sensitive information.
|
||||
self.get(
|
||||
url,
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_non_admin_token()
|
||||
)
|
||||
self.get(
|
||||
url,
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_admin_token()
|
||||
)
|
||||
|
||||
def test_get_security_compliance_password_regex(self):
|
||||
"""Ask for the security compliance password regular expression."""
|
||||
password_regex = uuid.uuid4().hex
|
||||
self.config_fixture.config(
|
||||
group='security_compliance',
|
||||
password_regex=password_regex
|
||||
)
|
||||
group = 'security_compliance'
|
||||
option = 'password_regex'
|
||||
url = (
|
||||
'/domains/%(domain_id)s/config/%(group)s/%(option)s' %
|
||||
{
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'group': group,
|
||||
'option': option
|
||||
}
|
||||
)
|
||||
|
||||
# Make sure regular users and administrators can ask for the
|
||||
# password regular expression.
|
||||
regular_response = self.get(url, token=self._get_non_admin_token())
|
||||
self.assertEqual(
|
||||
regular_response.result['config'][option],
|
||||
password_regex
|
||||
)
|
||||
admin_response = self.get(url, token=self._get_admin_token())
|
||||
self.assertEqual(
|
||||
admin_response.result['config'][option],
|
||||
password_regex
|
||||
)
|
||||
|
||||
def test_get_security_compliance_password_regex_description(self):
|
||||
"""Ask for the security compliance password regex description."""
|
||||
password_regex_description = uuid.uuid4().hex
|
||||
self.config_fixture.config(
|
||||
group='security_compliance',
|
||||
password_regex_description=password_regex_description
|
||||
)
|
||||
group = 'security_compliance'
|
||||
option = 'password_regex_description'
|
||||
url = (
|
||||
'/domains/%(domain_id)s/config/%(group)s/%(option)s' %
|
||||
{
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'group': group,
|
||||
'option': option
|
||||
}
|
||||
)
|
||||
|
||||
# Make sure regular users and administrators can ask for the
|
||||
# password regular expression.
|
||||
regular_response = self.get(url, token=self._get_non_admin_token())
|
||||
self.assertEqual(
|
||||
regular_response.result['config'][option],
|
||||
password_regex_description
|
||||
)
|
||||
admin_response = self.get(url, token=self._get_admin_token())
|
||||
self.assertEqual(
|
||||
admin_response.result['config'][option],
|
||||
password_regex_description
|
||||
)
|
||||
|
||||
def test_get_security_compliance_password_regex_returns_none(self):
|
||||
"""When an option isn't set, we should explicitly return None."""
|
||||
group = 'security_compliance'
|
||||
option = 'password_regex'
|
||||
url = (
|
||||
'/domains/%(domain_id)s/config/%(group)s/%(option)s' %
|
||||
{
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'group': group,
|
||||
'option': option
|
||||
}
|
||||
)
|
||||
|
||||
# Make sure regular users and administrators can ask for the password
|
||||
# regular expression, but since it isn't set the returned value should
|
||||
# be None.
|
||||
regular_response = self.get(url, token=self._get_non_admin_token())
|
||||
self.assertIsNone(regular_response.result['config'][option])
|
||||
admin_response = self.get(url, token=self._get_admin_token())
|
||||
self.assertIsNone(admin_response.result['config'][option])
|
||||
|
||||
def test_get_security_compliance_password_regex_desc_returns_none(self):
|
||||
"""When an option isn't set, we should explicitly return None."""
|
||||
group = 'security_compliance'
|
||||
option = 'password_regex_description'
|
||||
url = (
|
||||
'/domains/%(domain_id)s/config/%(group)s/%(option)s' %
|
||||
{
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'group': group,
|
||||
'option': option
|
||||
}
|
||||
)
|
||||
|
||||
# Make sure regular users and administrators can ask for the password
|
||||
# regular expression description, but since it isn't set the returned
|
||||
# value should be None.
|
||||
regular_response = self.get(url, token=self._get_non_admin_token())
|
||||
self.assertIsNone(regular_response.result['config'][option])
|
||||
admin_response = self.get(url, token=self._get_admin_token())
|
||||
self.assertIsNone(admin_response.result['config'][option])
|
||||
|
||||
def test_get_security_compliance_config_with_user_from_other_domain(self):
|
||||
"""Make sure users from other domains can access password requirements.
|
||||
|
||||
Even though a user is in a separate domain, they should be able to see
|
||||
the security requirements for the deployment. This is because security
|
||||
compliance is not yet implemented on a per domain basis. Once that
|
||||
happens, then this should no longer be possible since a user should
|
||||
only care about the security compliance requirements for the domain
|
||||
that they are in.
|
||||
"""
|
||||
# Make a new domain
|
||||
domain = unit.new_domain_ref()
|
||||
self.resource_api.create_domain(domain['id'], domain)
|
||||
|
||||
# Create a user in the new domain
|
||||
user = unit.create_user(self.identity_api, domain['id'])
|
||||
|
||||
# Create a project in the new domain
|
||||
project = unit.new_project_ref(domain_id=domain['id'])
|
||||
self.resource_api.create_project(project['id'], project)
|
||||
|
||||
# Give the new user a non-admin role on the project
|
||||
self.assignment_api.add_role_to_user_and_project(
|
||||
user['id'],
|
||||
project['id'],
|
||||
self.non_admin_role['id']
|
||||
)
|
||||
|
||||
# Set our security compliance config values, we do this after we've
|
||||
# created our test user otherwise password validation will fail with a
|
||||
# uuid type regex.
|
||||
password_regex = uuid.uuid4().hex
|
||||
password_regex_description = uuid.uuid4().hex
|
||||
group = 'security_compliance'
|
||||
self.config_fixture.config(
|
||||
group=group,
|
||||
password_regex=password_regex
|
||||
)
|
||||
self.config_fixture.config(
|
||||
group=group,
|
||||
password_regex_description=password_regex_description
|
||||
)
|
||||
|
||||
# Get a token for the newly created user scoped to the project in the
|
||||
# non-default domain and use it to get the password security
|
||||
# requirements.
|
||||
user_token = self.build_authentication_request(
|
||||
user_id=user['id'],
|
||||
password=user['password'],
|
||||
project_id=project['id']
|
||||
)
|
||||
user_token = self.get_requested_token(user_token)
|
||||
url = (
|
||||
'/domains/%(domain_id)s/config/%(group)s' %
|
||||
{
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'group': group,
|
||||
}
|
||||
)
|
||||
response = self.get(url, token=user_token)
|
||||
self.assertEqual(
|
||||
response.result['config'][group]['password_regex'],
|
||||
password_regex
|
||||
)
|
||||
self.assertEqual(
|
||||
response.result['config'][group]['password_regex_description'],
|
||||
password_regex_description
|
||||
)
|
||||
|
||||
def test_update_security_compliance_config_group_fails(self):
|
||||
"""Make sure that updates to the entire security group section fail.
|
||||
|
||||
We should only allow the ability to modify a deployments security
|
||||
compliance rules through configuration. Especially since it's only
|
||||
enforced on the default domain.
|
||||
"""
|
||||
new_config = {
|
||||
'security_compliance': {
|
||||
'password_regex': uuid.uuid4().hex,
|
||||
'password_regex_description': uuid.uuid4().hex
|
||||
}
|
||||
}
|
||||
url = (
|
||||
'/domains/%(domain_id)s/config/%(group)s' %
|
||||
{
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'group': 'security_compliance',
|
||||
}
|
||||
)
|
||||
|
||||
# Make sure regular users and administrators aren't allowed to modify
|
||||
# security compliance configuration through the API.
|
||||
self.patch(
|
||||
url,
|
||||
body={'config': new_config},
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_non_admin_token()
|
||||
)
|
||||
self.patch(
|
||||
url,
|
||||
body={'config': new_config},
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_admin_token()
|
||||
)
|
||||
|
||||
def test_update_security_compliance_password_regex_fails(self):
|
||||
"""Make sure any updates to security compliance options fail."""
|
||||
group = 'security_compliance'
|
||||
option = 'password_regex'
|
||||
url = (
|
||||
'/domains/%(domain_id)s/config/%(group)s/%(option)s' %
|
||||
{
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'group': group,
|
||||
'option': option
|
||||
}
|
||||
)
|
||||
new_config = {
|
||||
group: {
|
||||
option: uuid.uuid4().hex
|
||||
}
|
||||
}
|
||||
|
||||
# Make sure regular users and administrators aren't allowed to modify
|
||||
# security compliance configuration through the API.
|
||||
self.patch(
|
||||
url,
|
||||
body={'config': new_config},
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_non_admin_token()
|
||||
)
|
||||
self.patch(
|
||||
url,
|
||||
body={'config': new_config},
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_admin_token()
|
||||
)
|
||||
|
||||
def test_update_security_compliance_password_regex_description_fails(self):
|
||||
"""Make sure any updates to security compliance options fail."""
|
||||
group = 'security_compliance'
|
||||
option = 'password_regex_description'
|
||||
url = (
|
||||
'/domains/%(domain_id)s/config/%(group)s/%(option)s' %
|
||||
{
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'group': group,
|
||||
'option': option
|
||||
}
|
||||
)
|
||||
new_config = {
|
||||
group: {
|
||||
option: uuid.uuid4().hex
|
||||
}
|
||||
}
|
||||
|
||||
# Make sure regular users and administrators aren't allowed to modify
|
||||
# security compliance configuration through the API.
|
||||
self.patch(
|
||||
url,
|
||||
body={'config': new_config},
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_non_admin_token()
|
||||
)
|
||||
self.patch(
|
||||
url,
|
||||
body={'config': new_config},
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_admin_token()
|
||||
)
|
||||
|
||||
def test_update_non_whitelisted_security_compliance_option_fails(self):
|
||||
"""Updating security compliance options through the API is not allowed.
|
||||
|
||||
Requests to update anything in the security compliance group through
|
||||
the API should be Forbidden. This ensures that we are covering cases
|
||||
where the option being updated isn't in the white list.
|
||||
"""
|
||||
group = 'security_compliance'
|
||||
option = 'lockout_failure_attempts'
|
||||
url = (
|
||||
'/domains/%(domain_id)s/config/%(group)s/%(option)s' %
|
||||
{
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'group': group,
|
||||
'option': option
|
||||
}
|
||||
)
|
||||
new_config = {
|
||||
group: {
|
||||
option: 1
|
||||
}
|
||||
}
|
||||
|
||||
# Make sure this behavior is not possible for regular users or
|
||||
# administrators.
|
||||
self.patch(
|
||||
url,
|
||||
body={'config': new_config},
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_non_admin_token()
|
||||
)
|
||||
self.patch(
|
||||
url,
|
||||
body={'config': new_config},
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_admin_token()
|
||||
)
|
||||
|
||||
def test_delete_security_compliance_group_fails(self):
|
||||
"""The security compliance group shouldn't be deleteable."""
|
||||
url = (
|
||||
'/domains/%(domain_id)s/config/%(group)s/' %
|
||||
{
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'group': 'security_compliance',
|
||||
}
|
||||
)
|
||||
|
||||
# Make sure regular users and administrators can't delete the security
|
||||
# compliance configuration group.
|
||||
self.delete(
|
||||
url,
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_non_admin_token()
|
||||
)
|
||||
self.delete(
|
||||
url,
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_admin_token()
|
||||
)
|
||||
|
||||
def test_delete_security_compliance_password_regex_fails(self):
|
||||
"""The security compliance options shouldn't be deleteable."""
|
||||
url = (
|
||||
'/domains/%(domain_id)s/config/%(group)s/%(option)s' %
|
||||
{
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'group': 'security_compliance',
|
||||
'option': 'password_regex'
|
||||
}
|
||||
)
|
||||
|
||||
# Make sure regular users and administrators can't delete the security
|
||||
# compliance configuration group.
|
||||
self.delete(
|
||||
url,
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_non_admin_token()
|
||||
)
|
||||
self.delete(
|
||||
url,
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_admin_token()
|
||||
)
|
||||
|
||||
def test_delete_security_compliance_password_regex_description_fails(self):
|
||||
"""The security compliance options shouldn't be deleteable."""
|
||||
url = (
|
||||
'/domains/%(domain_id)s/config/%(group)s/%(option)s' %
|
||||
{
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'group': 'security_compliance',
|
||||
'option': 'password_regex_description'
|
||||
}
|
||||
)
|
||||
|
||||
# Make sure regular users and administrators can't delete the security
|
||||
# compliance configuration group.
|
||||
self.delete(
|
||||
url,
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_non_admin_token()
|
||||
)
|
||||
self.delete(
|
||||
url,
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_admin_token()
|
||||
)
|
||||
|
||||
def test_delete_non_whitelisted_security_compliance_options_fails(self):
|
||||
"""The security compliance options shouldn't be deleteable."""
|
||||
url = (
|
||||
'/domains/%(domain_id)s/config/%(group)s/%(option)s' %
|
||||
{
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'group': 'security_compliance',
|
||||
'option': 'lockout_failure_attempts'
|
||||
}
|
||||
)
|
||||
|
||||
# Make sure regular users and administrators can't delete the security
|
||||
# compliance configuration group.
|
||||
self.delete(
|
||||
url,
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_non_admin_token()
|
||||
)
|
||||
self.delete(
|
||||
url,
|
||||
expected_status=http_client.FORBIDDEN,
|
||||
token=self._get_admin_token()
|
||||
)
|
||||
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- User interfaces and clients now have the ability
|
||||
to retrieve password requirement information via the
|
||||
Domain Config API. Specifically, the ``password_regex``
|
||||
and ``password_regex_description`` options of the
|
||||
``[security_compliance]`` section.
|
Loading…
Reference in New Issue
Block a user