def get_user_groups(user_dn, user_attrs, connection=None): if not connection: connection = get_connection() groups = dict() # get groups referred by group objects if lsettings.GROUP_MEMBERS_FIELD: if lsettings.GROUP_MEMBERS_USE_DN: search_filter = '(%s=%s)' % (lsettings.GROUP_MEMBERS_FIELD, escape_filter_chars(user_dn)) elif hasattr(user_attrs, 'items') and user_attrs.get(lsettings.USER_ID_FIELD): search_filter = '(%s=%s)' % (lsettings.GROUP_MEMBERS_FIELD, escape_filter_chars(user_attrs[lsettings.USER_ID_FIELD][0])) else: search_filter = None if search_filter: results = ldap_search(lsettings.GROUP_SEARCH_SCOPE, search_filter, connection=connection) for group in results: groups[group['dn']] = group['attributes'] # get groups referred by user object if lsettings.USER_GROUPS_FIELD and hasattr(user_attrs, 'items') and user_attrs.get(lsettings.USER_GROUPS_FIELD): for name in user_attrs[lsettings.USER_GROUPS_FIELD]: if lsettings.USER_GROUPS_USE_DN: results = ldap_search(name, lsettings.GROUP_LIST_FILTER, connection=connection) else: results = ldap_search(lsettings.GROUP_SEARCH_SCOPE, lsettings.GROUP_SEARCH_FILTER % dict(group=escape_filter_chars(name)), connection=connection) for group in results: if group['dn'] not in groups: groups[group['dn']] = group['attributes'] return groups
def profile(request, username=None): if not username: if request.user.is_authenticated: username = request.user.username else: raise Http404("No such user!") with Connection(LDAP_URL) as c: c.search( "ou=People,dc=csua,dc=berkeley,dc=edu", "(uid={})".format(escape_filter_chars(username)), attributes="gecos", ) if len(c.entries) == 0: raise Http404("No such user!") realname = str(c.entries[0].gecos).split(",", 1)[0] c.search( "ou=Group,dc=csua,dc=berkeley,dc=edu", "(memberUid={})".format(escape_filter_chars(username)), attributes="cn", ) groups = [str(entry.cn) for entry in c.entries] return render( request, "profile.html", {"username": username, "groups": groups, "realname": realname}, )
def do_classes_search(q): c = LDAPConnection() result_dns = [] q = escape_filter_chars(q) q = q.replace(escape_filter_chars('*'), '*') parts = q.split(" ") # split on each word i = 0 for p in parts: exact = False logger.debug(p) if p.startswith('"') and p.endswith('"'): exact = True p = p[1:-1] if exact: logger.debug("Simple exact: {}".format(p)) # No implied wildcard query = (("(&(|(tjhsstClassId={0})" "(cn={0})" ")(|(objectClass=tjhsstClass)))")).format(p) else: logger.debug("Simple wildcard: {}".format(p)) if p.endswith("*"): p = p[:-1] if p.startswith("*"): p = p[1:] # Search for first, last, middle, nickname uid, with implied # wildcard at beginning and end query = (("(&(|(tjhsstClassId=*{0})" "(tjhsstClassId={0}*)" "(cn=*{0})" "(cn={0}*)" ")(|(objectClass=tjhsstClass)))")).format(p) logger.debug("Running LDAP query: {}".format(query)) res = c.search(settings.CLASS_DN, query, None) new_dns = [] # if multiple words, delete those that weren't in previous searches for row in res: dn = row["dn"] if i == 0: new_dns.append(dn) elif dn in result_dns: new_dns.append(dn) result_dns = new_dns i += 1 # loop through the DNs saved and get actual user objects classes = [] for dn in result_dns: result_class = Class(dn=dn) if result_class not in classes: classes.append(result_class) classes = sorted(classes, key=lambda c: str(c)) return classes
def get(cls, key): from ldap3.utils.conv import escape_filter_chars key_attr, _ = cls.keyMapping try: return cls.query('{key_attr}: {key}'.format( key_attr=escape_filter_chars(key_attr), key=escape_filter_chars(key)))[0] except IndexError: return None
def get_adobject(self, queried_domain=str(), queried_sid=str(), queried_name=str(), queried_sam_account_name=str(), ads_path=str(), attributes=list(), custom_filter=str()): for attr_desc, attr_value in (('objectSid', queried_sid), ('name', escape_filter_chars(queried_name)), ('samAccountName', escape_filter_chars(queried_sam_account_name))): if attr_value: object_filter = '(&({}={}){})'.format(attr_desc, attr_value, custom_filter) break else: object_filter = '(&(name=*){})'.format(custom_filter) return self._ldap_search(object_filter, adobj.ADObject, attributes=attributes)
def search_hosts(attributes=None, **params): assert isinstance(g.openldap_session, OpenLDAPSession) condition_list = ['(objectClass=*)', '(objectClass=device)'] if attributes is None: attributes = 'cn', 'ipHostNumber' if 'cn' in params: condition_list.append('(cn=%s)' % escape_filter_chars(params['cn'])) if 'cnlike' in params: condition_list.append('(cn=*%s*)' % escape_filter_chars(params['cnlike'])) condition_str = '(&' + ''.join(condition_list) + ')' return g.openldap_session.search_hosts(condition_str, attributes)
def authenticate(self, environ, identity): logger = logging.getLogger('repoze.who') if 'login' not in identity: return with make_connection(self.url, self.bind_dn, self.bind_pass) as conn: if self.start_tls: conn.start_tls() if not conn.bind(): logger.error('Cannot establish connection') return escaped_login = escape_filter_chars(identity['login']) search = \ self.search_pattern % escaped_login conn.search(self.base_dn, search, self.search_scope) if len(conn.response) > 1: logger.error('Too many entries found for %s', search) return if len(conn.response) < 1: logger.warn('No entry found for %s', search) return dn = conn.response[0]['dn'] # Ensure proper encoding of unicode passwords password = identity['password'].encode('utf-8') with make_connection(self.url, dn, password) as check: if not check.bind(): return save_userdata(identity, dn) return dn if self.ret_style == 'd' else identity['login']
def by_username(self, username) -> 'User': result = self._query('(uid={username:s})'.format( username=escape_filter_chars(username))) if len(result) > 0: return result[0] else: return None
def user_login(): """Allow passhportd to handle login/passwords for users""" # Only POST data are handled if request.method != "POST": return utils.response("ERROR: POST method is required ", 405) # Simplification for the reading login = request.form["login"] password = request.form["password"] # Check for required fields if not login or not password: return utils.response("ERROR: The login and password are required ", 417) elif login != escape_filter_chars(login): return utils.response("ERROR: Bad input", 417) # Check data validity uppon LDAP/local/whatever... result = try_login(login, password) if result == "success": app.logger.info("Authentication ok for {}".format(login)) # If the LDAP connection is ok, user can connect return utils.response("Authorized", 200) app.logger.warning("Authentication error for {} => ".format(login) + str(result)) return utils.response("Refused: " + str(result), 200)
def useruid(s, login): """Connect to a LDAP and check the uid matching the given field data""" uid = False c = Connection(s, config.LDAPACC, password=config.LDAPPASS, auto_bind=True) if c.result["description"] != "success": app.logger.error( "Error connecting to the LDAP with the service account") return False # Look for the user entry. if not c.search( config.LDAPBASE, "(" + config.LDAPFIELD + "=" + escape_filter_chars(login) + ")"): app.logger.error( "Error: Connection to the LDAP with service account failed") else: if len(c.entries) >= 1: if len(c.entries) > 1: app.logger.error("Error: multiple entries with this login. "+ \ "Trying first entry...") uid = c.entries[0].entry_dn else: app.logger.error("Error: Login not found") c.unbind() return uid
def do_rename_computer(self, line): args = shlex.split(line) if len(args) != 2: raise Exception("Current Computer sAMAccountName and New Computer sAMAccountName required (rename_computer comp1$ comp2$).") current_name = args[0] new_name = args[1] self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(current_name), attributes=['objectSid', 'sAMAccountName']) computer_dn = self.client.entries[0].entry_dn if not computer_dn: raise Exception("Computer not found in LDAP: %s" % current_name) entry = self.client.entries[0] samAccountName = entry["samAccountName"].value print("Original sAMAccountName: %s" % samAccountName) print("New sAMAccountName: %s" % new_name) self.client.modify(computer_dn, {'sAMAccountName':(ldap3.MODIFY_REPLACE, [new_name])}) if self.client.result["result"] == 0: print("Updated sAMAccountName successfully") else: if self.client.result['result'] == 50: raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message']) elif self.client.result['result'] == 19: raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message']) else: raise Exception('The server returned an error: %s', self.client.result['message'])
def get_user_info(self, user=None): """ Look up user info from LDAP :param user: :type user: :return: :rtype: """ if any(attr in user.casefold() for attr in ["uid=", "cn="]): search_base = user else: search_base = self.LDAP_USER_BASE_DN try: try: self.conn.search( search_base=search_base, search_filter=self.LDAP_USER_FILTER.replace( "{username}", escape_filter_chars(user)), attributes=["*"], ) if len(self.conn.entries) > 0: data = json.loads(self.conn.entries[0].entry_to_json()) return data except Exception as e: traceback.print_exc(file=sys.stderr) except Exception as e: traceback.print_exc(file=sys.stderr)
def passwordNone(): dn = "dc={}".format(escape_rdn(request.args['dc'])) search_filter = "(user={})".format(escape_filter_chars(request.args['search'])) srv = Server('servername', get_info=ALL) conn = Connection(srv, user='******', password=None) status, result, response, _ = conn.search(dn, search_filter)
def authenticate(self, credentials, request): # pylint: disable=unused-argument """Authenticate provided credentials""" if not self.enabled: return None attrs = credentials.attributes login = attrs.get('login') password = attrs.get('password') conn = self.get_connection() search = LDAPQuery(self.base_dn, self.login_query, self.search_scope, (self.login_attribute, self.uid_attribute)) result = search.execute(conn, login=escape_filter_chars(login)) if not result or len(result) > 1: return None result = result[0] login_dn = result[0] try: login_conn = self.get_connection(user=login_dn, password=password) login_conn.unbind() except LDAPBindError: LOGGER.debug("LDAP authentication exception with login %r", login, exc_info=True) return None else: if self.uid_attribute == DN_ATTRIBUTE: return USER_DN_PREFIX.format(prefix=self.prefix, dn=login_dn) attrs = result[1] if self.login_attribute in attrs: return USER_ATTR_PREFIX.format(prefix=self.prefix, attr=attrs[self.uid_attribute][0]) return None
def resolve_samname(self, samname): """ Resolve a SAM name in the GC. This can give multiple results. Returns a list of LDAP entries """ out = [] safename = escape_filter_chars(samname) with self.lock: if not self.addc.gcldap: if not self.addc.gc_connect(): # Error connecting, bail return None logging.debug('Querying GC for SAM Name %s', samname) entries = self.addc.search( search_base="", search_filter='(sAMAccountName=%s)' % safename, use_gc=True, attributes=[ 'sAMAccountName', 'distinguishedName', 'sAMAccountType' ]) # This uses a generator, however we return a list for entry in entries: out.append(entry) return out
def validatePrivileges(self, uname, domainDumper): # Find the user's DN membersids = [] sidmapping = {} privs = { 'create': False, # Whether we can create users 'createIn': None, # Where we can create users 'escalateViaGroup': False, # Whether we can escalate via a group 'escalateGroup': None, # The group we can escalate via 'aclEscalate': False, # Whether we can escalate via ACL on the domain object 'aclEscalateIn': None # The object which ACL we can edit } self.client.search(domainDumper.root, '(sAMAccountName=%s)' % escape_filter_chars(uname), attributes=['objectSid', 'primaryGroupId']) user = self.client.entries[0] usersid = user['objectSid'].value sidmapping[usersid] = user.entry_dn membersids.append(usersid) # The groups the user is a member of self.client.search(domainDumper.root, '(member:1.2.840.113556.1.4.1941:=%s)' % escape_filter_chars(user.entry_dn), attributes=['name', 'objectSid']) LOG.debug('User is a member of: %s' % self.client.entries) for entry in self.client.entries: sidmapping[entry['objectSid'].value] = entry.entry_dn membersids.append(entry['objectSid'].value) # Also search by primarygroupid # First get domain SID self.client.search(domainDumper.root, '(objectClass=domain)', attributes=['objectSid']) domainsid = self.client.entries[0]['objectSid'].value gid = user['primaryGroupId'].value # Now search for this group by SID self.client.search(domainDumper.root, '(objectSid=%s-%d)' % (domainsid, gid), attributes=['name', 'objectSid', 'distinguishedName']) group = self.client.entries[0] LOG.debug('User is a member of: %s' % self.client.entries) # Add the group sid of the primary group to the list sidmapping[group['objectSid'].value] = group.entry_dn membersids.append(group['objectSid'].value) controls = security_descriptor_control(sdflags=0x05) # Query Owner and Dacl # Now we have all the SIDs applicable to this user, now enumerate the privileges of domains and OUs entries = self.client.extend.standard.paged_search(domainDumper.root, '(|(objectClass=domain)(objectClass=organizationalUnit))', attributes=['nTSecurityDescriptor', 'objectClass'], controls=controls, generator=True) self.checkSecurityDescriptors(entries, privs, membersids, sidmapping, domainDumper) # Also get the privileges on the default Users container entries = self.client.extend.standard.paged_search(domainDumper.root, '(&(cn=Users)(objectClass=container))', attributes=['nTSecurityDescriptor', 'objectClass'], controls=controls, generator=True) self.checkSecurityDescriptors(entries, privs, membersids, sidmapping, domainDumper) # Interesting groups we'd like to be a member of, in order of preference interestingGroups = [ '%s-%d' % (domainsid, 519), # Enterprise admins '%s-%d' % (domainsid, 512), # Domain admins 'S-1-5-32-544', # Built-in Administrators 'S-1-5-32-551', # Backup operators 'S-1-5-32-548', # Account operators ] privs['escalateViaGroup'] = False for group in interestingGroups: self.client.search(domainDumper.root, '(objectSid=%s)' % group, attributes=['nTSecurityDescriptor', 'objectClass']) groupdata = self.client.response self.checkSecurityDescriptors(groupdata, privs, membersids, sidmapping, domainDumper) if privs['escalateViaGroup']: # We have a result - exit the loop break return (usersid, privs)
def validatePrivileges(self, uname, domainDumper): # Find the user's DN membersids = [] sidmapping = {} privs = { 'create': False, # Whether we can create users 'createIn': None, # Where we can create users 'escalateViaGroup': False, # Whether we can escalate via a group 'escalateGroup': None, # The group we can escalate via 'aclEscalate': False, # Whether we can escalate via ACL on the domain object 'aclEscalateIn': None # The object which ACL we can edit } self.client.search(domainDumper.root, '(sAMAccountName=%s)' % escape_filter_chars(uname), attributes=['objectSid', 'primaryGroupId']) user = self.client.entries[0] usersid = user['objectSid'].value sidmapping[usersid] = user.entry_dn membersids.append(usersid) # The groups the user is a member of self.client.search(domainDumper.root, '(member:1.2.840.113556.1.4.1941:=%s)' % escape_filter_chars(user.entry_dn), attributes=['name', 'objectSid']) LOG.debug('User is a member of: %s' % self.client.entries) for entry in self.client.entries: sidmapping[entry['objectSid'].value] = entry.entry_dn membersids.append(entry['objectSid'].value) # Also search by primarygroupid # First get domain SID self.client.search(domainDumper.root, '(objectClass=domain)', attributes=['objectSid']) domainsid = self.client.entries[0]['objectSid'].value gid = user['primaryGroupId'].value # Now search for this group by SID self.client.search(domainDumper.root, '(objectSid=%s-%d)' % (domainsid, gid), attributes=['name', 'objectSid', 'distinguishedName']) group = self.client.entries[0] LOG.debug('User is a member of: %s' % self.client.entries) # Add the group sid of the primary group to the list sidmapping[group['objectSid'].value] = group.entry_dn membersids.append(group['objectSid'].value) controls = security_descriptor_control(sdflags=0x05) # Query Owner and Dacl # Now we have all the SIDs applicable to this user, now enumerate the privileges of domains and OUs entries = self.client.extend.standard.paged_search(domainDumper.root, '(|(objectClass=domain)(objectClass=organizationalUnit))', attributes=['nTSecurityDescriptor', 'objectClass'], controls=controls, generator=True) self.checkSecurityDescriptors(entries, privs, membersids, sidmapping, domainDumper) # Also get the privileges on the default Users container entries = self.client.extend.standard.paged_search(domainDumper.root, '(&(cn=Users)(objectClass=container))', attributes=['nTSecurityDescriptor', 'objectClass'], controls=controls, generator=True) self.checkSecurityDescriptors(entries, privs, membersids, sidmapping, domainDumper) # Interesting groups we'd like to be a member of, in order of preference interestingGroups = [ '%s-%d' % (domainsid, 519), # Enterprise admins '%s-%d' % (domainsid, 512), # Domain admins 'S-1-5-32-544', # Built-in Administrators 'S-1-5-32-551', # Backup operators 'S-1-5-32-548', # Account operators ] privs['escalateViaGroup'] = False for group in interestingGroups: self.client.search(domainDumper.root, '(objectSid=%s)' % group, attributes=['nTSecurityDescriptor', 'objectClass'], controls=controls) groupdata = self.client.response self.checkSecurityDescriptors(groupdata, privs, membersids, sidmapping, domainDumper) if privs['escalateViaGroup']: # We have a result - exit the loop break return (usersid, privs)
def do_get_user_groups(self, user_name): user_dn = self.get_dn(user_name) if not user_dn: raise Exception("User not found in LDAP: %s" % user_name) self.search('(member:%s:=%s)' % (LdapShell.LDAP_MATCHING_RULE_IN_CHAIN, escape_filter_chars(user_dn)))
def toggle_account_enable_disable(self, user_name, enable): UF_ACCOUNT_DISABLE = 2 self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(user_name), attributes=['objectSid', 'userAccountControl']) if len(self.client.entries) != 1: raise Exception("Error expected only one search result got %d results", len(self.client.entries)) user_dn = self.client.entries[0].entry_dn if not user_dn: raise Exception("User not found in LDAP: %s" % user_name) entry = self.client.entries[0] userAccountControl = entry["userAccountControl"].value print("Original userAccountControl: %d" % userAccountControl) if enable: userAccountControl = userAccountControl & ~UF_ACCOUNT_DISABLE else: userAccountControl = userAccountControl | UF_ACCOUNT_DISABLE self.client.modify(user_dn, {'userAccountControl':(ldap3.MODIFY_REPLACE, [userAccountControl])}) if self.client.result['result'] == 0: print("Updated userAccountControl attribute successfully") else: if self.client.result['result'] == 50: raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message']) elif self.client.result['result'] == 19: raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message']) else: raise Exception('The server returned an error: %s', self.client.result['message'])
def _maybe_escape_filter_chars(value): """Escape and return according to RFC04515 if type is string-like. Else, return the unchanged object. """ if isinstance(value, type(b'')) or isinstance(value, type(u'')): return escape_filter_chars(value) return value
def _maybe_escape_filter_chars(value): """Escape and return according to RFC04515 if type is string-like. Else, return the unchanged object. """ if isinstance(value, bytes) or isinstance(value, str): return escape_filter_chars(value) return value
def test_parse_search_filter_parenteses(self): f = parse_filter( '(cn=' + escape_filter_chars('Doe (Missing Inc)') + ')', None, test_auto_escape, test_auto_encode, test_check_names) self.assertEqual(f.elements[0].tag, MATCH_EQUAL) self.assertEqual(f.elements[0].assertion['attr'], 'cn') self.assertEqual(f.elements[0].assertion['value'], b'Doe \\28Missing Inc\\29')
def get_sid_info(self, sid): self.ldap_session.search(self.domain_dumper.root, '(objectSid=%s)' % escape_filter_chars(sid), attributes=['samaccountname']) try: dn = self.ldap_session.entries[0].entry_dn samname = self.ldap_session.entries[0]['samaccountname'] return dn, samname except IndexError: logging.error('SID not found in LDAP: %s' % sid) return False
def get_user_info(self, samname): self.ldap_session.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(samname), attributes=['objectSid']) try: dn = self.ldap_session.entries[0].entry_dn sid = format_sid(self.ldap_session.entries[0]['objectSid'].raw_values[0]) return dn, sid except IndexError: logging.error('User not found in LDAP: %s' % samname) return False
def get_dn(self, sam_name): if "," in sam_name: return sam_name try: self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(sam_name), attributes=['objectSid']) return self.client.entries[0].entry_dn except IndexError: return None
def getUserInfo(self, domainDumper, samname): entries = self.client.search(domainDumper.root, '(sAMAccountName=%s)' % escape_filter_chars(samname), attributes=['objectSid']) try: dn = self.client.entries[0].entry_dn sid = self.client.entries[0]['objectSid'] return (dn, sid) except IndexError: LOG.error('User not found in LDAP: %s' % samname) return False
def scrub_dict(source, remove_empty: bool = False): target = {} for key in source: value = escape_filter_chars(source[key]) if remove_empty and (value is None or value == 'None' or len(value) == 0): continue target[key] = value return target
def do_get_group_users(self, group_name): group_dn = self.get_dn(group_name) if not group_dn: raise Exception("Group not found in LDAP: %s" % group_name) self.search( '(memberof:%s:=%s)' % (LdapShell.LDAP_MATCHING_RULE_IN_CHAIN, escape_filter_chars(group_dn)), "sAMAccountName", "name")
def search_services(attributes=None, **params): assert isinstance(g.openldap_session, OpenLDAPSession) condition_list = ['(objectClass=*)', '(objectClass=authorizedServiceObject)'] if attributes is None: attributes = 'cn', 'authorizedService' if 'cn' in params: condition_list.append('(cn=%s)' % escape_filter_chars(params['cn'])) if 'cnlike' in params: condition_list.append('(cn=*%s*)' % escape_filter_chars(params['cnlike'])) if 'cnin' in params: _cns = [] for cn in params['cnin']: _cns.append('(cn=%s)' % escape_filter_chars(cn)) condition_list.append('(|%s)' % ''.join(_cns)) condition_str = '(&' + ''.join(condition_list) + ')' return g.openldap_session.search_services(condition_str, attributes)
def type_of_host(hostname): """Returns the type of a host as specified in LDAP. >>> type_of_host('eruption') 'desktop' >>> type_of_host('supernova') 'server' """ hosts = hosts_by_filter('(cn={})'.format(escape_filter_chars(hostname))) return hosts[0]['type'] if hosts else None
def __call__(self, form, field): try: user = User.get(form.username.data) if user and user.mail == field.data: return except AttributeError: pass from ldap3.utils.conv import escape_filter_chars if len(User.query('mail: {}'.format(escape_filter_chars(field.data)))) != 0: raise ValidationError('E-Mail address already in use')
def authenticate(self, request, username=None, password=None): ldap_conn = connections['ldap'] user = None username = conv.escape_filter_chars(username, encoding=None) lu = LdapAcademiaUser.objects.filter(uid=username).first() if not lu: logger.info("--- LDAP BIND failed for {} ---".format(username)) return None # check if username exists and if it is active try: ldap_conn.connect() ldap_conn.connection.bind_s(lu.distinguished_name(), password) ldap_conn.connection.unbind_s() except Exception: logger.info( "--- LDAP {} seems to be unable to Auth ---".format(username)) return None # if account beign unlocked this will be always false if not lu.is_active(): logger.info( "--- LDAP {} seems to be disabled ---".format(username)) return None # username would be like an EPPN username = lu.uid try: user = get_user_model().objects.get(username=username) # update attrs: user.email = lu.mail[0] user.first_name = lu.cn user.last_name = lu.sn user.origin = 'ldap_peoples' user.save() except Exception: user = get_user_model().objects.create(username=username, email=lu.mail[0], first_name=lu.cn, last_name=lu.sn, origin='ldap_peoples') # TODO: Create a middleware for this # disconnect already created session, only a session per user is allowed # get all the active sessions # if not settings.MULTIPLE_USER_AUTH_SESSIONS: # for session in Session.objects.all(): # try: # if int(session.get_decoded()['_auth_user_id']) == user.pk: # session.delete() # except (KeyError, TypeError, ValueError): # pass return user
def normalize_username(self, username): """ Normalize username for ldap query modifications: - format to lowercase - escape filter characters (ldap3) """ username = username.lower() username = escape_filter_chars(username) return username
def _operator_predicate(self, name, op, value): name = self.mappings.get(name, name) if name is None: return if name is True: return '(objectClass=*)' if op == '=': return '({0}={1})'.format(name, escape_filter_chars(str(value))) if op == '~': return '({0}={1})'.format(name, str(value)) if op == '!=': return '(!({0}={1}))'.format(name, escape_filter_chars(str(value))) if op == 'in': return self._joint_predicate('or', [ (name, '=', str(v)) for v in value ])
def escape_userdn_if_needed(self, userdn): if self.escape_userdn: return escape_filter_chars(userdn) else: return userdn
def test_search_exact_match_with_escape_chars_backslash_in_filter(self): self.delete_at_teardown.append(add_user(self.connection, testcase_id, 'sea-15', attributes={'givenName': testcase_id + 'givenname\\-15'})) result = self.connection.search(search_base=test_base, search_filter='(givenname=' + testcase_id + '*' + escape_filter_chars('\\') + '*)', attributes=[test_name_attr, 'sn']) if not self.connection.strategy.sync: response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertEqual(result['description'], 'success') self.assertEqual(len(response), 1) if test_server_type == 'AD': self.assertEqual(response[0]['attributes'][test_name_attr], testcase_id + 'sea-15') else: self.assertEqual(response[0]['attributes'][test_name_attr][0], testcase_id + 'sea-15')
def authenticate(self, handler, data): username = data['username'] password = data['password'] # Get LDAP Connection def getConnection(userdn, username, password): server = ldap3.Server( self.server_address, port=self.server_port, use_ssl=self.use_ssl ) self.log.debug('Attempting to bind {username} with {userdn}'.format( username=username, userdn=userdn )) conn = ldap3.Connection( server, user=self.escape_userdn_if_needed(userdn), password=password, auto_bind=self.use_ssl and ldap3.AUTO_BIND_TLS_BEFORE_BIND or ldap3.AUTO_BIND_NO_TLS, ) return conn # Protect against invalid usernames as well as LDAP injection attacks if not re.match(self.valid_username_regex, username): self.log.warn('username:%s Illegal characters in username, must match regex %s', username, self.valid_username_regex) return None # No empty passwords! if password is None or password.strip() == '': self.log.warn('username:%s Login denied for blank password', username) return None isBound = False self.log.debug("TYPE= '%s'",isinstance(self.bind_dn_template, list)) resolved_username, userdn = self.resolve_username_and_userdn(username) if resolved_username is None: return None if self.lookup_dn: if str(self.lookup_dn_user_dn_attribute).upper() == 'CN': # Only escape commas if the lookup attribute is CN resolved_username = re.subn(r"([^\\]),", r"\1\,", resolved_username)[0] bind_dn_template = self.bind_dn_template if bind_dn_template == []: isBound = True conn = getConnection(userdn, username, password) else: if isinstance(bind_dn_template, str): # bind_dn_template should be of type List[str] bind_dn_template = [bind_dn_template] for dn in bind_dn_template: userdn = dn.format(username=resolved_username) msg = 'Status of user bind {username} with {userdn} : {isBound}' try: conn = getConnection(userdn, username, password) except ldap3.core.exceptions.LDAPBindError as exc: isBound = False msg += '\n{exc_type}: {exc_msg}'.format( exc_type=exc.__class__.__name__, exc_msg=exc.args[0] if exc.args else '' ) else: isBound = conn.bind() msg = msg.format( username=username, userdn=userdn, isBound=isBound ) self.log.debug(msg) if isBound: break if isBound: if self.allowed_groups: self.log.debug('username:%s Using dn %s', username, userdn) for group in self.allowed_groups: groupfilter = ( '(|' '(member={userdn})' '(uniqueMember={userdn})' '(memberUid={uid})' ')' ).format(userdn=escape_filter_chars(userdn), uid=escape_filter_chars(username)) groupattributes = ['member', 'uniqueMember', 'memberUid'] if conn.search( group, search_scope=ldap3.BASE, search_filter=groupfilter, attributes=groupattributes ): return username # If we reach here, then none of the groups matched self.log.warn('username:%s User not in any of the allowed groups', username) return None elif self.search_filter: conn.search( search_base=self.user_search_base, search_scope=ldap3.SUBTREE, search_filter=self.search_filter.format(userattr=self.user_attribute,username=username), attributes=self.attributes ) if len(conn.response) == 0: self.log.warn('User with {userattr}={username} not found in directory'.format( userattr=self.user_attribute, username=username)) return None elif len(conn.response) > 1: self.log.warn('User with {userattr}={username} found more than {len}-fold in directory'.format( userattr=self.user_attribute, username=username, len=len(conn.response))) return None return username else: return username else: self.log.warn('Invalid password for user {username}'.format( username=username, )) return None
def test_parse_search_filter_parenteses(self): f = parse_filter('(cn=' + escape_filter_chars('Doe (Missing Inc)') + ')', None, test_auto_escape, test_auto_encode, test_validator, test_check_names) self.assertEqual(f.elements[0].tag, MATCH_EQUAL) self.assertEqual(f.elements[0].assertion['attr'], 'cn') self.assertEqual(f.elements[0].assertion['value'], b'Doe \\28Missing Inc\\29')
def do_ldap_query(q, admin=False): c = LDAPConnection() result_dns = [] q = escape_filter_chars(q) # Allow wildcards q = q.replace(escape_filter_chars('*'), '*') # If only a digit, search for student ID and user ID if q.isdigit(): logger.debug("Digit search: {}".format(q)) if USE_SID_LDAP: query = ("(&(|(tjhsstStudentId={0})" "(iodineUidNumber={0})" ")(|(objectClass=tjhsstStudent)(objectClass=tjhsstTeacher)))").format(q) logger.debug("Running LDAP query: {}".format(query)) res = c.search(settings.USER_DN, query, None) for row in res: dn = row["dn"] result_dns.append(dn) else: sid_users = User.objects.filter(_student_id=q) uid_users = User.objects.filter(id=q) for u in sid_users: result_dns.append(u.dn) for u in uid_users: result_dns.append(u.dn) logger.debug("Running local DB query for UID and SID") elif ":" in q: logger.debug("Advanced search") # A mapping between search keys and LDAP entires map_attrs = { "firstname": ( "givenname", "nickname",), "first": ( "givenname", "nickname",), "lastname": ("sn",), "last": ("sn",), "nick": ("nickname",), "nickname": ("nickname",), "name": ( "sn", "mname", "givenname", "nickname",), "city": ("l",), "town": ("l",), "middlename": ("mname",), "middle": ("mname",), "phone": ( "homephone", "mobile",), "homephone": ("homephone",), "cell": ("mobile",), "address": ("street",), "zip": ("postalcode",), "grade": ("graduationYear",), "gradyear": ("graduationYear",), "email": ("mail",), "studentid": ("tjhsstStudentId",), "sex": ("sex",), "gender": ("sex",), "id": ("iodineUidNumber",), "username": ("iodineUid",), "counselor": ("counselor",), "type": ("objectClass",) } inner = "" parts = q.split(" ") # split each word for p in parts: # Check for less than/greater than, and replace = sep = "=" if ":" in p: cat, val = p.split(":") elif "=" in p: cat, val = p.split("=") elif "<" in p: cat, val = p.split("<") sep = "<=" elif ">" in p: cat, val = p.split(">") sep = ">=" else: logger.debug("Advanced fallback: {}".format(p)) # Fall back on regular searching (there's no key) # Wildcards are already implied at the start and end if p.endswith("*"): p = p[:-1] if p.startswith("*"): p = p[1:] exact = False if p.startswith('"') and p.endswith('"'): exact = True p = p[1:-1] if len(p) == 0: continue if exact: # No implied wildcard inner += (("(|(givenName={0})" "(sn={0})" "(iodineUid={0})") + ("(mname={0})" if admin else "") + ("(nickname={0})" ")")).format(p) else: # Search firstname, lastname, uid, nickname (+ middlename if admin) with # implied wildcard at beginning and end of the search # string inner += (("(|(givenName=*{0})" "(givenName={0}*)" "(sn=*{0})" "(sn={0}*)" "(iodineUid=*{0})" "(iodineUid={0}*)") + ("(mname=*{0})" "(mname={0}*)" if admin else "") + ("(nickname=*{0})" "(nickname={0}*)" ")")).format(p) continue # skip rest of processing logger.debug("Advanced exact: {}".format(p)) if val.startswith('"') and val.endswith('"'): # Already exact val = val[1:-1] cat = cat.lower() val = val.lower() # fix grade, because LDAP only stores graduation year if cat == "grade" and val.isdigit(): val = "{}".format(Grade.year_from_grade(int(val))) elif cat == "grade" and val == "staff": cat = "type" val = "teacher" elif cat == "grade" and val == "student": cat = "type" val = "student" if cat == "type" and val == "teacher": val = "tjhsstTeacher" elif cat == "type" and val == "student": val = "tjhsstStudent" # replace sex:male with sex:m and sex:female with sex:f if cat == "sex" or cat == "gender": val = val[:1] # if an invalid key, ignore if cat not in map_attrs: continue attrs = map_attrs[cat] inner += "(|" # for each of the possible LDAP fields, add to the search query for attr in attrs: inner += "({}{}{})".format(attr, sep, val) inner += ")" query = "(&{}(|(objectClass=tjhsstStudent)(objectClass=tjhsstTeacher)))".format(inner) logger.debug("Running LDAP query: {}".format(query)) res = c.search(settings.USER_DN, query, None) for row in res: dn = row["dn"] result_dns.append(dn) else: logger.debug("Simple search") # Non-advanced search; no ":" parts = q.split(" ") # split on each word i = 0 for p in parts: exact = False logger.debug(p) if p.startswith('"') and p.endswith('"'): exact = True p = p[1:-1] if exact: logger.debug("Simple exact: {}".format(p)) # No implied wildcard query = (("(&(|(givenName={0})" "(sn={0})" "(iodineUid={0})") + ("(mname={0})" if admin else "") + ("(nickname={0})" ")(|(objectClass=tjhsstStudent)(objectClass=tjhsstTeacher)))")).format(p) else: logger.debug("Simple wildcard: {}".format(p)) if p.endswith("*"): p = p[:-1] if p.startswith("*"): p = p[1:] # Search for first, last, middle, nickname uid, with implied # wildcard at beginning and end query = (("(&(|(givenName=*{0})" "(givenName={0}*)" "(sn=*{0})" "(sn={0}*)" "(iodineUid=*{0})" "(iodineUid={0}*)") + ("(mname=*{0})" "(mname={0}*)" if admin else "") + ("(nickname=*{0})" "(nickname={0}*)" ")(|(objectClass=tjhsstStudent)(objectClass=tjhsstTeacher)))")).format(p) logger.debug("Running LDAP query: {}".format(query)) res = c.search(settings.USER_DN, query, None) new_dns = [] # if multiple words, delete those that weren't in previous searches for row in res: dn = row["dn"] if i == 0: new_dns.append(dn) elif dn in result_dns: new_dns.append(dn) result_dns = new_dns i += 1 # loop through the DNs saved and get actual user objects users = [] for dn in result_dns: user = User.get_user(dn=dn) if user.is_active: users.append(user) return users
def get_user_info(username, connection=None): results = ldap_search(lsettings.USER_SEARCH_SCOPE, lsettings.USER_SEARCH_FILTER % dict(user=escape_filter_chars(username)), connection=connection) if not results: raise Exception(str(_('User not found.'))) if len(results) > 1: logger.warning('Multiple results found in LDAP server for search:\n%s\n%s\n%s', lsettings.USER_SEARCH_SCOPE, 'ldap3.SUBTREE', lsettings.USER_SEARCH_FILTER % dict(user=username)) return results[0]['dn'], results[0]['attributes']