Merge "Add system scope for admin auth"
This commit is contained in:
commit
cd147631f8
tempest
@ -245,6 +245,9 @@ def get_configured_admin_credentials(fill_in=True, identity_version=None):
|
||||
|
||||
if identity_version == 'v3':
|
||||
conf_attributes.append('domain_name')
|
||||
conf_attributes.append('user_domain_name')
|
||||
conf_attributes.append('project_domain_name')
|
||||
conf_attributes.append('system')
|
||||
# Read the parts of credentials from config
|
||||
params = config.service_client_config()
|
||||
for attr in conf_attributes:
|
||||
@ -284,7 +287,8 @@ def get_credentials(fill_in=True, identity_version=None, **kwargs):
|
||||
if identity_version == 'v3':
|
||||
domain_fields = set(x for x in auth.KeystoneV3Credentials.ATTRIBUTES
|
||||
if 'domain' in x)
|
||||
if not domain_fields.intersection(kwargs.keys()):
|
||||
if (not params.get('system') and
|
||||
not domain_fields.intersection(kwargs.keys())):
|
||||
domain_name = CONF.auth.default_credentials_domain_name
|
||||
# NOTE(andreaf) Setting domain_name implicitly sets user and
|
||||
# project domain names, if they are None
|
||||
|
@ -92,7 +92,24 @@ AuthGroup = [
|
||||
cfg.StrOpt('admin_domain_name',
|
||||
default='Default',
|
||||
help="Admin domain name for authentication (Keystone V3). "
|
||||
"The same domain applies to user and project"),
|
||||
"The same domain applies to user and project if "
|
||||
"admin_user_domain_name and admin_project_domain_name "
|
||||
"are not specified"),
|
||||
cfg.StrOpt('admin_user_domain_name',
|
||||
help="Domain name that contains the admin user (Keystone V3). "
|
||||
"May be different from admin_project_domain_name and "
|
||||
"admin_domain_name"),
|
||||
cfg.StrOpt('admin_project_domain_name',
|
||||
help="Domain name that contains the project given by "
|
||||
"admin_project_name (Keystone V3). May be different from "
|
||||
"admin_user_domain_name and admin_domain_name"),
|
||||
cfg.StrOpt('admin_system',
|
||||
default=None,
|
||||
help="The system scope on which an admin user has an admin "
|
||||
"role assignment, if any. Valid values are 'all' or None. "
|
||||
"This must be set to 'all' if using the "
|
||||
"[oslo_policy]/enforce_scope=true option for the "
|
||||
"identity service."),
|
||||
]
|
||||
|
||||
identity_group = cfg.OptGroup(name='identity',
|
||||
|
@ -428,7 +428,7 @@ class KeystoneV2AuthProvider(KeystoneAuthProvider):
|
||||
class KeystoneV3AuthProvider(KeystoneAuthProvider):
|
||||
"""Provides authentication based on the Identity V3 API"""
|
||||
|
||||
SCOPES = set(['project', 'domain', 'unscoped', None])
|
||||
SCOPES = set(['system', 'project', 'domain', 'unscoped', None])
|
||||
|
||||
def _auth_client(self, auth_url):
|
||||
return json_v3id.V3TokenClient(
|
||||
@ -441,8 +441,8 @@ class KeystoneV3AuthProvider(KeystoneAuthProvider):
|
||||
|
||||
Fields available in Credentials are passed to the token request,
|
||||
depending on the value of scope. Valid values for scope are: "project",
|
||||
"domain". Any other string (e.g. "unscoped") or None will lead to an
|
||||
unscoped token request.
|
||||
"domain", or "system". Any other string (e.g. "unscoped") or None will
|
||||
lead to an unscoped token request.
|
||||
"""
|
||||
|
||||
auth_params = dict(
|
||||
@ -465,12 +465,16 @@ class KeystoneV3AuthProvider(KeystoneAuthProvider):
|
||||
domain_id=self.credentials.domain_id,
|
||||
domain_name=self.credentials.domain_name)
|
||||
|
||||
if self.scope == 'system':
|
||||
auth_params.update(system='all')
|
||||
|
||||
return auth_params
|
||||
|
||||
def _fill_credentials(self, auth_data_body):
|
||||
# project or domain, depending on the scope
|
||||
# project, domain, or system depending on the scope
|
||||
project = auth_data_body.get('project', None)
|
||||
domain = auth_data_body.get('domain', None)
|
||||
system = auth_data_body.get('system', None)
|
||||
# user is always there
|
||||
user = auth_data_body['user']
|
||||
# Set project fields
|
||||
@ -490,6 +494,9 @@ class KeystoneV3AuthProvider(KeystoneAuthProvider):
|
||||
self.credentials.domain_id = domain['id']
|
||||
if self.credentials.domain_name is None:
|
||||
self.credentials.domain_name = domain['name']
|
||||
# Set system scope
|
||||
if system is not None:
|
||||
self.credentials.system = 'all'
|
||||
# Set user fields
|
||||
if self.credentials.username is None:
|
||||
self.credentials.username = user['name']
|
||||
@ -677,7 +684,8 @@ class Credentials(object):
|
||||
raise exceptions.InvalidCredentials(msg)
|
||||
for key in attr:
|
||||
if key in self.ATTRIBUTES:
|
||||
setattr(self, key, attr[key])
|
||||
if attr[key] is not None:
|
||||
setattr(self, key, attr[key])
|
||||
else:
|
||||
msg = '%s is not a valid attr for %s' % (key, self.__class__)
|
||||
raise exceptions.InvalidCredentials(msg)
|
||||
@ -779,7 +787,7 @@ class KeystoneV3Credentials(Credentials):
|
||||
ATTRIBUTES = ['domain_id', 'domain_name', 'password', 'username',
|
||||
'project_domain_id', 'project_domain_name', 'project_id',
|
||||
'project_name', 'tenant_id', 'tenant_name', 'user_domain_id',
|
||||
'user_domain_name', 'user_id']
|
||||
'user_domain_name', 'user_id', 'system']
|
||||
COLLISIONS = [('project_name', 'tenant_name'), ('project_id', 'tenant_id')]
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
|
@ -83,12 +83,15 @@ class CredsClient(object):
|
||||
role['id'], project['id'], user['id'])
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_credentials(self, user, project, password):
|
||||
def get_credentials(
|
||||
self, user, project, password, domain=None, system=None):
|
||||
"""Produces a Credentials object from the details provided
|
||||
|
||||
:param user: a user dict
|
||||
:param project: a project dict
|
||||
:param project: a project dict or None if using domain or system scope
|
||||
:param password: the password as a string
|
||||
:param domain: a domain dict
|
||||
:param system: a system dict
|
||||
:return: a Credentials object with all the available credential details
|
||||
"""
|
||||
pass
|
||||
@ -116,7 +119,8 @@ class V2CredsClient(CredsClient):
|
||||
def delete_project(self, project_id):
|
||||
self.projects_client.delete_tenant(project_id)
|
||||
|
||||
def get_credentials(self, user, project, password):
|
||||
def get_credentials(
|
||||
self, user, project, password, domain=None, system=None):
|
||||
# User and project already include both ID and name here,
|
||||
# so there's no need to use the fill_in mode
|
||||
return auth.get_credentials(
|
||||
@ -156,23 +160,37 @@ class V3CredsClient(CredsClient):
|
||||
def delete_project(self, project_id):
|
||||
self.projects_client.delete_project(project_id)
|
||||
|
||||
def get_credentials(self, user, project, password):
|
||||
def get_credentials(
|
||||
self, user, project, password, domain=None, system=None):
|
||||
# User, project and domain already include both ID and name here,
|
||||
# so there's no need to use the fill_in mode.
|
||||
# NOTE(andreaf) We need to set all fields in the returned credentials.
|
||||
# Scope is then used to pick only those relevant for the type of
|
||||
# token needed by each service client.
|
||||
if project:
|
||||
project_name = project['name']
|
||||
project_id = project['id']
|
||||
else:
|
||||
project_name = None
|
||||
project_id = None
|
||||
if domain:
|
||||
domain_name = domain['name']
|
||||
domain_id = domain['id']
|
||||
else:
|
||||
domain_name = self.creds_domain['name']
|
||||
domain_id = self.creds_domain['id']
|
||||
return auth.get_credentials(
|
||||
auth_url=None,
|
||||
fill_in=False,
|
||||
identity_version='v3',
|
||||
username=user['name'], user_id=user['id'],
|
||||
project_name=project['name'], project_id=project['id'],
|
||||
project_name=project_name, project_id=project_id,
|
||||
password=password,
|
||||
project_domain_id=self.creds_domain['id'],
|
||||
project_domain_name=self.creds_domain['name'],
|
||||
domain_id=self.creds_domain['id'],
|
||||
domain_name=self.creds_domain['name'])
|
||||
domain_id=domain_id,
|
||||
domain_name=domain_name,
|
||||
system=system)
|
||||
|
||||
def assign_user_role_on_domain(self, user, role_name, domain=None):
|
||||
"""Assign the specified role on a domain
|
||||
|
@ -142,7 +142,14 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider):
|
||||
else:
|
||||
# We use a dedicated client manager for identity client in case we
|
||||
# need a different token scope for them.
|
||||
scope = 'domain' if self.identity_admin_domain_scope else 'project'
|
||||
if self.default_admin_creds.system:
|
||||
scope = 'system'
|
||||
elif (self.default_admin_creds.domain_id or
|
||||
self.default_admin_creds.domain_name or
|
||||
self.identity_admin_domain_scope):
|
||||
scope = 'domain'
|
||||
else:
|
||||
scope = 'project'
|
||||
identity_os = clients.ServiceClients(self.default_admin_creds,
|
||||
self.identity_uri,
|
||||
scope=scope)
|
||||
|
@ -51,7 +51,7 @@ class V3TokenClient(rest_client.RestClient):
|
||||
def auth(self, user_id=None, username=None, password=None, project_id=None,
|
||||
project_name=None, user_domain_id=None, user_domain_name=None,
|
||||
project_domain_id=None, project_domain_name=None, domain_id=None,
|
||||
domain_name=None, token=None, app_cred_id=None,
|
||||
domain_name=None, system=None, token=None, app_cred_id=None,
|
||||
app_cred_secret=None):
|
||||
"""Obtains a token from the authentication service
|
||||
|
||||
@ -65,6 +65,7 @@ class V3TokenClient(rest_client.RestClient):
|
||||
:param domain_name: a domain name to scope to
|
||||
:param project_id: a project id to scope to
|
||||
:param project_name: a project name to scope to
|
||||
:param system: whether the token should be scoped to the system
|
||||
:param token: a token to re-scope.
|
||||
|
||||
Accepts different combinations of credentials.
|
||||
@ -74,6 +75,7 @@ class V3TokenClient(rest_client.RestClient):
|
||||
- user_id, password
|
||||
- username, password, user_domain_id
|
||||
- username, password, project_name, user_domain_id, project_domain_id
|
||||
- username, password, user_domain_id, system
|
||||
Validation is left to the server side.
|
||||
"""
|
||||
creds = {
|
||||
@ -135,6 +137,8 @@ class V3TokenClient(rest_client.RestClient):
|
||||
creds['auth']['scope'] = dict(domain={'id': domain_id})
|
||||
elif domain_name:
|
||||
creds['auth']['scope'] = dict(domain={'name': domain_name})
|
||||
elif system:
|
||||
creds['auth']['scope'] = dict(system={system: True})
|
||||
|
||||
body = json.dumps(creds, sort_keys=True)
|
||||
resp, body = self.post(self.auth_url, body=body)
|
||||
|
@ -173,10 +173,15 @@ class TestCredentialsFactory(base.TestCase):
|
||||
@mock.patch.object(cf, 'get_credentials')
|
||||
def test_get_configured_admin_credentials(self, mock_get_credentials):
|
||||
cfg.CONF.set_default('auth_version', 'v3', 'identity')
|
||||
all_params = [('admin_username', 'username', 'my_name'),
|
||||
('admin_password', 'password', 'secret'),
|
||||
('admin_project_name', 'project_name', 'my_pname'),
|
||||
('admin_domain_name', 'domain_name', 'my_dname')]
|
||||
all_params = [
|
||||
('admin_username', 'username', 'my_name'),
|
||||
('admin_user_domain_name', 'user_domain_name', 'my_dname'),
|
||||
('admin_password', 'password', 'secret'),
|
||||
('admin_project_name', 'project_name', 'my_pname'),
|
||||
('admin_project_domain_name', 'project_domain_name', 'my_dname'),
|
||||
('admin_domain_name', 'domain_name', 'my_dname'),
|
||||
('admin_system', 'system', None),
|
||||
]
|
||||
expected_result = 'my_admin_credentials'
|
||||
mock_get_credentials.return_value = expected_result
|
||||
for config_item, _, value in all_params:
|
||||
@ -194,10 +199,15 @@ class TestCredentialsFactory(base.TestCase):
|
||||
def test_get_configured_admin_credentials_not_fill_valid(
|
||||
self, mock_get_credentials):
|
||||
cfg.CONF.set_default('auth_version', 'v2', 'identity')
|
||||
all_params = [('admin_username', 'username', 'my_name'),
|
||||
('admin_password', 'password', 'secret'),
|
||||
('admin_project_name', 'project_name', 'my_pname'),
|
||||
('admin_domain_name', 'domain_name', 'my_dname')]
|
||||
all_params = [
|
||||
('admin_username', 'username', 'my_name'),
|
||||
('admin_user_domain_name', 'user_domain_name', 'my_dname'),
|
||||
('admin_password', 'password', 'secret'),
|
||||
('admin_project_domain_name', 'project_domain_name', 'my_dname'),
|
||||
('admin_project_name', 'project_name', 'my_pname'),
|
||||
('admin_domain_name', 'domain_name', 'my_dname'),
|
||||
('admin_system', 'system', None),
|
||||
]
|
||||
expected_result = mock.Mock()
|
||||
expected_result.is_valid.return_value = True
|
||||
mock_get_credentials.return_value = expected_result
|
||||
@ -278,3 +288,20 @@ class TestCredentialsFactory(base.TestCase):
|
||||
mock_auth_get_credentials.assert_called_once_with(
|
||||
expected_uri, fill_in=False, identity_version='v3',
|
||||
**expected_params)
|
||||
|
||||
@mock.patch('tempest.lib.auth.get_credentials')
|
||||
def test_get_credentials_v3_system(self, mock_auth_get_credentials):
|
||||
expected_uri = 'V3_URI'
|
||||
expected_result = 'my_creds'
|
||||
mock_auth_get_credentials.return_value = expected_result
|
||||
cfg.CONF.set_default('uri_v3', expected_uri, 'identity')
|
||||
cfg.CONF.set_default('admin_system', 'all', 'auth')
|
||||
params = {'system': 'all'}
|
||||
expected_params = params.copy()
|
||||
expected_params.update(config.service_client_config())
|
||||
result = cf.get_credentials(fill_in=False, identity_version='v3',
|
||||
**params)
|
||||
self.assertEqual(expected_result, result)
|
||||
mock_auth_get_credentials.assert_called_once_with(
|
||||
expected_uri, fill_in=False, identity_version='v3',
|
||||
**expected_params)
|
||||
|
@ -43,6 +43,14 @@ class TestCredClientV2(base.TestCase):
|
||||
self.projects_client.delete_tenant.assert_called_once_with(
|
||||
'fake_id')
|
||||
|
||||
def test_get_credentials(self):
|
||||
ret = self.creds_client.get_credentials(
|
||||
{'name': 'some_user', 'id': 'fake_id'},
|
||||
{'name': 'some_project', 'id': 'fake_id'},
|
||||
'password123')
|
||||
self.assertEqual(ret.username, 'some_user')
|
||||
self.assertEqual(ret.project_name, 'some_project')
|
||||
|
||||
|
||||
class TestCredClientV3(base.TestCase):
|
||||
def setUp(self):
|
||||
@ -53,7 +61,7 @@ class TestCredClientV3(base.TestCase):
|
||||
self.roles_client = mock.MagicMock()
|
||||
self.domains_client = mock.MagicMock()
|
||||
self.domains_client.list_domains.return_value = {
|
||||
'domains': [{'id': 'fake_domain_id'}]
|
||||
'domains': [{'id': 'fake_domain_id', 'name': 'some_domain'}]
|
||||
}
|
||||
self.creds_client = cred_client.V3CredsClient(self.identity_client,
|
||||
self.projects_client,
|
||||
@ -75,3 +83,31 @@ class TestCredClientV3(base.TestCase):
|
||||
self.creds_client.delete_project('fake_id')
|
||||
self.projects_client.delete_project.assert_called_once_with(
|
||||
'fake_id')
|
||||
|
||||
def test_get_credentials(self):
|
||||
ret = self.creds_client.get_credentials(
|
||||
{'name': 'some_user', 'id': 'fake_id'},
|
||||
{'name': 'some_project', 'id': 'fake_id'},
|
||||
'password123')
|
||||
self.assertEqual(ret.username, 'some_user')
|
||||
self.assertEqual(ret.project_name, 'some_project')
|
||||
self.assertIsNone(ret.system)
|
||||
self.assertEqual(ret.domain_name, 'some_domain')
|
||||
ret = self.creds_client.get_credentials(
|
||||
{'name': 'some_user', 'id': 'fake_id'},
|
||||
None,
|
||||
'password123',
|
||||
domain={'name': 'another_domain', 'id': 'another_id'})
|
||||
self.assertEqual(ret.username, 'some_user')
|
||||
self.assertIsNone(ret.project_name)
|
||||
self.assertIsNone(ret.system)
|
||||
self.assertEqual(ret.domain_name, 'another_domain')
|
||||
ret = self.creds_client.get_credentials(
|
||||
{'name': 'some_user', 'id': 'fake_id'},
|
||||
None,
|
||||
'password123',
|
||||
system={'system': 'all'})
|
||||
self.assertEqual(ret.username, 'some_user')
|
||||
self.assertIsNone(ret.project_name)
|
||||
self.assertEqual(ret.system, {'system': 'all'})
|
||||
self.assertEqual(ret.domain_name, 'some_domain')
|
||||
|
@ -786,6 +786,19 @@ class TestKeystoneV3AuthProvider(TestKeystoneV2AuthProvider):
|
||||
self.assertIn(attr, auth_params.keys())
|
||||
self.assertEqual(getattr(all_creds, attr), auth_params[attr])
|
||||
|
||||
def test_auth_parameters_with_system_scope(self):
|
||||
all_creds = fake_credentials.FakeKeystoneV3AllCredentials()
|
||||
self.auth_provider.credentials = all_creds
|
||||
self.auth_provider.scope = 'system'
|
||||
auth_params = self.auth_provider._auth_params()
|
||||
self.assertNotIn('scope', auth_params.keys())
|
||||
for attr in all_creds.get_init_attributes():
|
||||
if attr.startswith('project_') or attr.startswith('domain_'):
|
||||
self.assertNotIn(attr, auth_params.keys())
|
||||
else:
|
||||
self.assertIn(attr, auth_params.keys())
|
||||
self.assertEqual(getattr(all_creds, attr), auth_params[attr])
|
||||
|
||||
|
||||
class TestKeystoneV3Credentials(base.TestCase):
|
||||
def testSetAttrUserDomain(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user