implement multiple ldap servers

This commit is contained in:
Chris Meyers
2018-01-09 10:20:24 -05:00
parent 9431b0b6ff
commit 2ed97aeb0c
11 changed files with 846 additions and 253 deletions

View File

@@ -0,0 +1,163 @@
dn: dc=ansible,dc=com
dc: ansible
description: My wonderful company as much text as you want to place
in this line up to 32K continuation data for the line above must
have <CR> or <CR><LF> i.e. ENTER work
on both Windows and *nix system - new line MUST begin with ONE SPACE
objectClass: dcObject
objectClass: organization
o: ansible.com
# groups
dn: ou=groups,dc=ansible,dc=com
objectClass: top
objectClass: organizationalUnit
ou: groups
# group: Superusers
dn: cn=superusers,ou=groups,dc=ansible,dc=com
objectClass: top
objectClass: groupOfNames
cn: superusers
member: cn=super_user1,ou=people,dc=ansible,dc=com
# group: Engineering
dn: cn=engineering,ou=groups,dc=ansible,dc=com
objectClass: top
objectClass: groupOfNames
cn: engineering
member: cn=eng_admin1,ou=people,dc=ansible,dc=com
member: cn=eng_user1,ou=people,dc=ansible,dc=com
member: cn=eng_user2,ou=people,dc=ansible,dc=com
dn: cn=engineering_admins,ou=groups,dc=ansible,dc=com
objectClass: top
objectClass: groupOfNames
cn: engineering_admins
member: cn=eng_admin1,ou=people,dc=ansible,dc=com
# group: Sales
dn: cn=sales,ou=groups,dc=ansible,dc=com
objectClass: top
objectClass: groupOfNames
cn: sales
member: cn=sales_user1,ou=people,dc=ansible,dc=com
member: cn=sales_user2,ou=people,dc=ansible,dc=com
# group: IT
dn: cn=it,ou=groups,dc=ansible,dc=com
objectClass: top
objectClass: groupOfNames
cn: it
member: cn=it_user1,ou=people,dc=ansible,dc=com
member: cn=it_user2,ou=people,dc=ansible,dc=com
# users
dn: ou=people,dc=ansible,dc=com
objectClass: top
objectClass: organizationalUnit
ou: people
# users - superusers
dn: cn=super_user1,ou=people,dc=ansible,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: super_user1
sn: User 1
givenName: Super
mail: super_user1@ansible.com
userPassword: password
# users - engineering
dn: cn=eng_user1,ou=people,dc=ansible,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: eng_user1
sn: User 1
givenName: Engineering
mail: eng_user1@ansible.com
userPassword: password
dn: cn=eng_user2,ou=people,dc=ansible,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: eng_user2
sn: User 2
givenName: Engineering
mail: eng_user2@ansible.com
userPassword: password
dn: cn=eng_admin1,ou=people,dc=ansible,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: eng_admin1
sn: Admin 1
givenName: Engineering
mail: eng_admin1@ansible.com
userPassword: password
# users - IT
dn: cn=it_user1,ou=people,dc=ansible,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: it_user1
sn: Technology User 1
givenName: Information
mail: it_user1@ansible.com
userPassword: password
dn: cn=it_user2,ou=people,dc=ansible,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: it_user2
sn: Technology User 2
givenName: Information
mail: it_user2@ansible.com
userPassword: password
# users - Sales
dn: cn=sales_user1,ou=people,dc=ansible,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: sales_user1
sn: Person 1
givenName: Sales
mail: sales_user1@ansible.com
userPassword: password
dn: cn=sales_user2,ou=people,dc=ansible,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: sales_user2
sn: Person 2
givenName: Sales
mail: sales_user2@ansible.com
userPassword: password

View File

@@ -0,0 +1,78 @@
dn: dc=example,dc=com
dc: example
description: My wonderful company as much text as you want to place
in this line up to 32K continuation data for the line above must
have <CR> or <CR><LF> i.e. ENTER work
on both Windows and *nix system - new line MUST begin with ONE SPACE
objectClass: dcObject
objectClass: organization
o: example.com
# groups
dn: ou=groups,dc=example,dc=com
objectClass: top
objectClass: organizationalUnit
ou: groups
# group: Superusers
dn: cn=superusers,ou=groups,dc=example,dc=com
objectClass: top
objectClass: groupOfNames
cn: superusers
member: cn=super_user1,ou=people,dc=example,dc=com
# group: Sales
dn: cn=sales,ou=groups,dc=example,dc=com
objectClass: top
objectClass: groupOfNames
cn: sales
member: cn=sales_user1,ou=people,dc=example,dc=com
member: cn=sales_user2,ou=people,dc=example,dc=com
# users
dn: ou=people,dc=example,dc=com
objectClass: top
objectClass: organizationalUnit
ou: people
# users - superusers
dn: cn=super_user1,ou=people,dc=example,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: super_user1
sn: User 1
givenName: Super
mail: super_user1@example.com
userPassword: password
# users - Sales
dn: cn=sales_user1,ou=people,dc=example,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: sales_user1
sn: Person 1
givenName: Sales
mail: sales_user1@example.com
userPassword: password
dn: cn=sales_user2,ou=people,dc=example,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: sales_user2
sn: Person 2
givenName: Sales
mail: sales_user2@example.com
userPassword: password

View File

@@ -0,0 +1,163 @@
dn: dc=redhat,dc=com
dc: redhat
description: My wonderful company as much text as you want to place
in this line up to 32K continuation data for the line above must
have <CR> or <CR><LF> i.e. ENTER work
on both Windows and *nix system - new line MUST begin with ONE SPACE
objectClass: dcObject
objectClass: organization
o: redhat.com
# groups
dn: ou=groups,dc=redhat,dc=com
objectClass: top
objectClass: organizationalUnit
ou: groups
# group: Superusers
dn: cn=superusers,ou=groups,dc=redhat,dc=com
objectClass: top
objectClass: groupOfNames
cn: superusers
member: cn=super_user1,ou=people,dc=redhat,dc=com
# group: Engineering
dn: cn=engineering,ou=groups,dc=redhat,dc=com
objectClass: top
objectClass: groupOfNames
cn: engineering
member: cn=eng_admin1,ou=people,dc=redhat,dc=com
member: cn=eng_user1,ou=people,dc=redhat,dc=com
member: cn=eng_user2,ou=people,dc=redhat,dc=com
dn: cn=engineering_admins,ou=groups,dc=redhat,dc=com
objectClass: top
objectClass: groupOfNames
cn: engineering_admins
member: cn=eng_admin1,ou=people,dc=redhat,dc=com
# group: Sales
dn: cn=sales,ou=groups,dc=redhat,dc=com
objectClass: top
objectClass: groupOfNames
cn: sales
member: cn=sales_user1,ou=people,dc=redhat,dc=com
member: cn=sales_user2,ou=people,dc=redhat,dc=com
# group: IT
dn: cn=it,ou=groups,dc=redhat,dc=com
objectClass: top
objectClass: groupOfNames
cn: it
member: cn=it_user1,ou=people,dc=redhat,dc=com
member: cn=it_user2,ou=people,dc=redhat,dc=com
# users
dn: ou=people,dc=redhat,dc=com
objectClass: top
objectClass: organizationalUnit
ou: people
# users - superusers
dn: cn=super_user1,ou=people,dc=redhat,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: super_user1
sn: User 1
givenName: Super
mail: super_user1@redhat.com
userPassword: password
# users - engineering
dn: cn=eng_user1,ou=people,dc=redhat,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: eng_user1
sn: User 1
givenName: Engineering
mail: eng_user1@redhat.com
userPassword: password
dn: cn=eng_user2,ou=people,dc=redhat,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: eng_user2
sn: User 2
givenName: Engineering
mail: eng_user2@redhat.com
userPassword: password
dn: cn=eng_admin1,ou=people,dc=redhat,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: eng_admin1
sn: Admin 1
givenName: Engineering
mail: eng_admin1@redhat.com
userPassword: password
# users - IT
dn: cn=it_user1,ou=people,dc=redhat,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: it_user1
sn: Technology User 1
givenName: Information
mail: it_user1@redhat.com
userPassword: password
dn: cn=it_user2,ou=people,dc=redhat,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: it_user2
sn: Technology User 2
givenName: Information
mail: it_user2@redhat.com
userPassword: password
# users - Sales
dn: cn=sales_user1,ou=people,dc=redhat,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: sales_user1
sn: Person 1
givenName: Sales
mail: sales_user1@redhat.com
userPassword: password
dn: cn=sales_user2,ou=people,dc=redhat,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: sales_user2
sn: Person 2
givenName: Sales
mail: sales_user2@redhat.com
userPassword: password

View File

@@ -73,7 +73,8 @@ def user():
try: try:
user = User.objects.get(username=name) user = User.objects.get(username=name)
except User.DoesNotExist: except User.DoesNotExist:
user = User(username=name, is_superuser=is_superuser, password=name) user = User(username=name, is_superuser=is_superuser)
user.set_password(name)
user.save() user.save()
return user return user
return u return u

View File

@@ -0,0 +1,130 @@
import ldap
import ldif
import pytest
import os
from mockldap import MockLdap
from awx.api.versioning import reverse
@pytest.fixture
def ldap_generator():
def fn(fname, host='localhost'):
fh = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), fname), 'rb')
ctrl = ldif.LDIFRecordList(fh)
ctrl.parse()
directory = dict(ctrl.all_records)
mockldap = MockLdap(directory)
mockldap.start()
mockldap['ldap://{}/'.format(host)]
conn = ldap.initialize('ldap://{}/'.format(host))
return conn
#mockldap.stop()
return fn
@pytest.fixture
def ldap_settings_generator():
def fn(prefix='', dc='ansible', host='ldap.ansible.com'):
prefix = '_{}'.format(prefix) if prefix else ''
data = {
'AUTH_LDAP_SERVER_URI': 'ldap://{}'.format(host),
'AUTH_LDAP_BIND_DN': 'cn=eng_user1,ou=people,dc={},dc=com'.format(dc),
'AUTH_LDAP_BIND_PASSWORD': 'password',
"AUTH_LDAP_USER_SEARCH": [
"ou=people,dc={},dc=com".format(dc),
"SCOPE_SUBTREE",
"(cn=%(user)s)"
],
"AUTH_LDAP_TEAM_MAP": {
"LDAP Sales": {
"organization": "LDAP Organization",
"users": "cn=sales,ou=groups,dc={},dc=com".format(dc),
"remove": True
},
"LDAP IT": {
"organization": "LDAP Organization",
"users": "cn=it,ou=groups,dc={},dc=com".format(dc),
"remove": True
},
"LDAP Engineering": {
"organization": "LDAP Organization",
"users": "cn=engineering,ou=groups,dc={},dc=com".format(dc),
"remove": True
}
},
"AUTH_LDAP_REQUIRE_GROUP": None,
"AUTH_LDAP_USER_ATTR_MAP": {
"first_name": "givenName",
"last_name": "sn",
"email": "mail"
},
"AUTH_LDAP_GROUP_SEARCH": [
"dc={},dc=com".format(dc),
"SCOPE_SUBTREE",
"(objectClass=groupOfNames)"
],
"AUTH_LDAP_USER_FLAGS_BY_GROUP": {
"is_superuser": "cn=superusers,ou=groups,dc={},dc=com".format(dc)
},
"AUTH_LDAP_ORGANIZATION_MAP": {
"LDAP Organization": {
"admins": "cn=engineering_admins,ou=groups,dc={},dc=com".format(dc),
"remove_admins": False,
"users": [
"cn=engineering,ou=groups,dc={},dc=com".format(dc),
"cn=sales,ou=groups,dc={},dc=com".format(dc),
"cn=it,ou=groups,dc={},dc=com".format(dc)
],
"remove_users": False
}
},
}
if prefix:
data_new = dict()
for k,v in data.iteritems():
k_new = k.replace('AUTH_LDAP', 'AUTH_LDAP{}'.format(prefix))
data_new[k_new] = v
else:
data_new = data
return data_new
return fn
# Note: mockldap isn't fully featured. Fancy queries aren't fully baked.
# However, objects returned are solid so they should flow through django ldap middleware nicely.
@pytest.mark.django_db
def test_login(ldap_generator, patch, post, admin, ldap_settings_generator):
auth_url = reverse('api:auth_token_view')
ldap_settings_url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ldap'})
# Generate mock ldap servers and init with ldap data
ldap_generator("../data/ldap_example.ldif", "ldap.example.com")
ldap_generator("../data/ldap_redhat.ldif", "ldap.redhat.com")
ldap_generator("../data/ldap_ansible.ldif", "ldap.ansible.com")
ldap_settings_example = ldap_settings_generator(dc='example')
ldap_settings_ansible = ldap_settings_generator(prefix='1', dc='ansible')
ldap_settings_redhat = ldap_settings_generator(prefix='2', dc='redhat')
# eng_user1 exists in ansible and redhat but not example
patch(ldap_settings_url, user=admin, data=ldap_settings_example, expect=200)
post(auth_url, data={'username': 'eng_user1', 'password': 'password'}, expect=400)
patch(ldap_settings_url, user=admin, data=ldap_settings_ansible, expect=200)
patch(ldap_settings_url, user=admin, data=ldap_settings_redhat, expect=200)
post(auth_url, data={'username': 'eng_user1', 'password': 'password'}, expect=200)

View File

@@ -20,7 +20,7 @@ def test_model_to_dict_user(alice):
output_dict = model_to_dict(alice) output_dict = model_to_dict(alice)
assert output_dict['username'] == username assert output_dict['username'] == username
assert output_dict['password'] == 'hidden' assert output_dict['password'] == 'hidden'
assert alice.username == password assert alice.username == username
assert alice.password == password assert alice.password == password

View File

@@ -305,6 +305,11 @@ REST_FRAMEWORK = {
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (
'awx.sso.backends.LDAPBackend', 'awx.sso.backends.LDAPBackend',
'awx.sso.backends.LDAPBackend1',
'awx.sso.backends.LDAPBackend2',
'awx.sso.backends.LDAPBackend3',
'awx.sso.backends.LDAPBackend4',
'awx.sso.backends.LDAPBackend5',
'awx.sso.backends.RADIUSBackend', 'awx.sso.backends.RADIUSBackend',
'awx.sso.backends.TACACSPlusBackend', 'awx.sso.backends.TACACSPlusBackend',
'social_core.backends.google.GoogleOAuth2', 'social_core.backends.google.GoogleOAuth2',

View File

@@ -133,6 +133,26 @@ class LDAPBackend(BaseLDAPBackend):
return set() return set()
class LDAPBackend1(LDAPBackend):
settings_prefix = 'AUTH_LDAP_1_'
class LDAPBackend2(LDAPBackend):
settings_prefix = 'AUTH_LDAP_2_'
class LDAPBackend3(LDAPBackend):
settings_prefix = 'AUTH_LDAP_3_'
class LDAPBackend4(LDAPBackend):
settings_prefix = 'AUTH_LDAP_4_'
class LDAPBackend5(LDAPBackend):
settings_prefix = 'AUTH_LDAP_5_'
def _decorate_enterprise_user(user, provider): def _decorate_enterprise_user(user, provider):
user.set_unusable_password() user.set_unusable_password()
user.save() user.save()

View File

@@ -129,271 +129,283 @@ register(
# LDAP AUTHENTICATION SETTINGS # LDAP AUTHENTICATION SETTINGS
############################################################################### ###############################################################################
register(
'AUTH_LDAP_SERVER_URI',
field_class=fields.LDAPServerURIField,
allow_blank=True,
default='',
label=_('LDAP Server URI'),
help_text=_('URI to connect to LDAP server, such as "ldap://ldap.example.com:389" '
'(non-SSL) or "ldaps://ldap.example.com:636" (SSL). Multiple LDAP '
'servers may be specified by separating with spaces or commas. LDAP '
'authentication is disabled if this parameter is empty.'),
category=_('LDAP'),
category_slug='ldap',
placeholder='ldaps://ldap.example.com:636',
feature_required='ldap',
)
register( def _register_ldap(append=None):
'AUTH_LDAP_BIND_DN', append_str = '_{}'.format(append) if append else ''
field_class=fields.CharField,
allow_blank=True,
default='',
validators=[validate_ldap_bind_dn],
label=_('LDAP Bind DN'),
help_text=_('DN (Distinguished Name) of user to bind for all search queries. This'
' is the system user account we will use to login to query LDAP for other'
' user information. Refer to the Ansible Tower documentation for example syntax.'),
category=_('LDAP'),
category_slug='ldap',
feature_required='ldap',
)
register( register(
'AUTH_LDAP_BIND_PASSWORD', 'AUTH_LDAP{}_SERVER_URI'.format(append_str),
field_class=fields.CharField, field_class=fields.LDAPServerURIField,
allow_blank=True, allow_blank=True,
default='', default='',
label=_('LDAP Bind Password'), label=_('LDAP Server URI'),
help_text=_('Password used to bind LDAP user account.'), help_text=_('URI to connect to LDAP server, such as "ldap://ldap.example.com:389" '
category=_('LDAP'), '(non-SSL) or "ldaps://ldap.example.com:636" (SSL). Multiple LDAP '
category_slug='ldap', 'servers may be specified by separating with spaces or commas. LDAP '
feature_required='ldap', 'authentication is disabled if this parameter is empty.'),
encrypted=True, category=_('LDAP'),
) category_slug='ldap',
placeholder='ldaps://ldap.example.com:636',
feature_required='ldap',
)
register( register(
'AUTH_LDAP_START_TLS', 'AUTH_LDAP{}_BIND_DN'.format(append_str),
field_class=fields.BooleanField, field_class=fields.CharField,
default=False, allow_blank=True,
label=_('LDAP Start TLS'), default='',
help_text=_('Whether to enable TLS when the LDAP connection is not using SSL.'), validators=[validate_ldap_bind_dn],
category=_('LDAP'), label=_('LDAP Bind DN'),
category_slug='ldap', help_text=_('DN (Distinguished Name) of user to bind for all search queries. This'
feature_required='ldap', ' is the system user account we will use to login to query LDAP for other'
) ' user information. Refer to the Ansible Tower documentation for example syntax.'),
category=_('LDAP'),
category_slug='ldap',
feature_required='ldap',
)
register( register(
'AUTH_LDAP_CONNECTION_OPTIONS', 'AUTH_LDAP{}_BIND_PASSWORD'.format(append_str),
field_class=fields.LDAPConnectionOptionsField, field_class=fields.CharField,
default={'OPT_REFERRALS': 0, 'OPT_NETWORK_TIMEOUT': 30}, allow_blank=True,
label=_('LDAP Connection Options'), default='',
help_text=_('Additional options to set for the LDAP connection. LDAP ' label=_('LDAP Bind Password'),
'referrals are disabled by default (to prevent certain LDAP ' help_text=_('Password used to bind LDAP user account.'),
'queries from hanging with AD). Option names should be strings ' category=_('LDAP'),
'(e.g. "OPT_REFERRALS"). Refer to ' category_slug='ldap',
'https://www.python-ldap.org/doc/html/ldap.html#options for ' feature_required='ldap',
'possible options and values that can be set.'), encrypted=True,
category=_('LDAP'), )
category_slug='ldap',
placeholder=collections.OrderedDict([
('OPT_REFERRALS', 0),
('OPT_NETWORK_TIMEOUT', 30)
]),
feature_required='ldap',
)
register( register(
'AUTH_LDAP_USER_SEARCH', 'AUTH_LDAP{}_START_TLS'.format(append_str),
field_class=fields.LDAPSearchUnionField, field_class=fields.BooleanField,
default=[], default=False,
label=_('LDAP User Search'), label=_('LDAP Start TLS'),
help_text=_('LDAP search query to find users. Any user that matches the given ' help_text=_('Whether to enable TLS when the LDAP connection is not using SSL.'),
'pattern will be able to login to Tower. The user should also be ' category=_('LDAP'),
'mapped into a Tower organization (as defined in the ' category_slug='ldap',
'AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries ' feature_required='ldap',
'need to be supported use of "LDAPUnion" is possible. See ' )
'Tower documentation for details.'),
category=_('LDAP'),
category_slug='ldap',
placeholder=(
'OU=Users,DC=example,DC=com',
'SCOPE_SUBTREE',
'(sAMAccountName=%(user)s)',
),
feature_required='ldap',
)
register( register(
'AUTH_LDAP_USER_DN_TEMPLATE', 'AUTH_LDAP{}_CONNECTION_OPTIONS'.format(append_str),
field_class=fields.LDAPDNWithUserField, field_class=fields.LDAPConnectionOptionsField,
allow_blank=True, default={'OPT_REFERRALS': 0, 'OPT_NETWORK_TIMEOUT': 30},
allow_null=True, label=_('LDAP Connection Options'),
default=None, help_text=_('Additional options to set for the LDAP connection. LDAP '
label=_('LDAP User DN Template'), 'referrals are disabled by default (to prevent certain LDAP '
help_text=_('Alternative to user search, if user DNs are all of the same ' 'queries from hanging with AD). Option names should be strings '
'format. This approach is more efficient for user lookups than ' '(e.g. "OPT_REFERRALS"). Refer to '
'searching if it is usable in your organizational environment. If ' 'https://www.python-ldap.org/doc/html/ldap.html#options for '
'this setting has a value it will be used instead of ' 'possible options and values that can be set.'),
'AUTH_LDAP_USER_SEARCH.'), category=_('LDAP'),
category=_('LDAP'), category_slug='ldap',
category_slug='ldap', placeholder=collections.OrderedDict([
placeholder='uid=%(user)s,OU=Users,DC=example,DC=com', ('OPT_REFERRALS', 0),
feature_required='ldap', ('OPT_NETWORK_TIMEOUT', 30)
) ]),
feature_required='ldap',
)
register( register(
'AUTH_LDAP_USER_ATTR_MAP', 'AUTH_LDAP{}_USER_SEARCH'.format(append_str),
field_class=fields.LDAPUserAttrMapField, field_class=fields.LDAPSearchUnionField,
default={}, default=[],
label=_('LDAP User Attribute Map'), label=_('LDAP User Search'),
help_text=_('Mapping of LDAP user schema to Tower API user attributes. The default' help_text=_('LDAP search query to find users. Any user that matches the given '
' setting is valid for ActiveDirectory but users with other LDAP' 'pattern will be able to login to Tower. The user should also be '
' configurations may need to change the values. Refer to the Ansible' 'mapped into a Tower organization (as defined in the '
' Tower documentation for additonal details.'), 'AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries '
category=_('LDAP'), 'need to be supported use of "LDAPUnion" is possible. See '
category_slug='ldap', 'Tower documentation for details.'),
placeholder=collections.OrderedDict([ category=_('LDAP'),
('first_name', 'givenName'), category_slug='ldap',
('last_name', 'sn'), placeholder=(
('email', 'mail'), 'OU=Users,DC=example,DC=com',
]), 'SCOPE_SUBTREE',
feature_required='ldap', '(sAMAccountName=%(user)s)',
) ),
feature_required='ldap',
)
register( register(
'AUTH_LDAP_GROUP_SEARCH', 'AUTH_LDAP{}_USER_DN_TEMPLATE'.format(append_str),
field_class=fields.LDAPSearchField, field_class=fields.LDAPDNWithUserField,
default=[], allow_blank=True,
label=_('LDAP Group Search'), allow_null=True,
help_text=_('Users are mapped to organizations based on their membership in LDAP' default=None,
' groups. This setting defines the LDAP search query to find groups. ' label=_('LDAP User DN Template'),
'Unlike the user search, group search does not support LDAPSearchUnion.'), help_text=_('Alternative to user search, if user DNs are all of the same '
category=_('LDAP'), 'format. This approach is more efficient for user lookups than '
category_slug='ldap', 'searching if it is usable in your organizational environment. If '
placeholder=( 'this setting has a value it will be used instead of '
'DC=example,DC=com', 'AUTH_LDAP_USER_SEARCH.'),
'SCOPE_SUBTREE', category=_('LDAP'),
'(objectClass=group)', category_slug='ldap',
), placeholder='uid=%(user)s,OU=Users,DC=example,DC=com',
feature_required='ldap', feature_required='ldap',
) )
register( register(
'AUTH_LDAP_GROUP_TYPE', 'AUTH_LDAP{}_USER_ATTR_MAP'.format(append_str),
field_class=fields.LDAPGroupTypeField, field_class=fields.LDAPUserAttrMapField,
label=_('LDAP Group Type'), default={},
help_text=_('The group type may need to be changed based on the type of the ' label=_('LDAP User Attribute Map'),
'LDAP server. Values are listed at: ' help_text=_('Mapping of LDAP user schema to Tower API user attributes. The default'
'https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups'), ' setting is valid for ActiveDirectory but users with other LDAP'
category=_('LDAP'), ' configurations may need to change the values. Refer to the Ansible'
category_slug='ldap', ' Tower documentation for additonal details.'),
feature_required='ldap', category=_('LDAP'),
default='MemberDNGroupType', category_slug='ldap',
) placeholder=collections.OrderedDict([
('first_name', 'givenName'),
('last_name', 'sn'),
('email', 'mail'),
]),
feature_required='ldap',
)
register( register(
'AUTH_LDAP_REQUIRE_GROUP', 'AUTH_LDAP{}_GROUP_SEARCH'.format(append_str),
field_class=fields.LDAPDNField, field_class=fields.LDAPSearchField,
allow_blank=True, default=[],
allow_null=True, label=_('LDAP Group Search'),
default=None, help_text=_('Users are mapped to organizations based on their membership in LDAP'
label=_('LDAP Require Group'), ' groups. This setting defines the LDAP search query to find groups. '
help_text=_('Group DN required to login. If specified, user must be a member ' 'Unlike the user search, group search does not support LDAPSearchUnion.'),
'of this group to login via LDAP. If not set, everyone in LDAP ' category=_('LDAP'),
'that matches the user search will be able to login via Tower. ' category_slug='ldap',
'Only one require group is supported.'), placeholder=(
category=_('LDAP'), 'DC=example,DC=com',
category_slug='ldap', 'SCOPE_SUBTREE',
placeholder='CN=Tower Users,OU=Users,DC=example,DC=com', '(objectClass=group)',
feature_required='ldap', ),
) feature_required='ldap',
)
register( register(
'AUTH_LDAP_DENY_GROUP', 'AUTH_LDAP{}_GROUP_TYPE'.format(append_str),
field_class=fields.LDAPDNField, field_class=fields.LDAPGroupTypeField,
allow_blank=True, label=_('LDAP Group Type'),
allow_null=True, help_text=_('The group type may need to be changed based on the type of the '
default=None, 'LDAP server. Values are listed at: '
label=_('LDAP Deny Group'), 'https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups'),
help_text=_('Group DN denied from login. If specified, user will not be ' category=_('LDAP'),
'allowed to login if a member of this group. Only one deny group ' category_slug='ldap',
'is supported.'), feature_required='ldap',
category=_('LDAP'), default='MemberDNGroupType',
category_slug='ldap', )
placeholder='CN=Disabled Users,OU=Users,DC=example,DC=com',
feature_required='ldap',
)
register( register(
'AUTH_LDAP_USER_FLAGS_BY_GROUP', 'AUTH_LDAP{}_REQUIRE_GROUP'.format(append_str),
field_class=fields.LDAPUserFlagsField, field_class=fields.LDAPDNField,
default={}, allow_blank=True,
label=_('LDAP User Flags By Group'), allow_null=True,
help_text=_('Retrieve users from a given group. At this time, superuser and system' default=None,
' auditors are the only groups supported. Refer to the Ansible Tower' label=_('LDAP Require Group'),
' documentation for more detail.'), help_text=_('Group DN required to login. If specified, user must be a member '
category=_('LDAP'), 'of this group to login via LDAP. If not set, everyone in LDAP '
category_slug='ldap', 'that matches the user search will be able to login via Tower. '
placeholder=collections.OrderedDict([ 'Only one require group is supported.'),
('is_superuser', 'CN=Domain Admins,CN=Users,DC=example,DC=com'), category=_('LDAP'),
('is_system_auditor', 'CN=Domain Auditors,CN=Users,DC=example,DC=com'), category_slug='ldap',
]), placeholder='CN=Tower Users,OU=Users,DC=example,DC=com',
feature_required='ldap', feature_required='ldap',
) )
register( register(
'AUTH_LDAP_ORGANIZATION_MAP', 'AUTH_LDAP{}_DENY_GROUP'.format(append_str),
field_class=fields.LDAPOrganizationMapField, field_class=fields.LDAPDNField,
default={}, allow_blank=True,
label=_('LDAP Organization Map'), allow_null=True,
help_text=_('Mapping between organization admins/users and LDAP groups. This ' default=None,
'controls which users are placed into which Tower organizations ' label=_('LDAP Deny Group'),
'relative to their LDAP group memberships. Configuration details ' help_text=_('Group DN denied from login. If specified, user will not be '
'are available in the Ansible Tower documentation.'), 'allowed to login if a member of this group. Only one deny group '
category=_('LDAP'), 'is supported.'),
category_slug='ldap', category=_('LDAP'),
placeholder=collections.OrderedDict([ category_slug='ldap',
('Test Org', collections.OrderedDict([ placeholder='CN=Disabled Users,OU=Users,DC=example,DC=com',
('admins', 'CN=Domain Admins,CN=Users,DC=example,DC=com'), feature_required='ldap',
('users', ['CN=Domain Users,CN=Users,DC=example,DC=com']), )
('remove_users', True),
('remove_admins', True),
])),
('Test Org 2', collections.OrderedDict([
('admins', 'CN=Administrators,CN=Builtin,DC=example,DC=com'),
('users', True),
('remove_users', True),
('remove_admins', True),
])),
]),
feature_required='ldap',
)
register( register(
'AUTH_LDAP_TEAM_MAP', 'AUTH_LDAP{}_USER_FLAGS_BY_GROUP'.format(append_str),
field_class=fields.LDAPTeamMapField, field_class=fields.LDAPUserFlagsField,
default={}, default={},
label=_('LDAP Team Map'), label=_('LDAP User Flags By Group'),
help_text=_('Mapping between team members (users) and LDAP groups. Configuration' help_text=_('Retrieve users from a given group. At this time, superuser and system'
' details are available in the Ansible Tower documentation.'), ' auditors are the only groups supported. Refer to the Ansible Tower'
category=_('LDAP'), ' documentation for more detail.'),
category_slug='ldap', category=_('LDAP'),
placeholder=collections.OrderedDict([ category_slug='ldap',
('My Team', collections.OrderedDict([ placeholder=collections.OrderedDict([
('organization', 'Test Org'), ('is_superuser', 'CN=Domain Admins,CN=Users,DC=example,DC=com'),
('users', ['CN=Domain Users,CN=Users,DC=example,DC=com']), ('is_system_auditor', 'CN=Domain Auditors,CN=Users,DC=example,DC=com'),
('remove', True), ]),
])), feature_required='ldap',
('Other Team', collections.OrderedDict([ )
('organization', 'Test Org 2'),
('users', 'CN=Other Users,CN=Users,DC=example,DC=com'), register(
('remove', False), 'AUTH_LDAP{}_ORGANIZATION_MAP'.format(append_str),
])), field_class=fields.LDAPOrganizationMapField,
]), default={},
feature_required='ldap', label=_('LDAP Organization Map'),
) help_text=_('Mapping between organization admins/users and LDAP groups. This '
'controls which users are placed into which Tower organizations '
'relative to their LDAP group memberships. Configuration details '
'are available in the Ansible Tower documentation.'),
category=_('LDAP'),
category_slug='ldap',
placeholder=collections.OrderedDict([
('Test Org', collections.OrderedDict([
('admins', 'CN=Domain Admins,CN=Users,DC=example,DC=com'),
('users', ['CN=Domain Users,CN=Users,DC=example,DC=com']),
('remove_users', True),
('remove_admins', True),
])),
('Test Org 2', collections.OrderedDict([
('admins', 'CN=Administrators,CN=Builtin,DC=example,DC=com'),
('users', True),
('remove_users', True),
('remove_admins', True),
])),
]),
feature_required='ldap',
)
register(
'AUTH_LDAP{}_TEAM_MAP'.format(append_str),
field_class=fields.LDAPTeamMapField,
default={},
label=_('LDAP Team Map'),
help_text=_('Mapping between team members (users) and LDAP groups. Configuration'
' details are available in the Ansible Tower documentation.'),
category=_('LDAP'),
category_slug='ldap',
placeholder=collections.OrderedDict([
('My Team', collections.OrderedDict([
('organization', 'Test Org'),
('users', ['CN=Domain Users,CN=Users,DC=example,DC=com']),
('remove', True),
])),
('Other Team', collections.OrderedDict([
('organization', 'Test Org 2'),
('users', 'CN=Other Users,CN=Users,DC=example,DC=com'),
('remove', False),
])),
]),
feature_required='ldap',
)
_register_ldap()
_register_ldap('1')
_register_ldap('2')
_register_ldap('3')
_register_ldap('4')
_register_ldap('5')
############################################################################### ###############################################################################
# RADIUS AUTHENTICATION SETTINGS # RADIUS AUTHENTICATION SETTINGS

View File

@@ -31,6 +31,21 @@ class AuthenticationBackendsField(fields.StringListField):
('awx.sso.backends.LDAPBackend', [ ('awx.sso.backends.LDAPBackend', [
'AUTH_LDAP_SERVER_URI', 'AUTH_LDAP_SERVER_URI',
]), ]),
('awx.sso.backends.LDAPBackend1', [
'AUTH_LDAP_1_SERVER_URI',
]),
('awx.sso.backends.LDAPBackend2', [
'AUTH_LDAP_2_SERVER_URI',
]),
('awx.sso.backends.LDAPBackend3', [
'AUTH_LDAP_3_SERVER_URI',
]),
('awx.sso.backends.LDAPBackend4', [
'AUTH_LDAP_4_SERVER_URI',
]),
('awx.sso.backends.LDAPBackend5', [
'AUTH_LDAP_5_SERVER_URI',
]),
('awx.sso.backends.RADIUSBackend', [ ('awx.sso.backends.RADIUSBackend', [
'RADIUS_SERVER', 'RADIUS_SERVER',
]), ]),
@@ -70,6 +85,11 @@ class AuthenticationBackendsField(fields.StringListField):
REQUIRED_BACKEND_FEATURE = { REQUIRED_BACKEND_FEATURE = {
'awx.sso.backends.LDAPBackend': 'ldap', 'awx.sso.backends.LDAPBackend': 'ldap',
'awx.sso.backends.LDAPBackend1': 'ldap',
'awx.sso.backends.LDAPBackend2': 'ldap',
'awx.sso.backends.LDAPBackend3': 'ldap',
'awx.sso.backends.LDAPBackend4': 'ldap',
'awx.sso.backends.LDAPBackend5': 'ldap',
'awx.sso.backends.RADIUSBackend': 'enterprise_auth', 'awx.sso.backends.RADIUSBackend': 'enterprise_auth',
'awx.sso.backends.SAMLAuth': 'enterprise_auth', 'awx.sso.backends.SAMLAuth': 'enterprise_auth',
} }

View File

@@ -16,3 +16,4 @@ uwsgitop
jupyter jupyter
matplotlib matplotlib
backports.tempfile # support in unit tests for py32+ tempfile.TemporaryDirectory backports.tempfile # support in unit tests for py32+ tempfile.TemporaryDirectory
mockldap