Merge "Implement domain specific Identity backends"
This commit is contained in:
commit
81534a182a
@ -96,6 +96,25 @@ order:
|
||||
PasteDeploy configuration file is specified by the ``config_file`` parameter in ``[paste_deploy]`` section of the primary configuration file. If the parameter
|
||||
is not an absolute path, then Keystone looks for it in the same directories as above. If not specified, WSGI pipeline definitions are loaded from the primary configuration file.
|
||||
|
||||
Keystone supports the option (disabled by default) to specify identity driver
|
||||
configurations on a domain by domain basis, allowing, for example, a specific
|
||||
domain to have its own LDAP or SQL server. This is configured by specifying the
|
||||
following options::
|
||||
|
||||
[identity]
|
||||
domain_specific_drivers_enabled = True
|
||||
domain_config_dir = /etc/keystone/domains
|
||||
|
||||
Setting ``domain_specific_drivers_enabled`` to True will enable this feature, causing
|
||||
keystone to look in the ``domain_config_dir`` for config files of the form::
|
||||
|
||||
keystone.<domain_name>.conf
|
||||
|
||||
Options given in the domain specific configuration file will override those in the
|
||||
primary configuration file for the specified domain only. Domains without a specific
|
||||
configuration file will continue to use the options from the primary configuration
|
||||
file.
|
||||
|
||||
Authentication Plugins
|
||||
----------------------
|
||||
|
||||
|
@ -99,6 +99,14 @@
|
||||
# There is nothing special about this domain, other than the fact that it must
|
||||
# exist to order to maintain support for your v2 clients.
|
||||
# default_domain_id = default
|
||||
#
|
||||
# A subset (or all) of domains can have their own identity driver, each with
|
||||
# their own partial configuration file in a domain configuration directory.
|
||||
# Only values specific to the domain need to be placed in the domain specific
|
||||
# configuration file. This feature is disabled by default; set
|
||||
# domain_specific_drivers_enabled to True to enable.
|
||||
# domain_specific_drivers_enabled = False
|
||||
# domain_config_dir = /etc/keystone/domains
|
||||
|
||||
# Maximum supported length for user passwords; decrease to improve performance.
|
||||
# max_password_length = 4096
|
||||
|
@ -94,6 +94,7 @@ class UserAuthInfo(object):
|
||||
self._assert_user_is_enabled(user_ref)
|
||||
self.user_ref = user_ref
|
||||
self.user_id = user_ref['id']
|
||||
self.domain_id = domain_ref['id']
|
||||
|
||||
|
||||
class Password(auth.AuthMethodHandler):
|
||||
@ -106,7 +107,8 @@ class Password(auth.AuthMethodHandler):
|
||||
try:
|
||||
self.identity_api.authenticate(
|
||||
user_id=user_info.user_id,
|
||||
password=user_info.password)
|
||||
password=user_info.password,
|
||||
domain_scope=user_info.domain_id)
|
||||
except AssertionError:
|
||||
# authentication failed because of invalid username or password
|
||||
msg = _('Invalid username or password')
|
||||
|
@ -25,9 +25,6 @@ from keystone.openstack.common import log as logging
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = config.CONF
|
||||
config.register_str('template_file',
|
||||
default='default_catalog.templates',
|
||||
group='catalog')
|
||||
|
||||
|
||||
def parse_templates(template_lines):
|
||||
|
@ -24,6 +24,218 @@ _DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||
_DEFAULT_AUTH_METHODS = ['external', 'password', 'token']
|
||||
|
||||
|
||||
FILE_OPTIONS = {
|
||||
'': [
|
||||
cfg.StrOpt('admin_token', secret=True, default='ADMIN'),
|
||||
cfg.StrOpt('bind_host', default='0.0.0.0'),
|
||||
cfg.IntOpt('compute_port', default=8774),
|
||||
cfg.IntOpt('admin_port', default=35357),
|
||||
cfg.IntOpt('public_port', default=5000),
|
||||
cfg.StrOpt('public_endpoint',
|
||||
default='http://localhost:%(public_port)s/'),
|
||||
cfg.StrOpt('admin_endpoint',
|
||||
default='http://localhost:%(admin_port)s/'),
|
||||
cfg.StrOpt('onready'),
|
||||
cfg.StrOpt('auth_admin_prefix', default=''),
|
||||
cfg.StrOpt('policy_file', default='policy.json'),
|
||||
cfg.StrOpt('policy_default_rule', default=None),
|
||||
# default max request size is 112k
|
||||
cfg.IntOpt('max_request_body_size', default=114688),
|
||||
cfg.IntOpt('max_param_size', default=64),
|
||||
# we allow tokens to be a bit larger to accommodate PKI
|
||||
cfg.IntOpt('max_token_size', default=8192),
|
||||
cfg.StrOpt('member_role_id',
|
||||
default='9fe2ff9ee4384b1894a90878d3e92bab'),
|
||||
cfg.StrOpt('member_role_name', default='_member_'),
|
||||
cfg.IntOpt('crypt_strength', default=40000)],
|
||||
'identity': [
|
||||
cfg.StrOpt('default_domain_id', default='default'),
|
||||
cfg.BoolOpt('domain_specific_drivers_enabled',
|
||||
default=False),
|
||||
cfg.StrOpt('domain_config_dir',
|
||||
default='/etc/keystone/domains'),
|
||||
cfg.StrOpt('driver',
|
||||
default=('keystone.identity.backends'
|
||||
'.sql.Identity')),
|
||||
cfg.IntOpt('max_password_length', default=4096)],
|
||||
'trust': [
|
||||
cfg.BoolOpt('enabled', default=True),
|
||||
cfg.StrOpt('driver',
|
||||
default='keystone.trust.backends.sql.Trust')],
|
||||
'os_inherit': [
|
||||
cfg.BoolOpt('enabled', default=False)],
|
||||
'token': [
|
||||
cfg.ListOpt('bind', default=[]),
|
||||
cfg.StrOpt('enforce_token_bind', default='permissive'),
|
||||
cfg.IntOpt('expiration', default=86400),
|
||||
cfg.StrOpt('provider', default=None),
|
||||
cfg.StrOpt('driver',
|
||||
default='keystone.token.backends.sql.Token')],
|
||||
'ssl': [
|
||||
cfg.BoolOpt('enable', default=False),
|
||||
cfg.StrOpt('certfile',
|
||||
default="/etc/keystone/ssl/certs/keystone.pem"),
|
||||
cfg.StrOpt('keyfile',
|
||||
default="/etc/keystone/ssl/private/keystonekey.pem"),
|
||||
cfg.StrOpt('ca_certs',
|
||||
default="/etc/keystone/ssl/certs/ca.pem"),
|
||||
cfg.StrOpt('ca_key',
|
||||
default="/etc/keystone/ssl/certs/cakey.pem"),
|
||||
cfg.BoolOpt('cert_required', default=False),
|
||||
cfg.IntOpt('key_size', default=1024),
|
||||
cfg.IntOpt('valid_days', default=3650),
|
||||
cfg.StrOpt('ca_password', default=None),
|
||||
cfg.StrOpt('cert_subject',
|
||||
default='/C=US/ST=Unset/L=Unset/O=Unset/CN=localhost')],
|
||||
'signing': [
|
||||
cfg.StrOpt('token_format', default=None),
|
||||
cfg.StrOpt('certfile',
|
||||
default="/etc/keystone/ssl/certs/signing_cert.pem"),
|
||||
cfg.StrOpt('keyfile',
|
||||
default="/etc/keystone/ssl/private/signing_key.pem"),
|
||||
cfg.StrOpt('ca_certs',
|
||||
default="/etc/keystone/ssl/certs/ca.pem"),
|
||||
cfg.StrOpt('ca_key',
|
||||
default="/etc/keystone/ssl/certs/cakey.pem"),
|
||||
cfg.IntOpt('key_size', default=2048),
|
||||
cfg.IntOpt('valid_days', default=3650),
|
||||
cfg.StrOpt('ca_password', default=None),
|
||||
cfg.StrOpt('cert_subject',
|
||||
default=('/C=US/ST=Unset/L=Unset/O=Unset/'
|
||||
'CN=www.example.com'))],
|
||||
'sql': [
|
||||
cfg.StrOpt('connection', secret=True,
|
||||
default='sqlite:///keystone.db'),
|
||||
cfg.IntOpt('idle_timeout', default=200)],
|
||||
'assignment': [
|
||||
# assignment has no default for backward compatibility reasons.
|
||||
# If assignment driver is not specified, the identity driver chooses
|
||||
# the backend
|
||||
cfg.StrOpt('driver', default=None)],
|
||||
'credential': [
|
||||
cfg.StrOpt('driver',
|
||||
default=('keystone.credential.backends'
|
||||
'.sql.Credential'))],
|
||||
'policy': [
|
||||
cfg.StrOpt('driver',
|
||||
default='keystone.policy.backends.sql.Policy')],
|
||||
'ec2': [
|
||||
cfg.StrOpt('driver',
|
||||
default='keystone.contrib.ec2.backends.kvs.Ec2')],
|
||||
'stats': [
|
||||
cfg.StrOpt('driver',
|
||||
default=('keystone.contrib.stats.backends'
|
||||
'.kvs.Stats'))],
|
||||
'ldap': [
|
||||
cfg.StrOpt('url', default='ldap://localhost'),
|
||||
cfg.StrOpt('user', default=None),
|
||||
cfg.StrOpt('password', secret=True, default=None),
|
||||
cfg.StrOpt('suffix', default='cn=example,cn=com'),
|
||||
cfg.BoolOpt('use_dumb_member', default=False),
|
||||
cfg.StrOpt('dumb_member', default='cn=dumb,dc=nonexistent'),
|
||||
cfg.BoolOpt('allow_subtree_delete', default=False),
|
||||
cfg.StrOpt('query_scope', default='one'),
|
||||
cfg.IntOpt('page_size', default=0),
|
||||
cfg.StrOpt('alias_dereferencing', default='default'),
|
||||
|
||||
cfg.StrOpt('user_tree_dn', default=None),
|
||||
cfg.StrOpt('user_filter', default=None),
|
||||
cfg.StrOpt('user_objectclass', default='inetOrgPerson'),
|
||||
cfg.StrOpt('user_id_attribute', default='cn'),
|
||||
cfg.StrOpt('user_name_attribute', default='sn'),
|
||||
cfg.StrOpt('user_mail_attribute', default='email'),
|
||||
cfg.StrOpt('user_pass_attribute', default='userPassword'),
|
||||
cfg.StrOpt('user_enabled_attribute', default='enabled'),
|
||||
cfg.StrOpt('user_domain_id_attribute',
|
||||
default='businessCategory'),
|
||||
cfg.IntOpt('user_enabled_mask', default=0),
|
||||
cfg.StrOpt('user_enabled_default', default='True'),
|
||||
cfg.ListOpt('user_attribute_ignore',
|
||||
default='tenant_id,tenants'),
|
||||
cfg.BoolOpt('user_allow_create', default=True),
|
||||
cfg.BoolOpt('user_allow_update', default=True),
|
||||
cfg.BoolOpt('user_allow_delete', default=True),
|
||||
cfg.BoolOpt('user_enabled_emulation', default=False),
|
||||
cfg.StrOpt('user_enabled_emulation_dn', default=None),
|
||||
cfg.ListOpt('user_additional_attribute_mapping',
|
||||
default=None),
|
||||
|
||||
cfg.StrOpt('tenant_tree_dn', default=None),
|
||||
cfg.StrOpt('tenant_filter', default=None),
|
||||
cfg.StrOpt('tenant_objectclass', default='groupOfNames'),
|
||||
cfg.StrOpt('tenant_id_attribute', default='cn'),
|
||||
cfg.StrOpt('tenant_member_attribute', default='member'),
|
||||
cfg.StrOpt('tenant_name_attribute', default='ou'),
|
||||
cfg.StrOpt('tenant_desc_attribute', default='description'),
|
||||
cfg.StrOpt('tenant_enabled_attribute', default='enabled'),
|
||||
cfg.StrOpt('tenant_domain_id_attribute',
|
||||
default='businessCategory'),
|
||||
cfg.ListOpt('tenant_attribute_ignore', default=''),
|
||||
cfg.BoolOpt('tenant_allow_create', default=True),
|
||||
cfg.BoolOpt('tenant_allow_update', default=True),
|
||||
cfg.BoolOpt('tenant_allow_delete', default=True),
|
||||
cfg.BoolOpt('tenant_enabled_emulation', default=False),
|
||||
cfg.StrOpt('tenant_enabled_emulation_dn', default=None),
|
||||
cfg.ListOpt('tenant_additional_attribute_mapping',
|
||||
default=None),
|
||||
|
||||
cfg.StrOpt('role_tree_dn', default=None),
|
||||
cfg.StrOpt('role_filter', default=None),
|
||||
cfg.StrOpt('role_objectclass', default='organizationalRole'),
|
||||
cfg.StrOpt('role_id_attribute', default='cn'),
|
||||
cfg.StrOpt('role_name_attribute', default='ou'),
|
||||
cfg.StrOpt('role_member_attribute', default='roleOccupant'),
|
||||
cfg.ListOpt('role_attribute_ignore', default=''),
|
||||
cfg.BoolOpt('role_allow_create', default=True),
|
||||
cfg.BoolOpt('role_allow_update', default=True),
|
||||
cfg.BoolOpt('role_allow_delete', default=True),
|
||||
cfg.ListOpt('role_additional_attribute_mapping',
|
||||
default=None),
|
||||
|
||||
cfg.StrOpt('group_tree_dn', default=None),
|
||||
cfg.StrOpt('group_filter', default=None),
|
||||
cfg.StrOpt('group_objectclass', default='groupOfNames'),
|
||||
cfg.StrOpt('group_id_attribute', default='cn'),
|
||||
cfg.StrOpt('group_name_attribute', default='ou'),
|
||||
cfg.StrOpt('group_member_attribute', default='member'),
|
||||
cfg.StrOpt('group_desc_attribute', default='description'),
|
||||
cfg.StrOpt('group_domain_id_attribute',
|
||||
default='businessCategory'),
|
||||
cfg.ListOpt('group_attribute_ignore', default=''),
|
||||
cfg.BoolOpt('group_allow_create', default=True),
|
||||
cfg.BoolOpt('group_allow_update', default=True),
|
||||
cfg.BoolOpt('group_allow_delete', default=True),
|
||||
cfg.ListOpt('group_additional_attribute_mapping',
|
||||
default=None),
|
||||
|
||||
cfg.StrOpt('tls_cacertfile', default=None),
|
||||
cfg.StrOpt('tls_cacertdir', default=None),
|
||||
cfg.BoolOpt('use_tls', default=False),
|
||||
cfg.StrOpt('tls_req_cert', default='demand')],
|
||||
'pam': [
|
||||
cfg.StrOpt('userid', default=None),
|
||||
cfg.StrOpt('password', default=None)],
|
||||
'auth': [
|
||||
cfg.ListOpt('methods', default=_DEFAULT_AUTH_METHODS),
|
||||
cfg.StrOpt('password',
|
||||
default='keystone.auth.plugins.token.Token'),
|
||||
cfg.StrOpt('token',
|
||||
default='keystone.auth.plugins.password.Password'),
|
||||
#deals with REMOTE_USER authentication
|
||||
cfg.StrOpt('external',
|
||||
default='keystone.auth.plugins.external.ExternalDefault')],
|
||||
'paste_deploy': [
|
||||
cfg.StrOpt('config_file', default=None)],
|
||||
'memcache': [
|
||||
cfg.StrOpt('servers', default='localhost:11211'),
|
||||
cfg.IntOpt('max_compare_and_set_retry', default=16)],
|
||||
'catalog': [
|
||||
cfg.StrOpt('template_file',
|
||||
default='default_catalog.templates'),
|
||||
cfg.StrOpt('driver',
|
||||
default='keystone.catalog.backends.sql.Catalog')]}
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
@ -40,297 +252,35 @@ def setup_logging(conf, product_name='keystone'):
|
||||
logging.setup(product_name)
|
||||
|
||||
|
||||
def setup_authentication():
|
||||
def setup_authentication(conf=None):
|
||||
# register any non-default auth methods here (used by extensions, etc)
|
||||
for method_name in CONF.auth.methods:
|
||||
if conf is None:
|
||||
conf = CONF
|
||||
for method_name in conf.auth.methods:
|
||||
if method_name not in _DEFAULT_AUTH_METHODS:
|
||||
register_str(method_name, group="auth")
|
||||
conf.register_opt(cfg.StrOpt(method_name), group='auth')
|
||||
|
||||
|
||||
def register_str(*args, **kw):
|
||||
conf = kw.pop('conf', CONF)
|
||||
group = kw.pop('group', None)
|
||||
return conf.register_opt(cfg.StrOpt(*args, **kw), group=group)
|
||||
def configure(conf=None):
|
||||
if conf is None:
|
||||
conf = CONF
|
||||
|
||||
conf.register_cli_opt(
|
||||
cfg.BoolOpt('standard-threads', default=False,
|
||||
help='Do not monkey-patch threading system modules.'))
|
||||
conf.register_cli_opt(
|
||||
cfg.StrOpt('pydev-debug-host', default=None,
|
||||
help='Host to connect to for remote debugger.'))
|
||||
conf.register_cli_opt(
|
||||
cfg.IntOpt('pydev-debug-port', default=None,
|
||||
help='Port to connect to for remote debugger.'))
|
||||
|
||||
def register_cli_str(*args, **kw):
|
||||
conf = kw.pop('conf', CONF)
|
||||
group = kw.pop('group', None)
|
||||
return conf.register_cli_opt(cfg.StrOpt(*args, **kw), group=group)
|
||||
for section in FILE_OPTIONS:
|
||||
for option in FILE_OPTIONS[section]:
|
||||
if section:
|
||||
conf.register_opt(option, group=section)
|
||||
else:
|
||||
conf.register_opt(option)
|
||||
|
||||
|
||||
def register_list(*args, **kw):
|
||||
conf = kw.pop('conf', CONF)
|
||||
group = kw.pop('group', None)
|
||||
return conf.register_opt(cfg.ListOpt(*args, **kw), group=group)
|
||||
|
||||
|
||||
def register_cli_list(*args, **kw):
|
||||
conf = kw.pop('conf', CONF)
|
||||
group = kw.pop('group', None)
|
||||
return conf.register_cli_opt(cfg.ListOpt(*args, **kw), group=group)
|
||||
|
||||
|
||||
def register_bool(*args, **kw):
|
||||
conf = kw.pop('conf', CONF)
|
||||
group = kw.pop('group', None)
|
||||
return conf.register_opt(cfg.BoolOpt(*args, **kw), group=group)
|
||||
|
||||
|
||||
def register_cli_bool(*args, **kw):
|
||||
conf = kw.pop('conf', CONF)
|
||||
group = kw.pop('group', None)
|
||||
return conf.register_cli_opt(cfg.BoolOpt(*args, **kw), group=group)
|
||||
|
||||
|
||||
def register_int(*args, **kw):
|
||||
conf = kw.pop('conf', CONF)
|
||||
group = kw.pop('group', None)
|
||||
return conf.register_opt(cfg.IntOpt(*args, **kw), group=group)
|
||||
|
||||
|
||||
def register_cli_int(*args, **kw):
|
||||
conf = kw.pop('conf', CONF)
|
||||
group = kw.pop('group', None)
|
||||
return conf.register_cli_opt(cfg.IntOpt(*args, **kw), group=group)
|
||||
|
||||
|
||||
def configure():
|
||||
register_cli_bool('standard-threads', default=False,
|
||||
help='Do not monkey-patch threading system modules.')
|
||||
|
||||
register_cli_str('pydev-debug-host', default=None,
|
||||
help='Host to connect to for remote debugger.')
|
||||
register_cli_int('pydev-debug-port', default=None,
|
||||
help='Port to connect to for remote debugger.')
|
||||
|
||||
register_str('admin_token', secret=True, default='ADMIN')
|
||||
register_str('bind_host', default='0.0.0.0')
|
||||
register_int('compute_port', default=8774)
|
||||
register_int('admin_port', default=35357)
|
||||
register_int('public_port', default=5000)
|
||||
register_str(
|
||||
'public_endpoint', default='http://localhost:%(public_port)s/')
|
||||
register_str('admin_endpoint', default='http://localhost:%(admin_port)s/')
|
||||
register_str('onready')
|
||||
register_str('auth_admin_prefix', default='')
|
||||
register_str('policy_file', default='policy.json')
|
||||
register_str('policy_default_rule', default=None)
|
||||
# default max request size is 112k
|
||||
register_int('max_request_body_size', default=114688)
|
||||
register_int('max_param_size', default=64)
|
||||
# we allow tokens to be a bit larger to accommodate PKI
|
||||
register_int('max_token_size', default=8192)
|
||||
register_str(
|
||||
'member_role_id', default='9fe2ff9ee4384b1894a90878d3e92bab')
|
||||
register_str('member_role_name', default='_member_')
|
||||
|
||||
# identity
|
||||
register_str('default_domain_id', group='identity', default='default')
|
||||
register_int('max_password_length', group='identity', default=4096)
|
||||
|
||||
# trust
|
||||
register_bool('enabled', group='trust', default=True)
|
||||
|
||||
# os_inherit
|
||||
register_bool('enabled', group='os_inherit', default=False)
|
||||
|
||||
# binding
|
||||
register_list('bind', group='token', default=[])
|
||||
register_str('enforce_token_bind', group='token', default='permissive')
|
||||
|
||||
# ssl
|
||||
register_bool('enable', group='ssl', default=False)
|
||||
register_str('certfile', group='ssl',
|
||||
default="/etc/keystone/ssl/certs/keystone.pem")
|
||||
register_str('keyfile', group='ssl',
|
||||
default="/etc/keystone/ssl/private/keystonekey.pem")
|
||||
register_str('ca_certs', group='ssl',
|
||||
default="/etc/keystone/ssl/certs/ca.pem")
|
||||
register_str('ca_key', group='ssl',
|
||||
default="/etc/keystone/ssl/certs/cakey.pem")
|
||||
register_bool('cert_required', group='ssl', default=False)
|
||||
register_int('key_size', group='ssl', default=1024)
|
||||
register_int('valid_days', group='ssl', default=3650)
|
||||
register_str('ca_password', group='ssl', default=None)
|
||||
register_str('cert_subject', group='ssl',
|
||||
default='/C=US/ST=Unset/L=Unset/O=Unset/CN=localhost')
|
||||
|
||||
# signing
|
||||
register_str(
|
||||
'token_format', group='signing', default=None)
|
||||
register_str(
|
||||
'certfile',
|
||||
group='signing',
|
||||
default="/etc/keystone/ssl/certs/signing_cert.pem")
|
||||
register_str(
|
||||
'keyfile',
|
||||
group='signing',
|
||||
default="/etc/keystone/ssl/private/signing_key.pem")
|
||||
register_str(
|
||||
'ca_certs',
|
||||
group='signing',
|
||||
default="/etc/keystone/ssl/certs/ca.pem")
|
||||
register_str('ca_key', group='signing',
|
||||
default="/etc/keystone/ssl/certs/cakey.pem")
|
||||
register_int('key_size', group='signing', default=2048)
|
||||
register_int('valid_days', group='signing', default=3650)
|
||||
register_str('ca_password', group='signing', default=None)
|
||||
register_str('cert_subject', group='signing',
|
||||
default='/C=US/ST=Unset/L=Unset/O=Unset/CN=www.example.com')
|
||||
|
||||
# sql
|
||||
register_str('connection', group='sql', secret=True,
|
||||
default='sqlite:///keystone.db')
|
||||
register_int('idle_timeout', group='sql', default=200)
|
||||
|
||||
#assignment has no default for backward compatibility reasons.
|
||||
#If assignment is not specified, the identity driver chooses the backend
|
||||
register_str(
|
||||
'driver',
|
||||
group='assignment',
|
||||
default=None)
|
||||
register_str(
|
||||
'driver',
|
||||
group='catalog',
|
||||
default='keystone.catalog.backends.sql.Catalog')
|
||||
register_str(
|
||||
'driver',
|
||||
group='identity',
|
||||
default='keystone.identity.backends.sql.Identity')
|
||||
register_str(
|
||||
'driver',
|
||||
group='credential',
|
||||
default='keystone.credential.backends.sql.Credential')
|
||||
register_str(
|
||||
'driver',
|
||||
group='policy',
|
||||
default='keystone.policy.backends.sql.Policy')
|
||||
register_str(
|
||||
'driver', group='token', default='keystone.token.backends.sql.Token')
|
||||
register_str(
|
||||
'driver', group='trust', default='keystone.trust.backends.sql.Trust')
|
||||
register_str(
|
||||
'driver', group='ec2', default='keystone.contrib.ec2.backends.kvs.Ec2')
|
||||
register_str(
|
||||
'driver',
|
||||
group='stats',
|
||||
default='keystone.contrib.stats.backends.kvs.Stats')
|
||||
|
||||
# ldap
|
||||
register_str('url', group='ldap', default='ldap://localhost')
|
||||
register_str('user', group='ldap', default=None)
|
||||
register_str('password', group='ldap', secret=True, default=None)
|
||||
register_str('suffix', group='ldap', default='cn=example,cn=com')
|
||||
register_bool('use_dumb_member', group='ldap', default=False)
|
||||
register_str('dumb_member', group='ldap', default='cn=dumb,dc=nonexistent')
|
||||
register_bool('allow_subtree_delete', group='ldap', default=False)
|
||||
register_str('query_scope', group='ldap', default='one')
|
||||
register_int('page_size', group='ldap', default=0)
|
||||
register_str('alias_dereferencing', group='ldap', default='default')
|
||||
|
||||
register_str('user_tree_dn', group='ldap', default=None)
|
||||
register_str('user_filter', group='ldap', default=None)
|
||||
register_str('user_objectclass', group='ldap', default='inetOrgPerson')
|
||||
register_str('user_id_attribute', group='ldap', default='cn')
|
||||
register_str('user_name_attribute', group='ldap', default='sn')
|
||||
register_str('user_mail_attribute', group='ldap', default='email')
|
||||
register_str('user_pass_attribute', group='ldap', default='userPassword')
|
||||
register_str('user_enabled_attribute', group='ldap', default='enabled')
|
||||
register_str(
|
||||
'user_domain_id_attribute', group='ldap', default='businessCategory')
|
||||
register_int('user_enabled_mask', group='ldap', default=0)
|
||||
register_str('user_enabled_default', group='ldap', default='True')
|
||||
register_list(
|
||||
'user_attribute_ignore', group='ldap', default='tenant_id,tenants')
|
||||
register_bool('user_allow_create', group='ldap', default=True)
|
||||
register_bool('user_allow_update', group='ldap', default=True)
|
||||
register_bool('user_allow_delete', group='ldap', default=True)
|
||||
register_bool('user_enabled_emulation', group='ldap', default=False)
|
||||
register_str('user_enabled_emulation_dn', group='ldap', default=None)
|
||||
register_list(
|
||||
'user_additional_attribute_mapping', group='ldap', default=None)
|
||||
|
||||
register_str('tenant_tree_dn', group='ldap', default=None)
|
||||
register_str('tenant_filter', group='ldap', default=None)
|
||||
register_str('tenant_objectclass', group='ldap', default='groupOfNames')
|
||||
register_str('tenant_id_attribute', group='ldap', default='cn')
|
||||
register_str('tenant_member_attribute', group='ldap', default='member')
|
||||
register_str('tenant_name_attribute', group='ldap', default='ou')
|
||||
register_str('tenant_desc_attribute', group='ldap', default='description')
|
||||
register_str('tenant_enabled_attribute', group='ldap', default='enabled')
|
||||
register_str(
|
||||
'tenant_domain_id_attribute', group='ldap', default='businessCategory')
|
||||
register_list('tenant_attribute_ignore', group='ldap', default='')
|
||||
register_bool('tenant_allow_create', group='ldap', default=True)
|
||||
register_bool('tenant_allow_update', group='ldap', default=True)
|
||||
register_bool('tenant_allow_delete', group='ldap', default=True)
|
||||
register_bool('tenant_enabled_emulation', group='ldap', default=False)
|
||||
register_str('tenant_enabled_emulation_dn', group='ldap', default=None)
|
||||
register_list(
|
||||
'tenant_additional_attribute_mapping', group='ldap', default=None)
|
||||
|
||||
register_str('role_tree_dn', group='ldap', default=None)
|
||||
register_str('role_filter', group='ldap', default=None)
|
||||
register_str(
|
||||
'role_objectclass', group='ldap', default='organizationalRole')
|
||||
register_str('role_id_attribute', group='ldap', default='cn')
|
||||
register_str('role_name_attribute', group='ldap', default='ou')
|
||||
register_str('role_member_attribute', group='ldap', default='roleOccupant')
|
||||
register_list('role_attribute_ignore', group='ldap', default='')
|
||||
register_bool('role_allow_create', group='ldap', default=True)
|
||||
register_bool('role_allow_update', group='ldap', default=True)
|
||||
register_bool('role_allow_delete', group='ldap', default=True)
|
||||
register_list(
|
||||
'role_additional_attribute_mapping', group='ldap', default=None)
|
||||
|
||||
register_str('group_tree_dn', group='ldap', default=None)
|
||||
register_str('group_filter', group='ldap', default=None)
|
||||
register_str('group_objectclass', group='ldap', default='groupOfNames')
|
||||
register_str('group_id_attribute', group='ldap', default='cn')
|
||||
register_str('group_name_attribute', group='ldap', default='ou')
|
||||
register_str('group_member_attribute', group='ldap', default='member')
|
||||
register_str('group_desc_attribute', group='ldap', default='description')
|
||||
register_str(
|
||||
'group_domain_id_attribute', group='ldap', default='businessCategory')
|
||||
register_list('group_attribute_ignore', group='ldap', default='')
|
||||
register_bool('group_allow_create', group='ldap', default=True)
|
||||
register_bool('group_allow_update', group='ldap', default=True)
|
||||
register_bool('group_allow_delete', group='ldap', default=True)
|
||||
register_list(
|
||||
'group_additional_attribute_mapping', group='ldap', default=None)
|
||||
|
||||
register_str('tls_cacertfile', group='ldap', default=None)
|
||||
register_str('tls_cacertdir', group='ldap', default=None)
|
||||
register_bool('use_tls', group='ldap', default=False)
|
||||
register_str('tls_req_cert', group='ldap', default='demand')
|
||||
|
||||
# pam
|
||||
register_str('userid', group='pam', default=None)
|
||||
register_str('password', group='pam', default=None)
|
||||
|
||||
# default authentication methods
|
||||
register_list('methods', group='auth', default=_DEFAULT_AUTH_METHODS)
|
||||
register_str(
|
||||
'password', group='auth', default='keystone.auth.plugins.token.Token')
|
||||
register_str(
|
||||
'token', group='auth',
|
||||
default='keystone.auth.plugins.password.Password')
|
||||
#deals with REMOTE_USER authentication
|
||||
register_str(
|
||||
'external',
|
||||
group='auth',
|
||||
default='keystone.auth.plugins.external.ExternalDefault')
|
||||
# register any non-default auth methods here (used by extensions, etc)
|
||||
for method_name in CONF.auth.methods:
|
||||
if method_name not in _DEFAULT_AUTH_METHODS:
|
||||
register_str(method_name, group='auth')
|
||||
|
||||
# PasteDeploy config file
|
||||
register_str('config_file', group='paste_deploy', default=None)
|
||||
|
||||
# token provider
|
||||
register_str(
|
||||
'provider',
|
||||
group='token',
|
||||
default=None)
|
||||
setup_authentication(conf)
|
||||
|
@ -303,34 +303,35 @@ class V3Controller(V2Controller):
|
||||
ref['id'] = uuid.uuid4().hex
|
||||
return ref
|
||||
|
||||
def _normalize_domain_id(self, context, ref):
|
||||
"""Fill in domain_id if not specified in a v3 call."""
|
||||
def _get_domain_id_for_request(self, context):
|
||||
"""Get the domain_id for a v3 call."""
|
||||
|
||||
if 'domain_id' not in ref:
|
||||
if context['is_admin']:
|
||||
ref['domain_id'] = DEFAULT_DOMAIN_ID
|
||||
else:
|
||||
return DEFAULT_DOMAIN_ID
|
||||
|
||||
# Fish the domain_id out of the token
|
||||
#
|
||||
# We could make this more efficient by loading the domain_id
|
||||
# into the context in the wrapper function above (since
|
||||
# this version of normalize_domain will only be called inside
|
||||
# a v3 protected call). However, given that we only use this
|
||||
# for creating entities, this optimization is probably not
|
||||
# a v3 protected call). However, this optimization is probably not
|
||||
# worth the duplication of state
|
||||
try:
|
||||
token_ref = self.token_api.get_token(
|
||||
token_id=context['token_id'])
|
||||
except exception.TokenNotFound:
|
||||
LOG.warning(_('Invalid token in normalize_domain_id'))
|
||||
LOG.warning(_('Invalid token in _get_domain_id_for_request'))
|
||||
raise exception.Unauthorized()
|
||||
|
||||
if 'domain' in token_ref:
|
||||
ref['domain_id'] = token_ref['domain']['id']
|
||||
return token_ref['domain']['id']
|
||||
else:
|
||||
# FIXME(henry-nash) Revisit this once v3 token scoping
|
||||
# across domains has been hashed out
|
||||
ref['domain_id'] = DEFAULT_DOMAIN_ID
|
||||
return DEFAULT_DOMAIN_ID
|
||||
|
||||
def _normalize_domain_id(self, context, ref):
|
||||
"""Fill in domain_id if not specified in a v3 call."""
|
||||
if 'domain_id' not in ref:
|
||||
ref['domain_id'] = self._get_domain_id_for_request(context)
|
||||
return ref
|
||||
|
||||
def _filter_domain_id(self, ref):
|
||||
|
@ -123,18 +123,14 @@ server_fail = False
|
||||
|
||||
|
||||
class FakeShelve(dict):
|
||||
@classmethod
|
||||
def get_instance(cls):
|
||||
try:
|
||||
return cls.__instance
|
||||
except AttributeError:
|
||||
cls.__instance = cls()
|
||||
return cls.__instance
|
||||
|
||||
def sync(self):
|
||||
pass
|
||||
|
||||
|
||||
FakeShelves = {}
|
||||
|
||||
|
||||
class FakeLdap(object):
|
||||
"""Fake LDAP connection."""
|
||||
|
||||
@ -142,8 +138,10 @@ class FakeLdap(object):
|
||||
|
||||
def __init__(self, url):
|
||||
LOG.debug(_('FakeLdap initialize url=%s'), url)
|
||||
if url == 'fake://memory':
|
||||
self.db = FakeShelve.get_instance()
|
||||
if url.startswith('fake://memory'):
|
||||
if url not in FakeShelves:
|
||||
FakeShelves[url] = FakeShelve()
|
||||
self.db = FakeShelves[url]
|
||||
else:
|
||||
self.db = shelve.open(url[7:])
|
||||
|
||||
|
@ -32,7 +32,6 @@ from keystone.openstack.common import log as logging
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
config.register_int('crypt_strength', default=40000)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
@ -25,15 +25,8 @@ config.configure()
|
||||
CONF = config.CONF
|
||||
|
||||
setup_logging = config.setup_logging
|
||||
register_str = config.register_str
|
||||
register_cli_str = config.register_cli_str
|
||||
register_list = config.register_list
|
||||
register_cli_list = config.register_cli_list
|
||||
register_bool = config.register_bool
|
||||
register_cli_bool = config.register_cli_bool
|
||||
register_int = config.register_int
|
||||
register_cli_int = config.register_cli_int
|
||||
setup_authentication = config.setup_authentication
|
||||
configure = config.configure
|
||||
|
||||
|
||||
def find_paste_config():
|
||||
|
@ -27,6 +27,9 @@ class Identity(kvs.Base, identity.Driver):
|
||||
def default_assignment_driver(self):
|
||||
return "keystone.assignment.backends.kvs.Assignment"
|
||||
|
||||
def is_domain_aware(self):
|
||||
return True
|
||||
|
||||
# Public interface
|
||||
def authenticate(self, user_id, password):
|
||||
user_ref = None
|
||||
|
@ -41,14 +41,19 @@ DEFAULT_DOMAIN = {
|
||||
|
||||
@dependency.requires('assignment_api')
|
||||
class Identity(identity.Driver):
|
||||
def __init__(self):
|
||||
def __init__(self, conf=None):
|
||||
super(Identity, self).__init__()
|
||||
self.user = UserApi(CONF)
|
||||
self.group = GroupApi(CONF)
|
||||
if conf is None:
|
||||
conf = CONF
|
||||
self.user = UserApi(conf)
|
||||
self.group = GroupApi(conf)
|
||||
|
||||
def default_assignment_driver(self):
|
||||
return "keystone.assignment.backends.ldap.Assignment"
|
||||
|
||||
def is_domain_aware(self):
|
||||
return False
|
||||
|
||||
# Identity interface
|
||||
|
||||
def create_project(self, project_id, project):
|
||||
@ -68,37 +73,31 @@ class Identity(identity.Driver):
|
||||
raise AssertionError('Invalid user / password')
|
||||
except Exception:
|
||||
raise AssertionError('Invalid user / password')
|
||||
return self.assignment_api._set_default_domain(
|
||||
identity.filter_user(user_ref))
|
||||
return identity.filter_user(user_ref)
|
||||
|
||||
def _get_user(self, user_id):
|
||||
return self.user.get(user_id)
|
||||
|
||||
def get_user(self, user_id):
|
||||
ref = identity.filter_user(self._get_user(user_id))
|
||||
return self.assignment_api._set_default_domain(ref)
|
||||
return identity.filter_user(self._get_user(user_id))
|
||||
|
||||
def list_users(self):
|
||||
return (self.assignment_api._set_default_domain
|
||||
(self.user.get_all_filtered()))
|
||||
return self.user.get_all_filtered()
|
||||
|
||||
def get_user_by_name(self, user_name, domain_id):
|
||||
self.assignment_api._validate_default_domain_id(domain_id)
|
||||
ref = identity.filter_user(self.user.get_by_name(user_name))
|
||||
return self.assignment_api._set_default_domain(ref)
|
||||
# domain_id will already have been handled in the Manager layer,
|
||||
# parameter left in so this matches the Driver specification
|
||||
return identity.filter_user(self.user.get_by_name(user_name))
|
||||
|
||||
# CRUD
|
||||
def create_user(self, user_id, user):
|
||||
user = self.assignment_api._validate_default_domain(user)
|
||||
user_ref = self.user.create(user)
|
||||
tenant_id = user.get('tenant_id')
|
||||
if tenant_id is not None:
|
||||
self.assignment_api.add_user_to_project(tenant_id, user_id)
|
||||
return (self.assignment_api._set_default_domain
|
||||
(identity.filter_user(user_ref)))
|
||||
return identity.filter_user(user_ref)
|
||||
|
||||
def update_user(self, user_id, user):
|
||||
user = self.assignment_api._validate_default_domain(user)
|
||||
if 'id' in user and user['id'] != user_id:
|
||||
raise exception.ValidationError('Cannot change user ID')
|
||||
old_obj = self.user.get(user_id)
|
||||
@ -121,8 +120,7 @@ class Identity(identity.Driver):
|
||||
user['enabled_nomask'] = old_obj['enabled_nomask']
|
||||
self.user.mask_enabled_attribute(user)
|
||||
self.user.update(user_id, user, old_obj)
|
||||
return (self.assignment_api._set_default_domain
|
||||
(self.user.get_filtered(user_id)))
|
||||
return self.user.get_filtered(user_id)
|
||||
|
||||
def delete_user(self, user_id):
|
||||
self.assignment_api.delete_user(user_id)
|
||||
@ -138,21 +136,16 @@ class Identity(identity.Driver):
|
||||
self.user.delete(user_id)
|
||||
|
||||
def create_group(self, group_id, group):
|
||||
group = self.assignment_api._validate_default_domain(group)
|
||||
group['name'] = clean.group_name(group['name'])
|
||||
return self.assignment_api._set_default_domain(
|
||||
self.group.create(group))
|
||||
return self.group.create(group)
|
||||
|
||||
def get_group(self, group_id):
|
||||
return self.assignment_api._set_default_domain(
|
||||
self.group.get(group_id))
|
||||
return self.group.get(group_id)
|
||||
|
||||
def update_group(self, group_id, group):
|
||||
group = self.assignment_api._validate_default_domain(group)
|
||||
if 'name' in group:
|
||||
group['name'] = clean.group_name(group['name'])
|
||||
return (self.assignment_api._set_default_domain
|
||||
(self.group.update(group_id, group)))
|
||||
return self.group.update(group_id, group)
|
||||
|
||||
def delete_group(self, group_id):
|
||||
return self.group.delete(group_id)
|
||||
@ -172,11 +165,10 @@ class Identity(identity.Driver):
|
||||
def list_groups_for_user(self, user_id):
|
||||
self.get_user(user_id)
|
||||
user_dn = self.user._id_to_dn(user_id)
|
||||
return (self.assignment_api._set_default_domain
|
||||
(self.group.list_user_groups(user_dn)))
|
||||
return self.group.list_user_groups(user_dn)
|
||||
|
||||
def list_groups(self):
|
||||
return self.assignment_api._set_default_domain(self.group.get_all())
|
||||
return self.group.get_all()
|
||||
|
||||
def list_users_in_group(self, group_id):
|
||||
self.get_group(group_id)
|
||||
@ -190,7 +182,7 @@ class Identity(identity.Driver):
|
||||
" '%(group_id)s'. The user should be removed"
|
||||
" from the group. The user will be ignored.") %
|
||||
dict(user_dn=user_dn, group_id=group_id))
|
||||
return self.assignment_api._set_default_domain(users)
|
||||
return users
|
||||
|
||||
def check_user_in_group(self, user_id, group_id):
|
||||
self.get_user(user_id)
|
||||
|
@ -58,6 +58,9 @@ class PamIdentity(identity.Driver):
|
||||
Tenant is always the same as User, root user has admin role.
|
||||
"""
|
||||
|
||||
def is_domain_aware(self):
|
||||
return False
|
||||
|
||||
def authenticate(self, user_id, password):
|
||||
auth = pam.authenticate if pam else PAM_authenticate
|
||||
if not auth(user_id, password):
|
||||
|
@ -85,6 +85,9 @@ class Identity(sql.Base, identity.Driver):
|
||||
"""
|
||||
return utils.check_password(password, user_ref.password)
|
||||
|
||||
def is_domain_aware(self):
|
||||
return True
|
||||
|
||||
# Identity interface
|
||||
def authenticate(self, user_id, password):
|
||||
session = self.get_session()
|
||||
|
@ -620,23 +620,30 @@ class UserV3(controller.V3Controller):
|
||||
|
||||
@controller.filterprotected('domain_id', 'email', 'enabled', 'name')
|
||||
def list_users(self, context, filters):
|
||||
refs = self.identity_api.list_users()
|
||||
refs = self.identity_api.list_users(
|
||||
domain_scope=self._get_domain_id_for_request(context))
|
||||
return UserV3.wrap_collection(context, refs, filters)
|
||||
|
||||
@controller.filterprotected('domain_id', 'email', 'enabled', 'name')
|
||||
def list_users_in_group(self, context, filters, group_id):
|
||||
refs = self.identity_api.list_users_in_group(group_id)
|
||||
refs = self.identity_api.list_users_in_group(
|
||||
group_id,
|
||||
domain_scope=self._get_domain_id_for_request(context))
|
||||
return UserV3.wrap_collection(context, refs, filters)
|
||||
|
||||
@controller.protected
|
||||
def get_user(self, context, user_id):
|
||||
ref = self.identity_api.get_user(user_id)
|
||||
ref = self.identity_api.get_user(
|
||||
user_id,
|
||||
domain_scope=self._get_domain_id_for_request(context))
|
||||
return UserV3.wrap_member(context, ref)
|
||||
|
||||
@controller.protected
|
||||
def update_user(self, context, user_id, user):
|
||||
self._require_matching_id(user_id, user)
|
||||
ref = self.identity_api.update_user(user_id, user)
|
||||
ref = self.identity_api.update_user(
|
||||
user_id, user,
|
||||
domain_scope=self._get_domain_id_for_request(context))
|
||||
|
||||
if user.get('password') or not user.get('enabled', True):
|
||||
# revoke all tokens owned by this user
|
||||
@ -646,18 +653,24 @@ class UserV3(controller.V3Controller):
|
||||
|
||||
@controller.protected
|
||||
def add_user_to_group(self, context, user_id, group_id):
|
||||
self.identity_api.add_user_to_group(user_id, group_id)
|
||||
self.identity_api.add_user_to_group(
|
||||
user_id, group_id,
|
||||
domain_scope=self._get_domain_id_for_request(context))
|
||||
# Delete any tokens so that group membership can have an
|
||||
# immediate effect
|
||||
self._delete_tokens_for_user(user_id)
|
||||
|
||||
@controller.protected
|
||||
def check_user_in_group(self, context, user_id, group_id):
|
||||
return self.identity_api.check_user_in_group(user_id, group_id)
|
||||
return self.identity_api.check_user_in_group(
|
||||
user_id, group_id,
|
||||
domain_scope=self._get_domain_id_for_request(context))
|
||||
|
||||
@controller.protected
|
||||
def remove_user_from_group(self, context, user_id, group_id):
|
||||
self.identity_api.remove_user_from_group(user_id, group_id)
|
||||
self.identity_api.remove_user_from_group(
|
||||
user_id, group_id,
|
||||
domain_scope=self._get_domain_id_for_request(context))
|
||||
self._delete_tokens_for_user(user_id)
|
||||
|
||||
def _delete_user(self, context, user_id):
|
||||
@ -667,11 +680,13 @@ class UserV3(controller.V3Controller):
|
||||
self.credential_api.delete_credential(cred['id'])
|
||||
|
||||
# Make sure any tokens are marked as deleted
|
||||
domain_id = self._get_domain_id_for_request(context)
|
||||
self._delete_tokens_for_user(user_id)
|
||||
# Finally delete the user itself - the backend is
|
||||
# responsible for deleting any role assignments related
|
||||
# to this user
|
||||
return self.identity_api.delete_user(user_id)
|
||||
return self.identity_api.delete_user(
|
||||
user_id, domain_scope=domain_id)
|
||||
|
||||
@controller.protected
|
||||
def delete_user(self, context, user_id):
|
||||
@ -693,24 +708,31 @@ class GroupV3(controller.V3Controller):
|
||||
|
||||
@controller.filterprotected('domain_id', 'name')
|
||||
def list_groups(self, context, filters):
|
||||
refs = self.identity_api.list_groups()
|
||||
refs = self.identity_api.list_groups(
|
||||
domain_scope=self._get_domain_id_for_request(context))
|
||||
return GroupV3.wrap_collection(context, refs, filters)
|
||||
|
||||
@controller.filterprotected('name')
|
||||
def list_groups_for_user(self, context, filters, user_id):
|
||||
refs = self.identity_api.list_groups_for_user(user_id)
|
||||
refs = self.identity_api.list_groups_for_user(
|
||||
user_id,
|
||||
domain_scope=self._get_domain_id_for_request(context))
|
||||
return GroupV3.wrap_collection(context, refs, filters)
|
||||
|
||||
@controller.protected
|
||||
def get_group(self, context, group_id):
|
||||
ref = self.identity_api.get_group(group_id)
|
||||
ref = self.identity_api.get_group(
|
||||
group_id,
|
||||
domain_scope=self._get_domain_id_for_request(context))
|
||||
return GroupV3.wrap_member(context, ref)
|
||||
|
||||
@controller.protected
|
||||
def update_group(self, context, group_id, group):
|
||||
self._require_matching_id(group_id, group)
|
||||
|
||||
ref = self.identity_api.update_group(group_id, group)
|
||||
ref = self.identity_api.update_group(
|
||||
group_id, group,
|
||||
domain_scope=self._get_domain_id_for_request(context))
|
||||
return GroupV3.wrap_member(context, ref)
|
||||
|
||||
def _delete_group(self, context, group_id):
|
||||
@ -720,8 +742,10 @@ class GroupV3(controller.V3Controller):
|
||||
# deletion, so that we can remove these tokens after we know
|
||||
# the group deletion succeeded.
|
||||
|
||||
user_refs = self.identity_api.list_users_in_group(group_id)
|
||||
self.identity_api.delete_group(group_id)
|
||||
domain_id = self._get_domain_id_for_request(context)
|
||||
user_refs = self.identity_api.list_users_in_group(
|
||||
group_id, domain_scope=domain_id)
|
||||
self.identity_api.delete_group(group_id, domain_scope=domain_id)
|
||||
for user in user_refs:
|
||||
self._delete_tokens_for_user(user['id'])
|
||||
|
||||
|
@ -16,11 +16,17 @@
|
||||
|
||||
"""Main entry point into the Identity service."""
|
||||
|
||||
import functools
|
||||
import os
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from keystone import clean
|
||||
from keystone.common import dependency
|
||||
from keystone.common import manager
|
||||
from keystone import config
|
||||
from keystone import exception
|
||||
from keystone.openstack.common import importutils
|
||||
from keystone.openstack.common import log as logging
|
||||
|
||||
|
||||
@ -51,6 +57,121 @@ def filter_user(user_ref):
|
||||
return user_ref
|
||||
|
||||
|
||||
class DomainConfigs(dict):
|
||||
"""Discover, store and provide access to domain specifc configs.
|
||||
|
||||
The setup_domain_drives() call will be made via the wrapper from
|
||||
the first call to any driver function handled by this manager. This
|
||||
setup call it will scan the domain config directory for files of the form
|
||||
|
||||
keystone.<domain_name>.conf
|
||||
|
||||
For each file, the domain_name will be turned into a domain_id and then
|
||||
this class will:
|
||||
- Create a new config structure, adding in the specific additional options
|
||||
defined in this config file
|
||||
- Initialise a new instance of the required driver with this new config.
|
||||
|
||||
"""
|
||||
configured = False
|
||||
driver = None
|
||||
|
||||
def _load_driver(self, assignment_api, domain_id):
|
||||
domain_config = self[domain_id]
|
||||
domain_config['driver'] = (
|
||||
importutils.import_object(
|
||||
domain_config['cfg'].identity.driver, domain_config['cfg']))
|
||||
domain_config['driver'].assignment_api = assignment_api
|
||||
|
||||
def _load_config(self, assignment_api, file_list, domain_name):
|
||||
try:
|
||||
domain_ref = assignment_api.get_domain_by_name(domain_name)
|
||||
except exception.DomainNotFound:
|
||||
msg = (_('Invalid domain name (%s) found in config file name')
|
||||
% domain_name)
|
||||
LOG.warning(msg)
|
||||
|
||||
if domain_ref:
|
||||
# Create a new entry in the domain config dict, which contains
|
||||
# a new instance of both the conf environment and driver using
|
||||
# options defined in this set of config files. Later, when we
|
||||
# service calls via this Manager, we'll index via this domain
|
||||
# config dict to make sure we call the right driver
|
||||
domain = domain_ref['id']
|
||||
self[domain] = {}
|
||||
self[domain]['cfg'] = cfg.ConfigOpts()
|
||||
config.configure(conf=self[domain]['cfg'])
|
||||
self[domain]['cfg'](args=[], project='keystone',
|
||||
default_config_files=file_list)
|
||||
self._load_driver(assignment_api, domain)
|
||||
|
||||
def setup_domain_drivers(self, standard_driver, assignment_api):
|
||||
# This is called by the api call wrapper
|
||||
self.configured = True
|
||||
self.driver = standard_driver
|
||||
|
||||
conf_dir = CONF.identity.domain_config_dir
|
||||
if not os.path.exists(conf_dir):
|
||||
msg = _('Unable to locate domain config directory: %s') % conf_dir
|
||||
LOG.warning(msg)
|
||||
return
|
||||
|
||||
for r, d, f in os.walk(conf_dir):
|
||||
for file in f:
|
||||
if file.startswith('keystone.') and file.endswith('.conf'):
|
||||
names = file.split('.')
|
||||
if len(names) == 3:
|
||||
self._load_config(assignment_api,
|
||||
[os.path.join(r, file)],
|
||||
names[1])
|
||||
else:
|
||||
msg = (_('Ignoring file (%s) while scanning domain '
|
||||
'config directory') % file)
|
||||
LOG.debug(msg)
|
||||
|
||||
def get_domain_driver(self, domain_id):
|
||||
if domain_id in self:
|
||||
return self[domain_id]['driver']
|
||||
|
||||
def get_domain_conf(self, domain_id):
|
||||
if domain_id in self:
|
||||
return self[domain_id]['cfg']
|
||||
|
||||
def reload_domain_driver(self, assignment_api, domain_id):
|
||||
# Only used to support unit tests that want to set
|
||||
# new config values. This should only be called once
|
||||
# the domains have been configured, since it relies on
|
||||
# the fact that the configuration files have already been
|
||||
# read.
|
||||
if self.configured:
|
||||
if domain_id in self:
|
||||
self._load_driver(assignment_api, domain_id)
|
||||
else:
|
||||
# The standard driver
|
||||
self.driver = self.driver()
|
||||
self.driver.assignment_api = assignment_api
|
||||
|
||||
|
||||
def domains_configured(f):
|
||||
"""Wraps API calls to lazy load domain configs after init.
|
||||
|
||||
This is required since the assignment manager needs to be initialized
|
||||
before this manager, and yet this manager's init wants to be
|
||||
able to make assignment calls (to build the domain configs). So
|
||||
instead, we check if the domains have been initialized on entry
|
||||
to each call, and if requires load them,
|
||||
|
||||
"""
|
||||
@functools.wraps(f)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
if (not self.domain_configs.configured and
|
||||
CONF.identity.domain_specific_drivers_enabled):
|
||||
self.domain_configs.setup_domain_drivers(
|
||||
self.driver, self.assignment_api)
|
||||
return f(self, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
@dependency.provider('identity_api')
|
||||
@dependency.requires('assignment_api')
|
||||
class Manager(manager.Manager):
|
||||
@ -59,30 +180,228 @@ class Manager(manager.Manager):
|
||||
See :mod:`keystone.common.manager.Manager` for more details on how this
|
||||
dynamically calls the backend.
|
||||
|
||||
This class also handles the support of domain specific backends, by using
|
||||
the DomainConfigs class. The setup call for DomainConfigs is called
|
||||
from with the @domains_configured wrapper in a lazy loading fashion
|
||||
to get around the fact that we can't satisfy the assignment api it needs
|
||||
from within our __init__() function since the assignment driver is not
|
||||
itself yet intitalized.
|
||||
|
||||
Each of the identity calls are pre-processed here to choose, based on
|
||||
domain, which of the drivers should be called. The non-domain-specific
|
||||
driver is still in place, and is used if there is no specific driver for
|
||||
the domain in question.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(Manager, self).__init__(CONF.identity.driver)
|
||||
self.domain_configs = DomainConfigs()
|
||||
|
||||
# Domain ID normalization methods
|
||||
|
||||
def _set_domain_id(self, ref, domain_id):
|
||||
if isinstance(ref, dict):
|
||||
ref = ref.copy()
|
||||
ref['domain_id'] = domain_id
|
||||
return ref
|
||||
elif isinstance(ref, list):
|
||||
return [self._set_domain_id(x, domain_id) for x in ref]
|
||||
else:
|
||||
raise ValueError(_('Expected dict or list: %s') % type(ref))
|
||||
|
||||
def _clear_domain_id(self, ref):
|
||||
# Clear the domain_id, and then check to ensure that if this
|
||||
# was not the default domain, it is being handled by its own
|
||||
# backend driver.
|
||||
ref = ref.copy()
|
||||
domain_id = ref.pop('domain_id', CONF.identity.default_domain_id)
|
||||
if (domain_id != CONF.identity.default_domain_id and
|
||||
domain_id not in self.domain_configs):
|
||||
raise exception.DomainNotFound(domain_id=domain_id)
|
||||
return ref
|
||||
|
||||
def _normalize_scope(self, domain_scope):
|
||||
if domain_scope is None:
|
||||
return CONF.identity.default_domain_id
|
||||
else:
|
||||
return domain_scope
|
||||
|
||||
def _select_identity_driver(self, domain_id):
|
||||
driver = self.domain_configs.get_domain_driver(domain_id)
|
||||
if driver:
|
||||
return driver
|
||||
else:
|
||||
return self.driver
|
||||
|
||||
def _get_domain_conf(self, domain_id):
|
||||
conf = self.domain_configs.get_domain_conf(domain_id)
|
||||
if conf:
|
||||
return conf
|
||||
else:
|
||||
return CONF
|
||||
|
||||
def _get_domain_id_and_driver(self, domain_scope):
|
||||
domain_id = self._normalize_scope(domain_scope)
|
||||
driver = self._select_identity_driver(domain_id)
|
||||
return (domain_id, driver)
|
||||
|
||||
# The actual driver calls - these are pre/post processed here as
|
||||
# part of the Manager layer to make sure we:
|
||||
#
|
||||
# - select the right driver for this domain
|
||||
# - clear/set domain_ids for drivers that do not support domains
|
||||
|
||||
@domains_configured
|
||||
def authenticate(self, user_id, password, domain_scope=None):
|
||||
domain_id, driver = self._get_domain_id_and_driver(domain_scope)
|
||||
ref = driver.authenticate(user_id, password)
|
||||
if not driver.is_domain_aware():
|
||||
ref = self._set_domain_id(ref, domain_id)
|
||||
return ref
|
||||
|
||||
@domains_configured
|
||||
def create_user(self, user_id, user_ref):
|
||||
user = user_ref.copy()
|
||||
user['name'] = clean.user_name(user['name'])
|
||||
user.setdefault('enabled', True)
|
||||
user['enabled'] = clean.user_enabled(user['enabled'])
|
||||
return self.driver.create_user(user_id, user)
|
||||
|
||||
def update_user(self, user_id, user_ref):
|
||||
# For creating a user, the domain is in the object itself
|
||||
domain_id = user_ref['domain_id']
|
||||
driver = self._select_identity_driver(domain_id)
|
||||
if not driver.is_domain_aware():
|
||||
user = self._clear_domain_id(user)
|
||||
ref = driver.create_user(user_id, user)
|
||||
if not driver.is_domain_aware():
|
||||
ref = self._set_domain_id(ref, domain_id)
|
||||
return ref
|
||||
|
||||
@domains_configured
|
||||
def get_user(self, user_id, domain_scope=None):
|
||||
domain_id, driver = self._get_domain_id_and_driver(domain_scope)
|
||||
ref = driver.get_user(user_id)
|
||||
if not driver.is_domain_aware():
|
||||
ref = self._set_domain_id(ref, domain_id)
|
||||
return ref
|
||||
|
||||
@domains_configured
|
||||
def get_user_by_name(self, user_name, domain_id):
|
||||
driver = self._select_identity_driver(domain_id)
|
||||
ref = driver.get_user_by_name(user_name, domain_id)
|
||||
if not driver.is_domain_aware():
|
||||
ref = self._set_domain_id(ref, domain_id)
|
||||
return ref
|
||||
|
||||
@domains_configured
|
||||
def list_users(self, domain_scope=None):
|
||||
domain_id, driver = self._get_domain_id_and_driver(domain_scope)
|
||||
user_list = driver.list_users()
|
||||
if not driver.is_domain_aware():
|
||||
user_list = self._set_domain_id(user_list, domain_id)
|
||||
return user_list
|
||||
|
||||
@domains_configured
|
||||
def update_user(self, user_id, user_ref, domain_scope=None):
|
||||
user = user_ref.copy()
|
||||
if 'name' in user:
|
||||
user['name'] = clean.user_name(user['name'])
|
||||
if 'enabled' in user:
|
||||
user['enabled'] = clean.user_enabled(user['enabled'])
|
||||
return self.driver.update_user(user_id, user)
|
||||
|
||||
domain_id, driver = self._get_domain_id_and_driver(domain_scope)
|
||||
if not driver.is_domain_aware():
|
||||
user = self._clear_domain_id(user)
|
||||
ref = driver.update_user(user_id, user)
|
||||
if not driver.is_domain_aware():
|
||||
ref = self._set_domain_id(ref, domain_id)
|
||||
return ref
|
||||
|
||||
@domains_configured
|
||||
def delete_user(self, user_id, domain_scope=None):
|
||||
domain_id, driver = self._get_domain_id_and_driver(domain_scope)
|
||||
driver.delete_user(user_id)
|
||||
|
||||
@domains_configured
|
||||
def create_group(self, group_id, group_ref):
|
||||
group = group_ref.copy()
|
||||
group.setdefault('description', '')
|
||||
return self.driver.create_group(group_id, group)
|
||||
|
||||
# For creating a group, the domain is in the object itself
|
||||
domain_id = group_ref['domain_id']
|
||||
driver = self._select_identity_driver(domain_id)
|
||||
if not driver.is_domain_aware():
|
||||
group = self._clear_domain_id(group)
|
||||
ref = driver.create_group(group_id, group)
|
||||
if not driver.is_domain_aware():
|
||||
ref = self._set_domain_id(ref, domain_id)
|
||||
return ref
|
||||
|
||||
@domains_configured
|
||||
def get_group(self, group_id, domain_scope=None):
|
||||
domain_id, driver = self._get_domain_id_and_driver(domain_scope)
|
||||
ref = driver.get_group(group_id)
|
||||
if not driver.is_domain_aware():
|
||||
ref = self._set_domain_id(ref, domain_id)
|
||||
return ref
|
||||
|
||||
@domains_configured
|
||||
def update_group(self, group_id, group, domain_scope=None):
|
||||
domain_id, driver = self._get_domain_id_and_driver(domain_scope)
|
||||
if not driver.is_domain_aware():
|
||||
group = self._clear_domain_id(group)
|
||||
ref = driver.update_group(group_id, group)
|
||||
if not driver.is_domain_aware():
|
||||
ref = self._set_domain_id(ref, domain_id)
|
||||
return ref
|
||||
|
||||
@domains_configured
|
||||
def delete_group(self, group_id, domain_scope=None):
|
||||
domain_id, driver = self._get_domain_id_and_driver(domain_scope)
|
||||
driver.delete_group(group_id)
|
||||
|
||||
@domains_configured
|
||||
def add_user_to_group(self, user_id, group_id, domain_scope=None):
|
||||
domain_id, driver = self._get_domain_id_and_driver(domain_scope)
|
||||
driver.add_user_to_group(user_id, group_id)
|
||||
|
||||
@domains_configured
|
||||
def remove_user_from_group(self, user_id, group_id, domain_scope=None):
|
||||
domain_id, driver = self._get_domain_id_and_driver(domain_scope)
|
||||
driver.remove_user_from_group(user_id, group_id)
|
||||
|
||||
@domains_configured
|
||||
def list_groups_for_user(self, user_id, domain_scope=None):
|
||||
domain_id, driver = self._get_domain_id_and_driver(domain_scope)
|
||||
group_list = driver.list_groups_for_user(user_id)
|
||||
if not driver.is_domain_aware():
|
||||
group_list = self._set_domain_id(group_list, domain_id)
|
||||
return group_list
|
||||
|
||||
@domains_configured
|
||||
def list_groups(self, domain_scope=None):
|
||||
domain_id, driver = self._get_domain_id_and_driver(domain_scope)
|
||||
group_list = driver.list_groups()
|
||||
if not driver.is_domain_aware():
|
||||
group_list = self._set_domain_id(group_list, domain_id)
|
||||
return group_list
|
||||
|
||||
@domains_configured
|
||||
def list_users_in_group(self, group_id, domain_scope=None):
|
||||
domain_id, driver = self._get_domain_id_and_driver(domain_scope)
|
||||
user_list = driver.list_users_in_group(group_id)
|
||||
if not driver.is_domain_aware():
|
||||
user_list = self._set_domain_id(user_list, domain_id)
|
||||
return user_list
|
||||
|
||||
@domains_configured
|
||||
def check_user_in_group(self, user_id, group_id, domain_scope=None):
|
||||
domain_id, driver = self._get_domain_id_and_driver(domain_scope)
|
||||
return driver.check_user_in_group(user_id, group_id)
|
||||
|
||||
# TODO(henry-nash, ayoung) The following cross calls to the assignment
|
||||
# API should be removed, with the controller and tests making the correct
|
||||
# calls direct to assignment.
|
||||
|
||||
def create_project(self, tenant_id, tenant_ref):
|
||||
tenant = tenant_ref.copy()
|
||||
@ -358,6 +677,8 @@ class Driver(object):
|
||||
"""
|
||||
raise exception.NotImplemented()
|
||||
|
||||
#end of identity
|
||||
def is_domain_aware(self):
|
||||
"""Indicates if Driver supports domains."""
|
||||
raise exception.NotImplemented()
|
||||
|
||||
# Assignments
|
||||
#end of identity
|
||||
|
35
keystone/tests/backend_multi_ldap_sql.conf
Normal file
35
keystone/tests/backend_multi_ldap_sql.conf
Normal file
@ -0,0 +1,35 @@
|
||||
[sql]
|
||||
connection = sqlite://
|
||||
#For a file based sqlite use
|
||||
#connection = sqlite:////tmp/keystone.db
|
||||
#To Test MySQL:
|
||||
#connection = mysql://keystone:keystone@localhost/keystone?charset=utf8
|
||||
#To Test PostgreSQL:
|
||||
#connection = postgresql://keystone:keystone@localhost/keystone?client_encoding=utf8
|
||||
idle_timeout = 200
|
||||
|
||||
[identity]
|
||||
# common identity backend is SQL, domain specific configs will
|
||||
# set their backends to ldap
|
||||
driver = keystone.identity.backends.sql.Identity
|
||||
# The test setup will set this to True, to allow easier creation
|
||||
# of initial domain data
|
||||
# domain_specific_drivers_enabled = True
|
||||
|
||||
[assignment]
|
||||
driver = keystone.assignment.backends.sql.Assignment
|
||||
|
||||
[token]
|
||||
driver = keystone.token.backends.sql.Token
|
||||
|
||||
[ec2]
|
||||
driver = keystone.contrib.ec2.backends.sql.Ec2
|
||||
|
||||
[catalog]
|
||||
driver = keystone.catalog.backends.sql.Catalog
|
||||
|
||||
[policy]
|
||||
driver = keystone.policy.backends.sql.Policy
|
||||
|
||||
[trust]
|
||||
driver = keystone.trust.backends.sql.Trust
|
@ -292,9 +292,11 @@ class TestCase(NoModule, unittest.TestCase):
|
||||
for domain in fixtures.DOMAINS:
|
||||
try:
|
||||
rv = self.identity_api.create_domain(domain['id'], domain)
|
||||
except (exception.Conflict, exception.NotImplemented):
|
||||
pass
|
||||
setattr(self, 'domain_%s' % domain['id'], domain)
|
||||
except exception.Conflict:
|
||||
rv = self.identity_api.get_domain(domain['id'])
|
||||
except exception.NotImplemented:
|
||||
rv = domain
|
||||
setattr(self, 'domain_%s' % domain['id'], rv)
|
||||
|
||||
for tenant in fixtures.TENANTS:
|
||||
try:
|
||||
|
14
keystone/tests/keystone.Default.conf
Normal file
14
keystone/tests/keystone.Default.conf
Normal file
@ -0,0 +1,14 @@
|
||||
# The domain-specific configuration file for the default domain for
|
||||
# use with unit tests.
|
||||
#
|
||||
# The domain_name of the default domain is 'Default', hence the
|
||||
# strange mix of upper/lower case in the file name.
|
||||
|
||||
[ldap]
|
||||
url = fake://memory
|
||||
user = cn=Admin
|
||||
password = password
|
||||
suffix = cn=example,cn=com
|
||||
|
||||
[identity]
|
||||
driver = keystone.identity.backends.ldap.Identity
|
11
keystone/tests/keystone.domain1.conf
Normal file
11
keystone/tests/keystone.domain1.conf
Normal file
@ -0,0 +1,11 @@
|
||||
# The domain-specific configuration file for the test domain
|
||||
# 'domain1' for use with unit tests.
|
||||
|
||||
[ldap]
|
||||
url = fake://memory1
|
||||
user = cn=Admin
|
||||
password = password
|
||||
suffix = cn=example,cn=com
|
||||
|
||||
[identity]
|
||||
driver = keystone.identity.backends.ldap.Identity
|
13
keystone/tests/keystone.domain2.conf
Normal file
13
keystone/tests/keystone.domain2.conf
Normal file
@ -0,0 +1,13 @@
|
||||
# The domain-specific configuration file for the test domain
|
||||
# 'domain2' for use with unit tests.
|
||||
|
||||
[ldap]
|
||||
url = fake://memory
|
||||
user = cn=Admin
|
||||
password = password
|
||||
suffix = cn=myroot,cn=com
|
||||
group_tree_dn = ou=UserGroups,dc=myroot,dc=org
|
||||
user_tree_dn = ou=Users,dc=myroot,dc=org
|
||||
|
||||
[identity]
|
||||
driver = keystone.identity.backends.ldap.Identity
|
@ -105,7 +105,9 @@ class IdentityTests(object):
|
||||
self.assertIn(CONF.member_role_id, role_list)
|
||||
|
||||
def test_password_hashed(self):
|
||||
user_ref = self.identity_api._get_user(self.user_foo['id'])
|
||||
driver = self.identity_api._select_identity_driver(
|
||||
self.user_foo['domain_id'])
|
||||
user_ref = driver._get_user(self.user_foo['id'])
|
||||
self.assertNotEqual(user_ref['password'], self.user_foo['password'])
|
||||
|
||||
def test_create_unicode_user_name(self):
|
||||
@ -1521,7 +1523,8 @@ class IdentityTests(object):
|
||||
self.assertRaises(exception.UserNotFound,
|
||||
self.identity_api.update_user,
|
||||
user_id,
|
||||
{'id': user_id})
|
||||
{'id': user_id,
|
||||
'domain_id': DEFAULT_DOMAIN_ID})
|
||||
|
||||
def test_delete_user_with_project_association(self):
|
||||
user = {'id': uuid.uuid4().hex,
|
||||
|
@ -38,8 +38,16 @@ class BaseLDAPIdentity(test_backend.IdentityTests):
|
||||
return self.identity_api.get_domain(CONF.identity.default_domain_id)
|
||||
|
||||
def clear_database(self):
|
||||
db = fakeldap.FakeShelve().get_instance()
|
||||
db.clear()
|
||||
for shelf in fakeldap.FakeShelves:
|
||||
fakeldap.FakeShelves[shelf].clear()
|
||||
|
||||
def reload_backends(self, domain_id):
|
||||
# Only one backend unless we are using separate domain backends
|
||||
self.load_backends()
|
||||
|
||||
def get_config(self, domain_id):
|
||||
# Only one conf structure unless we are using separate domain backends
|
||||
return CONF
|
||||
|
||||
def _set_config(self):
|
||||
self.config([test.etcdir('keystone.conf.sample'),
|
||||
@ -57,6 +65,7 @@ class BaseLDAPIdentity(test_backend.IdentityTests):
|
||||
user = {'id': 'fake1',
|
||||
'name': 'fake1',
|
||||
'password': 'fakepass1',
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'tenants': ['bar']}
|
||||
self.identity_api.create_user('fake1', user)
|
||||
user_ref = self.identity_api.get_user('fake1')
|
||||
@ -71,14 +80,16 @@ class BaseLDAPIdentity(test_backend.IdentityTests):
|
||||
'fake1')
|
||||
|
||||
def test_configurable_forbidden_user_actions(self):
|
||||
CONF.ldap.user_allow_create = False
|
||||
CONF.ldap.user_allow_update = False
|
||||
CONF.ldap.user_allow_delete = False
|
||||
self.load_backends()
|
||||
conf = self.get_config(CONF.identity.default_domain_id)
|
||||
conf.ldap.user_allow_create = False
|
||||
conf.ldap.user_allow_update = False
|
||||
conf.ldap.user_allow_delete = False
|
||||
self.reload_backends(CONF.identity.default_domain_id)
|
||||
|
||||
user = {'id': 'fake1',
|
||||
'name': 'fake1',
|
||||
'password': 'fakepass1',
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'tenants': ['bar']}
|
||||
self.assertRaises(exception.ForbiddenAction,
|
||||
self.identity_api.create_user,
|
||||
@ -100,8 +111,9 @@ class BaseLDAPIdentity(test_backend.IdentityTests):
|
||||
self.user_foo.pop('password')
|
||||
self.assertDictEqual(user_ref, self.user_foo)
|
||||
|
||||
CONF.ldap.user_filter = '(CN=DOES_NOT_MATCH)'
|
||||
self.load_backends()
|
||||
conf = self.get_config(user_ref['domain_id'])
|
||||
conf.ldap.user_filter = '(CN=DOES_NOT_MATCH)'
|
||||
self.reload_backends(user_ref['domain_id'])
|
||||
self.assertRaises(exception.UserNotFound,
|
||||
self.identity_api.get_user,
|
||||
self.user_foo['id'])
|
||||
@ -205,18 +217,21 @@ class BaseLDAPIdentity(test_backend.IdentityTests):
|
||||
|
||||
# Create a group
|
||||
group_id = None
|
||||
group = dict(name=uuid.uuid4().hex)
|
||||
group = dict(name=uuid.uuid4().hex,
|
||||
domain_id=CONF.identity.default_domain_id)
|
||||
group_id = self.identity_api.create_group(group_id, group)['id']
|
||||
|
||||
# Create a couple of users and add them to the group.
|
||||
user_id = None
|
||||
user = dict(name=uuid.uuid4().hex, id=uuid.uuid4().hex)
|
||||
user = dict(name=uuid.uuid4().hex, id=uuid.uuid4().hex,
|
||||
domain_id=CONF.identity.default_domain_id)
|
||||
user_1_id = self.identity_api.create_user(user_id, user)['id']
|
||||
|
||||
self.identity_api.add_user_to_group(user_1_id, group_id)
|
||||
|
||||
user_id = None
|
||||
user = dict(name=uuid.uuid4().hex, id=uuid.uuid4().hex)
|
||||
user = dict(name=uuid.uuid4().hex, id=uuid.uuid4().hex,
|
||||
domain_id=CONF.identity.default_domain_id)
|
||||
user_2_id = self.identity_api.create_user(user_id, user)['id']
|
||||
|
||||
self.identity_api.add_user_to_group(user_2_id, group_id)
|
||||
@ -224,7 +239,9 @@ class BaseLDAPIdentity(test_backend.IdentityTests):
|
||||
# Delete user 2
|
||||
# NOTE(blk-u): need to go directly to user interface to keep from
|
||||
# updating the group.
|
||||
self.identity_api.driver.user.delete(user_2_id)
|
||||
driver = self.identity_api._select_identity_driver(
|
||||
user['domain_id'])
|
||||
driver.user.delete(user_2_id)
|
||||
|
||||
# List group users and verify only user 1.
|
||||
res = self.identity_api.list_users_in_group(group_id)
|
||||
@ -249,13 +266,16 @@ class BaseLDAPIdentity(test_backend.IdentityTests):
|
||||
self.identity_api.create_user(user['id'], user)
|
||||
self.identity_api.add_user_to_project(self.tenant_baz['id'],
|
||||
user['id'])
|
||||
self.identity_api.driver.user.LDAP_USER = None
|
||||
self.identity_api.driver.user.LDAP_PASSWORD = None
|
||||
driver = self.identity_api._select_identity_driver(
|
||||
user['domain_id'])
|
||||
driver.user.LDAP_USER = None
|
||||
driver.user.LDAP_PASSWORD = None
|
||||
|
||||
self.assertRaises(AssertionError,
|
||||
self.identity_api.authenticate,
|
||||
user_id=user['id'],
|
||||
password=None)
|
||||
password=None,
|
||||
domain_scope=user['domain_id'])
|
||||
|
||||
# (spzala)The group and domain crud tests below override the standard ones
|
||||
# in test_backend.py so that we can exclude the update name test, since we
|
||||
@ -460,7 +480,8 @@ class LDAPIdentity(test.TestCase, BaseLDAPIdentity):
|
||||
self.load_backends()
|
||||
self.load_fixtures(default_fixtures)
|
||||
|
||||
user = {'id': 'fake1', 'name': 'fake1', 'enabled': True}
|
||||
user = {'id': 'fake1', 'name': 'fake1', 'enabled': True,
|
||||
'domain_id': CONF.identity.default_domain_id}
|
||||
self.identity_api.create_user('fake1', user)
|
||||
user_ref = self.identity_api.get_user('fake1')
|
||||
self.assertEqual(user_ref['enabled'], True)
|
||||
@ -512,6 +533,7 @@ class LDAPIdentity(test.TestCase, BaseLDAPIdentity):
|
||||
'id': 'extra_attributes',
|
||||
'name': 'EXTRA_ATTRIBUTES',
|
||||
'password': 'extra',
|
||||
'domain_id': CONF.identity.default_domain_id
|
||||
}
|
||||
self.identity_api.create_user(user['id'], user)
|
||||
dn, attrs = self.identity_api.driver.user._ldap_get(user['id'])
|
||||
@ -745,3 +767,230 @@ class LdapIdentitySqlAssignment(sql.Base, test.TestCase, BaseLDAPIdentity):
|
||||
def test_role_filter(self):
|
||||
self.skipTest(
|
||||
'N/A: Not part of SQL backend')
|
||||
|
||||
|
||||
class MultiLDAPandSQLIdentity(sql.Base, test.TestCase, BaseLDAPIdentity):
|
||||
"""Class to test common SQL plus individual LDAP backends.
|
||||
|
||||
We define a set of domains and domain-specific backends:
|
||||
|
||||
- A separate LDAP backend for the default domain
|
||||
- A separate LDAP backend for domain1
|
||||
- domain2 shares the same LDAP as domain1, but uses a different
|
||||
tree attach point
|
||||
- An SQL backend for all other domains (which will include domain3
|
||||
and domain4)
|
||||
|
||||
Normally one would expect that the default domain would be handled as
|
||||
part of the "other domains" - however the above provides better
|
||||
test coverage since most of the existing backend tests use the default
|
||||
domain.
|
||||
|
||||
"""
|
||||
def setUp(self):
|
||||
super(MultiLDAPandSQLIdentity, self).setUp()
|
||||
|
||||
self._set_config()
|
||||
self.load_backends()
|
||||
self.engine = self.get_engine()
|
||||
sql.ModelBase.metadata.create_all(bind=self.engine)
|
||||
self._setup_domain_test_data()
|
||||
|
||||
# All initial domain data setup complete, time to switch on support
|
||||
# for separate backends per domain.
|
||||
|
||||
self.orig_config_domains_enabled = (
|
||||
config.CONF.identity.domain_specific_drivers_enabled)
|
||||
self.opt_in_group('identity', domain_specific_drivers_enabled=True)
|
||||
self.orig_config_dir = (
|
||||
config.CONF.identity.domain_config_dir)
|
||||
self.opt_in_group('identity', domain_config_dir=test.TESTSDIR)
|
||||
self._set_domain_configs()
|
||||
self.clear_database()
|
||||
self.load_fixtures(default_fixtures)
|
||||
|
||||
def tearDown(self):
|
||||
super(MultiLDAPandSQLIdentity, self).tearDown()
|
||||
self.opt_in_group(
|
||||
'identity',
|
||||
domain_config_dir=self.orig_config_dir)
|
||||
self.opt_in_group(
|
||||
'identity',
|
||||
domain_specific_drivers_enabled=self.orig_config_domains_enabled)
|
||||
sql.ModelBase.metadata.drop_all(bind=self.engine)
|
||||
self.engine.dispose()
|
||||
sql.set_global_engine(None)
|
||||
|
||||
def _set_config(self):
|
||||
self.config([test.etcdir('keystone.conf.sample'),
|
||||
test.testsdir('test_overrides.conf'),
|
||||
test.testsdir('backend_multi_ldap_sql.conf')])
|
||||
|
||||
def _setup_domain_test_data(self):
|
||||
|
||||
def create_domain(domain):
|
||||
try:
|
||||
ref = self.assignment_api.create_domain(
|
||||
domain['id'], domain)
|
||||
except exception.Conflict:
|
||||
ref = (
|
||||
self.assignment_api.get_domain_by_name(domain['name']))
|
||||
return ref
|
||||
|
||||
self.domain_default = create_domain(assignment.DEFAULT_DOMAIN)
|
||||
self.domain1 = create_domain(
|
||||
{'id': uuid.uuid4().hex, 'name': 'domain1'})
|
||||
self.domain2 = create_domain(
|
||||
{'id': uuid.uuid4().hex, 'name': 'domain2'})
|
||||
self.domain3 = create_domain(
|
||||
{'id': uuid.uuid4().hex, 'name': 'domain3'})
|
||||
self.domain4 = create_domain(
|
||||
{'id': uuid.uuid4().hex, 'name': 'domain4'})
|
||||
|
||||
def _set_domain_configs(self):
|
||||
# We need to load the domain configs explicitly to ensure the
|
||||
# test overrides are included.
|
||||
self.identity_api.domain_configs._load_config(
|
||||
self.identity_api.assignment_api,
|
||||
[test.etcdir('keystone.conf.sample'),
|
||||
test.testsdir('test_overrides.conf'),
|
||||
test.testsdir('backend_multi_ldap_sql.conf'),
|
||||
test.testsdir('keystone.Default.conf')],
|
||||
'Default')
|
||||
self.identity_api.domain_configs._load_config(
|
||||
self.identity_api.assignment_api,
|
||||
[test.etcdir('keystone.conf.sample'),
|
||||
test.testsdir('test_overrides.conf'),
|
||||
test.testsdir('backend_multi_ldap_sql.conf'),
|
||||
test.testsdir('keystone.domain1.conf')],
|
||||
'domain1')
|
||||
self.identity_api.domain_configs._load_config(
|
||||
self.identity_api.assignment_api,
|
||||
[test.etcdir('keystone.conf.sample'),
|
||||
test.testsdir('test_overrides.conf'),
|
||||
test.testsdir('backend_multi_ldap_sql.conf'),
|
||||
test.testsdir('keystone.domain2.conf')],
|
||||
'domain2')
|
||||
|
||||
def reload_backends(self, domain_id):
|
||||
# Just reload the driver for this domain - which will pickup
|
||||
# any updated cfg
|
||||
self.identity_api.domain_configs.reload_domain_driver(
|
||||
self.identity_api.assignment_api, domain_id)
|
||||
|
||||
def get_config(self, domain_id):
|
||||
# Get the config for this domain, will return CONF
|
||||
# if no specific config defined for this domain
|
||||
return self.identity_api.domain_configs.get_domain_conf(domain_id)
|
||||
|
||||
def test_list_domains(self):
|
||||
self.skipTest(
|
||||
'N/A: Not relevant for multi ldap testing')
|
||||
|
||||
def test_domain_segregation(self):
|
||||
"""Test that separate configs have segregated the domain.
|
||||
|
||||
Test Plan:
|
||||
- Create a user in each of the domains
|
||||
- Make sure that you can only find a given user in its
|
||||
relevant domain
|
||||
- Make sure that for a backend that supports multiple domains
|
||||
you can get the users via any of the domain scopes
|
||||
|
||||
"""
|
||||
def create_user(domain_id):
|
||||
user = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
|
||||
'domain_id': domain_id,
|
||||
'password': uuid.uuid4().hex,
|
||||
'enabled': True}
|
||||
self.identity_api.create_user(user['id'], user)
|
||||
return user
|
||||
|
||||
userd = create_user(CONF.identity.default_domain_id)
|
||||
user1 = create_user(self.domain1['id'])
|
||||
user2 = create_user(self.domain2['id'])
|
||||
user3 = create_user(self.domain3['id'])
|
||||
user4 = create_user(self.domain4['id'])
|
||||
|
||||
# Now check that I can read user1 with the appropriate domain
|
||||
# scope, but won't find it if the wrong scope is used
|
||||
|
||||
ref = self.identity_api.get_user(
|
||||
userd['id'], domain_scope=CONF.identity.default_domain_id)
|
||||
del userd['password']
|
||||
self.assertDictEqual(ref, userd)
|
||||
self.assertRaises(exception.UserNotFound,
|
||||
self.identity_api.get_user,
|
||||
userd['id'],
|
||||
domain_scope=self.domain1['id'])
|
||||
self.assertRaises(exception.UserNotFound,
|
||||
self.identity_api.get_user,
|
||||
userd['id'],
|
||||
domain_scope=self.domain2['id'])
|
||||
self.assertRaises(exception.UserNotFound,
|
||||
self.identity_api.get_user,
|
||||
userd['id'],
|
||||
domain_scope=self.domain3['id'])
|
||||
self.assertRaises(exception.UserNotFound,
|
||||
self.identity_api.get_user,
|
||||
userd['id'],
|
||||
domain_scope=self.domain4['id'])
|
||||
|
||||
ref = self.identity_api.get_user(
|
||||
user1['id'], domain_scope=self.domain1['id'])
|
||||
del user1['password']
|
||||
self.assertDictEqual(ref, user1)
|
||||
ref = self.identity_api.get_user(
|
||||
user2['id'], domain_scope=self.domain2['id'])
|
||||
del user2['password']
|
||||
self.assertDictEqual(ref, user2)
|
||||
|
||||
# Domains 3 and 4 share the same backend, so you should be
|
||||
# able to see user3 and 4 from either
|
||||
|
||||
ref = self.identity_api.get_user(
|
||||
user3['id'], domain_scope=self.domain3['id'])
|
||||
del user3['password']
|
||||
self.assertDictEqual(ref, user3)
|
||||
ref = self.identity_api.get_user(
|
||||
user4['id'], domain_scope=self.domain4['id'])
|
||||
del user4['password']
|
||||
self.assertDictEqual(ref, user4)
|
||||
ref = self.identity_api.get_user(
|
||||
user3['id'], domain_scope=self.domain4['id'])
|
||||
self.assertDictEqual(ref, user3)
|
||||
ref = self.identity_api.get_user(
|
||||
user4['id'], domain_scope=self.domain3['id'])
|
||||
self.assertDictEqual(ref, user4)
|
||||
|
||||
def test_scanning_of_config_dir(self):
|
||||
"""Test the Manager class scans the config directory.
|
||||
|
||||
The setup for the main tests above load the domain configs directly
|
||||
so that the test overrides can be included. This test just makes sure
|
||||
that the standard config directory scanning does pick up the relevant
|
||||
domain config files.
|
||||
|
||||
"""
|
||||
# Confirm that config has drivers_enabled as True, which we will
|
||||
# check has been set to False later in this test
|
||||
self.assertTrue(config.CONF.identity.domain_specific_drivers_enabled)
|
||||
self.load_backends()
|
||||
# Execute any command to trigger the lazy loading of domain configs
|
||||
self.identity_api.list_users(domain_scope=self.domain1['id'])
|
||||
# ...and now check the domain configs have been set up
|
||||
self.assertIn('default', self.identity_api.domain_configs)
|
||||
self.assertIn(self.domain1['id'], self.identity_api.domain_configs)
|
||||
self.assertIn(self.domain2['id'], self.identity_api.domain_configs)
|
||||
self.assertNotIn(self.domain3['id'], self.identity_api.domain_configs)
|
||||
self.assertNotIn(self.domain4['id'], self.identity_api.domain_configs)
|
||||
|
||||
# Finally check that a domain specific config contains items from both
|
||||
# the primary config and the domain specific config
|
||||
conf = self.identity_api.domain_configs.get_domain_conf(
|
||||
self.domain1['id'])
|
||||
# This should now be false, as is the default, since this is not
|
||||
# set in the standard primary config file
|
||||
self.assertFalse(conf.identity.domain_specific_drivers_enabled)
|
||||
# ..and make sure a domain-specifc options is also set
|
||||
self.assertEqual(conf.ldap.url, 'fake://memory1')
|
||||
|
@ -29,8 +29,6 @@ from keystone import token
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
config.register_str('servers', group='memcache', default='localhost:11211')
|
||||
config.register_int('max_compare_and_set_retry', group='memcache', default=16)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
@ -29,7 +29,7 @@ from keystone.openstack.common import timeutils
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
config.register_int('expiration', group='token', default=86400)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user