class CSHLDAP: __domain__ = "csh.rit.edu" def __init__(self, bind_dn, bind_pw, batch_mods=False, sasl=False, ro=False): """Handler for bindings to CSH LDAP. Keyword arguments: batch_mods -- whether or not to batch LDAP writes (default False) sasl -- whether or not to bypass bind_dn and bind_pw and use SASL bind ro -- whether or not CSH LDAP is in read only mode (default False) """ if ro: print("########################################\n" "# #\n" "# CSH LDAP IS IN READ ONLY MODE #\n" "# #\n" "########################################") ldap_srvs = srvlookup.lookup("ldap", "tcp", self.__domain__) ldap_uris = "" for uri in ldap_srvs: ldap_uris += "ldaps://"+uri.hostname+"," self.__con__ = ReconnectLDAPObject(ldap_uris) # Allow connections with self-signed certs self.__con__.set_option(self.__con__.OPT_X_TLS_REQUIRE_CERT, self.__con__.OPT_X_TLS_ALLOW) if sasl: self.__con__.sasl_non_interactive_bind_s('') else: self.__con__.simple_bind_s(bind_dn, bind_pw) self.__mod_queue__ = {} self.__pending_mod_dn__ = [] self.__batch_mods__ = batch_mods self.__ro__ = ro def get_member(self, val, uid=False): """Get a CSHMember object. Arguments: val -- the uuid (or uid) of the member Keyword arguments: uid -- whether or not val is a uid (default False) """ return CSHMember(self, val, uid) def get_member_ibutton(self, val): """Get a CSHMember object. Arguments: val -- the iButton ID of the member Returns: None if the iButton supplied does not correspond to a CSH Member """ members = self.__con__.search_s( CSHMember.__ldap_user_ou__, ldap.SCOPE_SUBTREE, "(ibutton=%s)" % val, ['ipaUniqueID']) if members: return CSHMember( self, members[0][1]['ipaUniqueID'][0].decode('utf-8'), False) return None def get_member_slackuid(self, slack): """Get a CSHMember object. Arguments: slack -- the Slack UID of the member Returns: None if the Slack UID provided does not correspond to a CSH Member """ members = self.__con__.search_s( CSHMember.__ldap_user_ou__, ldap.SCOPE_SUBTREE, "(slackuid=%s)" % slack, ['ipaUniqueID']) if members: return CSHMember( self, members[0][1]['ipaUniqueID'][0].decode('utf-8'), False) return None def get_group(self, val): """Get a CSHGroup object. Arguments: val -- the cn of the group """ return CSHGroup(self, val) def get_con(self): """Get the PyLDAP Connection""" return self.__con__ def get_directorship_heads(self, val): """Get the head of a directorship Arguments: val -- the cn of the directorship """ __ldap_group_ou__ = "cn=groups,cn=accounts,dc=csh,dc=rit,dc=edu" res = self.__con__.search_s( __ldap_group_ou__, ldap.SCOPE_SUBTREE, "(cn=eboard-%s)" % val, ['member']) ret = [] for member in res[0][1]['member']: try: ret.append(member.decode('utf-8')) except UnicodeDecodeError: ret.append(member) except KeyError: continue return [CSHMember(self, dn.split('=')[1].split(',')[0], True) for dn in ret] def enqueue_mod(self, dn, mod): """Enqueue a LDAP modification. Arguments: dn -- the distinguished name of the object to modify mod -- an ldap modfication entry to enqueue """ # mark for update if dn not in self.__pending_mod_dn__: self.__pending_mod_dn__.append(dn) self.__mod_queue__[dn] = [] self.__mod_queue__[dn].append(mod) def flush_mod(self): """Flush all pending LDAP modifications.""" for dn in self.__pending_mod_dn__: try: if self.__ro__: for mod in self.__mod_queue__[dn]: if mod[0] == ldap.MOD_DELETE: mod_str = "DELETE" elif mod[0] == ldap.MOD_ADD: mod_str = "ADD" else: mod_str = "REPLACE" print("{} VALUE {} = {} FOR {}".format(mod_str, mod[1], mod[2], dn)) else: self.__con__.modify_s(dn, self.__mod_queue__[dn]) except ldap.TYPE_OR_VALUE_EXISTS: print("Error! Conflicting Batch Modification: %s" % str(self.__mod_queue__[dn])) continue except ldap.NO_SUCH_ATTRIBUTE: print("Error! Conflicting Batch Modification: %s" % str(self.__mod_queue__[dn])) continue self.__mod_queue__[dn] = None self.__pending_mod_dn__ = []
class LDAPUser(object): EPOCH_DATE = datetime.datetime.fromtimestamp(0) LOGIN_SHELL = "/bin/bash" HOME_BASE_DIRECTORY = "/home" GROUP_ID_NUMBER = None CREATOR = None VERIFY_CREATOR = True password_compile = re.compile("^{(%s)}(\S+)$" % "|".join(SUPPORT_METHOD)) def __init__(self, ldap_uri, base_dn, admin_user, admin_password, home_base_directory=None, group_id=None): self.ldap_uri = ldap_uri self.ldap_com = ReconnectLDAPObject(ldap_uri, retry_max=10, retry_delay=3) # self.ldap_com = ldap.initialize(ldap_uri) self.ldap_base_dn = base_dn if home_base_directory is not None: self.HOME_BASE_DIRECTORY = home_base_directory if group_id is not None: self.GROUP_ID_NUMBER = "%s" % group_id self.ldap_admin = "cn=%s,%s" % (admin_user, self.ldap_base_dn) self.people_base_cn = "ou=People,%s" % self.ldap_base_dn self.ldap_com.bind_s(self.ldap_admin, admin_password) def __get_next_uid_number(self): filter_str = "uid=*" attributes = ["uidNumber", "gidNumber"] items = self.ldap_com.search_s(self.people_base_cn, LDAP_SCOPE_SUBTREE, filter_str, attributes) max_number = 1000 for item in items: if "uidNumber" in item[1]: u_number = int(item[1]["uidNumber"][0]) if max_number < u_number: max_number = u_number return max_number + 1 def delete_user(self, user_name): if self.exist(user_name) is None: return True dn = "uid=%s,%s" % (user_name, self.people_base_cn) return self.ldap_com.delete_s(dn) def add_user(self, user_name, uid_number=None, gid_number=None, home_directory=None): dn = "uid=%s,%s" % (user_name, self.people_base_cn) attributes = dict() for key in ("uid", "cn", "sn"): attributes[key] = encode(user_name) attributes["objectClass"] = [ "top", "person", "inetOrgPerson", "posixAccount", "organizationalPerson", "shadowAccount" ] if uid_number is None: attributes["uidNumber"] = encode(self.__get_next_uid_number()) else: attributes["uidNumber"] = encode(uid_number) if gid_number is None: attributes["gidNumber"] = encode(self.GROUP_ID_NUMBER) else: attributes["gidNumber"] = encode(gid_number) if attributes["gidNumber"] is None: raise LDAPError("please set gidNumber") if home_directory is None: home_directory = os.path.join(self.HOME_BASE_DIRECTORY, user_name) attributes["homeDirectory"] = encode(home_directory) attributes["loginShell"] = encode(self.LOGIN_SHELL) if self.CREATOR is not None: attributes["givenName"] = encode(self.CREATOR) mod_list = ldap.modlist.addModlist(attributes) self.ldap_com.add_s(dn, mod_list) def expire_user(self, user_name, shadow_expire=None): user_entry = self.exist(user_name) if user_entry is None: return False old_attributes = user_entry[1] attributes = dict(**old_attributes) if shadow_expire is None: attributes["shadowExpire"] = [ "%s" % (datetime.datetime.now() - self.EPOCH_DATE).days ] else: attributes["shadowExpire"] = "" mod_list = ldap.modlist.modifyModlist(old_attributes, attributes) return self.ldap_com.modify_s(user_entry[0], mod_list) def block_user(self, user_name): return self.expire_user(user_name) def unlock_user(self, user_name): user_entry = self.exist(user_name, "pwdAccountLockedTime", "shadowExpire") if user_entry is None: return False old_attributes = user_entry[1] attributes = dict(**old_attributes) if "pwdAccountLockedTime" in attributes: attributes["pwdAccountLockedTime"] = "" if "shadowExpire" in attributes: attributes["shadowExpire"] = "" mod_list = ldap.modlist.modifyModlist(old_attributes, attributes) return self.ldap_com.modify_s(user_entry[0], mod_list) def _verify_creator(self, user_entry): if self.CREATOR is None or self.VERIFY_CREATOR is False: return True if "givenName" not in user_entry: return False return self.CREATOR in user_entry["givenName"] def exist(self, user_name, *attributes): l_attributes = set(attributes) sr = self.ldap_com.search_s(self.ldap_base_dn, LDAP_SCOPE_SUBTREE, "uid=%s" % user_name, l_attributes) if len(sr) <= 0: return None return sr[0] def verify_password(self, password, ldap_password): match_r = self.password_compile.match(ldap_password) if match_r is not None: method = match_r.groups()[0] cipher_text = match_r.groups()[1] return verify(method, password, cipher_text) return password == ldap_password def login(self, user_name, password): user_entry = self.exist(user_name) if user_entry is None: return False attr = user_entry[1] if "userPassword" not in attr: return False ldap_password = attr["userPassword"][0] return self.verify_password(password, ldap_password) def login2(self, user_name, password): user_entry = self.exist(user_name) if user_entry is None: return False attr = user_entry[1] if "userPassword" not in attr: return False try: self.ldap_com.simple_bind_s(user_entry[0], password) return True except ldap.INVALID_CREDENTIALS: return False def set_password(self, user_name, new_password): user_entry = self.exist(user_name) if user_entry is None: return False if self._verify_creator(user_entry) is False: return False pr = self.ldap_com.passwd_s(user_entry[0], None, new_password) if pr[0] is None and pr[1] is None: return True return False