mirror of
https://github.com/ansible/awx.git
synced 2026-05-24 00:57:48 -02:30
add ldap group type like posixGroupType
* Adds pattern to easy add django-auth-ldap group types classes and to pass parameters via AUTH_LDAP_GROUP_TYPE_PARAMS * Adds new group type PosixUIDGroupType that accepts the attribute, ldap_group_user_attr, on which to search for the user(s) in the group.
This commit is contained in:
@@ -355,6 +355,40 @@ AUTH_LDAP_CONNECTION_OPTIONS = {
|
|||||||
ldap.OPT_NETWORK_TIMEOUT: 30
|
ldap.OPT_NETWORK_TIMEOUT: 30
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# LDAP Backend settings
|
||||||
|
_AUTH_LDAP_SETTINGS_BASE = {
|
||||||
|
"AUTH_LDAP_SERVER_URI": "",
|
||||||
|
"AUTH_LDAP_BIND_DN": "",
|
||||||
|
"AUTH_LDAP_BIND_PASSWORD": "",
|
||||||
|
"AUTH_LDAP_START_TLS": False,
|
||||||
|
"AUTH_LDAP_USER_SEARCH": [],
|
||||||
|
"AUTH_LDAP_USER_DN_TEMPLATE": None,
|
||||||
|
"AUTH_LDAP_USER_ATTR_MAP": {},
|
||||||
|
"AUTH_LDAP_GROUP_SEARCH": [],
|
||||||
|
"AUTH_LDAP_GROUP_TYPE": None,
|
||||||
|
"AUTH_LDAP_GROUP_TYPE_PARAMS": {},
|
||||||
|
"AUTH_LDAP_REQUIRE_GROUP": None,
|
||||||
|
"AUTH_LDAP_DENY_GROUP": None,
|
||||||
|
"AUTH_LDAP_USER_FLAGS_BY_GROUP": {},
|
||||||
|
"AUTH_LDAP_ORGANIZATION_MAP": {},
|
||||||
|
"AUTH_LDAP_TEAM_MAP": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def generate_ldap_backend(kv, count=None):
|
||||||
|
num = ''
|
||||||
|
if count is not None:
|
||||||
|
num = '{}_'.format(count)
|
||||||
|
for k,v in kv.iteritems():
|
||||||
|
new_k = k.replace('AUTH_LDAP_', 'AUTH_LDAP_{}'.format(num))
|
||||||
|
setattr(sys.modules[__name__], new_k, v)
|
||||||
|
|
||||||
|
|
||||||
|
generate_ldap_backend(_AUTH_LDAP_SETTINGS_BASE)
|
||||||
|
for i in xrange(1, 5):
|
||||||
|
generate_ldap_backend(_AUTH_LDAP_SETTINGS_BASE, i)
|
||||||
|
|
||||||
|
|
||||||
# Radius server settings (default to empty string to skip using Radius auth).
|
# Radius server settings (default to empty string to skip using Radius auth).
|
||||||
# Note: These settings may be overridden by database settings.
|
# Note: These settings may be overridden by database settings.
|
||||||
RADIUS_SERVER = ''
|
RADIUS_SERVER = ''
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class LDAPSettings(BaseLDAPSettings):
|
|||||||
defaults = dict(BaseLDAPSettings.defaults.items() + {
|
defaults = dict(BaseLDAPSettings.defaults.items() + {
|
||||||
'ORGANIZATION_MAP': {},
|
'ORGANIZATION_MAP': {},
|
||||||
'TEAM_MAP': {},
|
'TEAM_MAP': {},
|
||||||
|
'GROUP_TYPE_PARAMS': {},
|
||||||
}.items())
|
}.items())
|
||||||
|
|
||||||
def __init__(self, prefix='AUTH_LDAP_', defaults={}):
|
def __init__(self, prefix='AUTH_LDAP_', defaults={}):
|
||||||
|
|||||||
@@ -295,6 +295,27 @@ def _register_ldap(append=None):
|
|||||||
category_slug='ldap',
|
category_slug='ldap',
|
||||||
feature_required='ldap',
|
feature_required='ldap',
|
||||||
default='MemberDNGroupType',
|
default='MemberDNGroupType',
|
||||||
|
depends_on=['AUTH_LDAP{}_GROUP_TYPE_PARAMS'.format(append_str)],
|
||||||
|
)
|
||||||
|
|
||||||
|
register(
|
||||||
|
'AUTH_LDAP{}_GROUP_TYPE_PARAMS'.format(append_str),
|
||||||
|
field_class=fields.LDAPGroupTypeParamsField,
|
||||||
|
label=_('LDAP Group Type'),
|
||||||
|
help_text=_('Parameters to send the chosen group type.'),
|
||||||
|
category=_('LDAP'),
|
||||||
|
category_slug='ldap',
|
||||||
|
default=collections.OrderedDict([
|
||||||
|
#('member_attr', 'member'),
|
||||||
|
('name_attr', 'cn'),
|
||||||
|
]),
|
||||||
|
placeholder=collections.OrderedDict([
|
||||||
|
('ldap_group_user_attr', 'legacyuid'),
|
||||||
|
('member_attr', 'member'),
|
||||||
|
('name_attr', 'cn'),
|
||||||
|
]),
|
||||||
|
feature_required='ldap',
|
||||||
|
depends_on=['AUTH_LDAP{}_GROUP_TYPE'.format(append_str)],
|
||||||
)
|
)
|
||||||
|
|
||||||
register(
|
register(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Python LDAP
|
# Python LDAP
|
||||||
import ldap
|
import ldap
|
||||||
|
import awx
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
@@ -9,6 +10,9 @@ from django.core.exceptions import ValidationError
|
|||||||
import django_auth_ldap.config
|
import django_auth_ldap.config
|
||||||
from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
|
from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
|
||||||
|
|
||||||
|
# This must be imported so get_subclasses picks it up
|
||||||
|
from awx.sso.ldap_group_types import PosixUIDGroupType # noqa
|
||||||
|
|
||||||
# Tower
|
# Tower
|
||||||
from awx.conf import fields
|
from awx.conf import fields
|
||||||
from awx.conf.fields import * # noqa
|
from awx.conf.fields import * # noqa
|
||||||
@@ -335,19 +339,48 @@ class LDAPGroupTypeField(fields.ChoiceField):
|
|||||||
|
|
||||||
def to_representation(self, value):
|
def to_representation(self, value):
|
||||||
if not value:
|
if not value:
|
||||||
return ''
|
return 'MemberDNGroupType'
|
||||||
if not isinstance(value, django_auth_ldap.config.LDAPGroupType):
|
if not isinstance(value, django_auth_ldap.config.LDAPGroupType):
|
||||||
self.fail('type_error', input_type=type(value))
|
self.fail('type_error', input_type=type(value))
|
||||||
return value.__class__.__name__
|
return value.__class__.__name__
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
|
def find_class_in_modules(class_name):
|
||||||
|
module_search_space = [django_auth_ldap.config, awx.sso.ldap_group_types]
|
||||||
|
for m in module_search_space:
|
||||||
|
cls = getattr(m, class_name, None)
|
||||||
|
if cls:
|
||||||
|
return cls
|
||||||
|
return None
|
||||||
|
|
||||||
data = super(LDAPGroupTypeField, self).to_internal_value(data)
|
data = super(LDAPGroupTypeField, self).to_internal_value(data)
|
||||||
if not data:
|
if not data:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
params = getattr(settings, iter(self.depends_on).next(), None) or {}
|
||||||
|
cls = find_class_in_modules(data)
|
||||||
|
if not cls:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Per-group type parameter validation and handling here
|
||||||
|
|
||||||
|
# Backwords compatability. Before AUTH_LDAP_GROUP_TYPE_PARAMS existed
|
||||||
|
# MemberDNGroupType was the only group type, of the underlying lib, that
|
||||||
|
# took a parameter.
|
||||||
|
params_sanitized = dict()
|
||||||
if data.endswith('MemberDNGroupType'):
|
if data.endswith('MemberDNGroupType'):
|
||||||
return getattr(django_auth_ldap.config, data)(member_attr='member')
|
params.setdefault('member_attr', 'member')
|
||||||
else:
|
params_sanitized['member_attr'] = params['member_attr']
|
||||||
return getattr(django_auth_ldap.config, data)()
|
elif data.endswith('PosixUIDGroupType'):
|
||||||
|
params.setdefault('ldap_group_user_attr', 'uid')
|
||||||
|
params_sanitized['ldap_group_user_attr'] = params['ldap_group_user_attr']
|
||||||
|
|
||||||
|
return cls(**params_sanitized)
|
||||||
|
|
||||||
|
|
||||||
|
class LDAPGroupTypeParamsField(fields.DictField):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LDAPUserFlagsField(fields.DictField):
|
class LDAPUserFlagsField(fields.DictField):
|
||||||
|
|||||||
76
awx/sso/ldap_group_types.py
Normal file
76
awx/sso/ldap_group_types.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Copyright (c) 2017 Ansible by Red Hat
|
||||||
|
# All Rights Reserved.
|
||||||
|
|
||||||
|
# Python
|
||||||
|
import ldap
|
||||||
|
|
||||||
|
# Django
|
||||||
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
|
# 3rd party
|
||||||
|
from django_auth_ldap.config import LDAPGroupType
|
||||||
|
|
||||||
|
|
||||||
|
class PosixUIDGroupType(LDAPGroupType):
|
||||||
|
|
||||||
|
def __init__(self, ldap_group_user_attr, *args, **kwargs):
|
||||||
|
super(PosixUIDGroupType, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.ldap_group_user_attr = ldap_group_user_attr
|
||||||
|
|
||||||
|
"""
|
||||||
|
An LDAPGroupType subclass that handles non-standard DS.
|
||||||
|
"""
|
||||||
|
def user_groups(self, ldap_user, group_search):
|
||||||
|
"""
|
||||||
|
Searches for any group that is either the user's primary or contains the
|
||||||
|
user as a member.
|
||||||
|
"""
|
||||||
|
groups = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
user_uid = ldap_user.attrs[self.ldap_group_user_attr][0]
|
||||||
|
|
||||||
|
if 'gidNumber' in ldap_user.attrs:
|
||||||
|
user_gid = ldap_user.attrs['gidNumber'][0]
|
||||||
|
filterstr = u'(|(gidNumber=%s)(memberUid=%s))' % (
|
||||||
|
self.ldap.filter.escape_filter_chars(user_gid),
|
||||||
|
self.ldap.filter.escape_filter_chars(user_uid)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
filterstr = u'(memberUid=%s)' % (
|
||||||
|
self.ldap.filter.escape_filter_chars(user_uid),
|
||||||
|
)
|
||||||
|
|
||||||
|
search = group_search.search_with_additional_term_string(filterstr)
|
||||||
|
groups = search.execute(ldap_user.connection)
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return groups
|
||||||
|
|
||||||
|
def is_member(self, ldap_user, group_dn):
|
||||||
|
"""
|
||||||
|
Returns True if the group is the user's primary group or if the user is
|
||||||
|
listed in the group's memberUid attribute.
|
||||||
|
"""
|
||||||
|
is_member = False
|
||||||
|
try:
|
||||||
|
user_uid = ldap_user.attrs[self.ldap_group_user_attr][0]
|
||||||
|
|
||||||
|
try:
|
||||||
|
is_member = ldap_user.connection.compare_s(force_str(group_dn), 'memberUid', force_str(user_uid))
|
||||||
|
except (ldap.UNDEFINED_TYPE, ldap.NO_SUCH_ATTRIBUTE):
|
||||||
|
is_member = False
|
||||||
|
|
||||||
|
if not is_member:
|
||||||
|
try:
|
||||||
|
user_gid = ldap_user.attrs['gidNumber'][0]
|
||||||
|
is_member = ldap_user.connection.compare_s(force_str(group_dn), 'gidNumber', force_str(user_gid))
|
||||||
|
except (ldap.UNDEFINED_TYPE, ldap.NO_SUCH_ATTRIBUTE):
|
||||||
|
is_member = False
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
is_member = False
|
||||||
|
|
||||||
|
return is_member
|
||||||
|
|
||||||
Reference in New Issue
Block a user