Ejemplo n.º 1
0
def ldap_connect(settings, use_cache=True):
    """Establishes an LDAP connection.

    Establishes a connection to the LDAP server from the `uri` in the
    ``settings``.

    To establish a connection, the settings must be specified:
     - ``uri``: valid URI which points to a LDAP server,
     - ``bind_dn``: `dn` used to initially bind every LDAP connection
     - ``bind_password``" password used for the initial bind
     - ``tls``: ``True`` if the connection should use TLS encryption
     - ``starttls``: ``True`` to negotiate TLS with the server

    `Note`: ``starttls`` is ignored if the URI uses LDAPS and ``tls`` is
    set to ``True``.

    This function re-uses an existing LDAP connection if there is one
    available in the application context, unless caching is disabled.

    :param settings: dict -- The settings for a LDAP provider.
    :param use_cache: bool -- If the connection should be cached.
    :return: The ldap connection.
    """

    if use_cache:
        cache = _get_ldap_cache()
        cache_key = frozenset((k, hash(v)) for k, v in iteritems(settings) if k in conn_keys)
        conn = cache.get(cache_key)
        if conn is not None:
            return conn

    uri_info = urlparse(settings['uri'])
    use_ldaps = uri_info.scheme == 'ldaps'
    credentials = (settings['bind_dn'], settings['bind_password'])
    ldap_connection = ReconnectLDAPObject(settings['uri'])
    ldap_connection.protocol_version = ldap.VERSION3
    ldap_connection.set_option(ldap.OPT_REFERRALS, 0)
    ldap_connection.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_DEMAND if use_ldaps else ldap.OPT_X_TLS_NEVER)
    if settings['cert_file']:
        ldap_connection.set_option(ldap.OPT_X_TLS_CACERTFILE, settings['cert_file'])
    ldap_connection.set_option(ldap.OPT_X_TLS_REQUIRE_CERT,
                               ldap.OPT_X_TLS_DEMAND if settings['verify_cert'] else ldap.OPT_X_TLS_ALLOW)
    # force the creation of a new TLS context. This must be the last TLS option.
    # see: http://stackoverflow.com/a/27713355/298479
    ldap_connection.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
    if use_ldaps and settings['starttls']:
        warn("Unable to start TLS, LDAP connection already secured over SSL (LDAPS)")
    elif settings['starttls']:
        ldap_connection.start_tls_s()
    # TODO: allow anonymous bind
    ldap_connection.simple_bind_s(*credentials)
    if use_cache:
        cache[cache_key] = ldap_connection
    return ldap_connection
Ejemplo n.º 2
0
def ldap_connect(settings, use_cache=True):
    """Establishes an LDAP connection.

    Establishes a connection to the LDAP server from the `uri` in the
    ``settings``.

    To establish a connection, the settings must be specified:
     - ``uri``: valid URI which points to a LDAP server,
     - ``bind_dn``: `dn` used to initially bind every LDAP connection
     - ``bind_password``" password used for the initial bind
     - ``tls``: ``True`` if the connection should use TLS encryption
     - ``starttls``: ``True`` to negotiate TLS with the server

    `Note`: ``starttls`` is ignored if the URI uses LDAPS and ``tls`` is
    set to ``True``.

    This function re-uses an existing LDAP connection if there is one
    available in the application context, unless caching is disabled.

    :param settings: dict -- The settings for a LDAP provider.
    :param use_cache: bool -- If the connection should be cached.
    :return: The ldap connection.
    """

    if use_cache:
        cache = _get_ldap_cache()
        cache_key = frozenset(
            (k, hash(v)) for k, v in iteritems(settings) if k in conn_keys)
        conn = cache.get(cache_key)
        if conn is not None:
            return conn

    uri_info = urlparse(settings['uri'])
    use_ldaps = uri_info.scheme == 'ldaps'
    credentials = (settings['bind_dn'], settings['bind_password'])
    ldap_connection = ReconnectLDAPObject(settings['uri'])
    ldap_connection.protocol_version = ldap.VERSION3
    ldap_connection.set_option(ldap.OPT_REFERRALS, 0)
    ldap_connection.set_option(
        ldap.OPT_X_TLS,
        ldap.OPT_X_TLS_DEMAND if use_ldaps else ldap.OPT_X_TLS_NEVER)
    ldap_connection.set_option(
        ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND
        if settings['verify_cert'] else ldap.OPT_X_TLS_ALLOW)
    # force the creation of a new TLS context. This must be the last TLS option.
    # see: http://stackoverflow.com/a/27713355/298479
    ldap_connection.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
    if use_ldaps and settings['starttls']:
        warn(
            "Unable to start TLS, LDAP connection already secured over SSL (LDAPS)"
        )
    elif settings['starttls']:
        ldap_connection.start_tls_s()
    # TODO: allow anonymous bind
    ldap_connection.simple_bind_s(*credentials)
    if use_cache:
        cache[cache_key] = ldap_connection
    return ldap_connection
Ejemplo n.º 3
0
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__ = []
Ejemplo n.º 4
0
class DatabaseWrapper(BaseDatabaseWrapper):
    vendor = 'ldap'

    # NOTE: These are copied from the mysql DatabaseWrapper
    operators = {
        'exact': '= %s',
        'iexact': 'LIKE %s',
        'contains': 'LIKE BINARY %s',
        'icontains': 'LIKE %s',
        'regex': 'REGEXP BINARY %s',
        'iregex': 'REGEXP %s',
        'gt': '> %s',
        'gte': '>= %s',
        'lt': '< %s',
        'lte': '<= %s',
        'startswith': 'LIKE BINARY %s',
        'endswith': 'LIKE BINARY %s',
        'istartswith': 'LIKE %s',
        'iendswith': 'LIKE %s',
    }

    def __init__(self, *args, **kwargs):
        super(DatabaseWrapper, self).__init__(*args, **kwargs)

        self.charset = "utf-8"
        self.creation = DatabaseCreation(self)
        self.features = DatabaseFeatures(self)
        if django.VERSION > (1, 4):
            self.ops = DatabaseOperations(self)
        else:
            self.ops = DatabaseOperations()
        self.settings_dict['SUPPORTS_TRANSACTIONS'] = True
        self.autocommit = True

    def close(self):
        if hasattr(self, 'validate_thread_sharing'):
            # django >= 1.4
            self.validate_thread_sharing()
        if self.connection is not None:
            self.connection.unbind_s()
            self.connection = None

    def ensure_connection(self):
        if self.connection is None:
            #self.connection = ldap.initialize(self.settings_dict['NAME'])
            self.connection = ReconnectLDAPObject(self.settings_dict['NAME'])

            options = self.settings_dict.get('CONNECTION_OPTIONS', {})
            for opt, value in options.items():
                self.connection.set_option(opt, value)

            if self.settings_dict.get('TLS', False):
                self.connection.start_tls_s()

            self.connection.simple_bind_s(
                self.settings_dict['USER'],
                self.settings_dict['PASSWORD'])

    def _commit(self):
        pass

    def _cursor(self):
        self.ensure_connection()
        return DatabaseCursor(self.connection)

    def _rollback(self):
        pass

    def _set_autocommit(self, autocommit):
        pass

    def add_s(self, dn, modlist):
        cursor = self._cursor()
        return cursor.connection.add_s(dn.encode(self.charset), modlist)

    def delete_s(self, dn):
        cursor = self._cursor()
        return cursor.connection.delete_s(dn.encode(self.charset))

    def modify_s(self, dn, modlist):
        cursor = self._cursor()
        return cursor.connection.modify_s(dn.encode(self.charset), modlist)

    def rename_s(self, dn, newrdn):
        cursor = self._cursor()
        return cursor.connection.rename_s(dn.encode(self.charset),
                                          newrdn.encode(self.charset))

    def search_s(self, base, scope, filterstr='(objectClass=*)',
                 attrlist=None):
        cursor = self._cursor()
        results = cursor.connection.search_s(base, scope,
                                             filterstr.encode(self.charset),
                                             attrlist)
        output = []
        for dn, attrs in results:
            # skip referrals
            if dn is not None:
                output.append((dn.decode(self.charset), attrs))
        return output
Ejemplo n.º 5
0
class LDAPLookup:
    """ Wraps ldap library query

    Args:
        ldap_url: LDAP server in the form of 'ldap://ldaphost'
        ldap_base: LDAP base for search ('ou=users,dc=department,dc=org')
        ldap_retry_max: LDAP number of reconnect attempts
        ldap_retry_delay: LDAP seconds between reconnect attempts
    """

    DEFAULT_QUERY_FIELDS: List[str] = ['uid']
    DEFAULT_RETURN_FIELDS: List[str] = ['uid', 'cn', 'mail']

    def __init__(self,
                 ldap_url: str,
                 ldap_base: str,
                 ldap_retry_max: int = 3,
                 ldap_retry_delay: float = 5.0):
        self.ldap_url = ldap_url
        self.ldap_base = ldap_base

        self.ldap_retry_max = ldap_retry_max
        self.ldap_retry_delay = ldap_retry_delay

        self._ldap_client = None  # lazy client init

    @property
    def ldap_client(self):
        if not self._ldap_client:
            self._ldap_client = ReconnectLDAPObject(
                self.ldap_url,
                retry_max=self.ldap_retry_max,
                retry_delay=self.ldap_retry_delay)

            self._ldap_client.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
            self._ldap_client.set_option(ldap.OPT_REFERRALS, 0)

        return self._ldap_client

    def query(
        self,
        query: str,
        query_fields: List[str] = None,
        return_fields: List[str] = None,
        raise_exception: bool = False,
    ) -> List[dict]:
        """ Perform LDAP query

        Args:
            query: String to search
            query_fields: Which LDAP fields to search in
            return_fields: What LDAP fields to return
            raise_exception: If True - raise exception if no results

        Returns:
            List if dicts with LDAP results

            Example:

            [
                {'uid': 'us1', 'cn': 'user 1', 'mail': '*****@*****.**'},

                {'uid': 'us2', 'cn': 'user 2', 'mail': '*****@*****.**'}

            ]

        Raises:
            LDAPQueryNotFoundError: No result while raise_exception True
        """

        query = query.rstrip('*')

        if not query_fields:
            query_fields = self.DEFAULT_QUERY_FIELDS

        if not return_fields:
            return_fields = self.DEFAULT_RETURN_FIELDS

        if len(query_fields) == 1:
            query_string = f'{query_fields[0]}={query}'
        else:
            # Example: (|(cn=query*)(sn=query*)(mail=query*))
            field_queries = [
                f'({field}={query}*)' for field in query_fields if field
            ]
            query_string = '(|%s)' % ''.join(field_queries)

        res = self.ldap_client.search_s(self.ldap_base, ldap.SCOPE_SUBTREE,
                                        query_string, return_fields)

        if raise_exception and not res:
            raise LDAPQueryNotFoundError(f'Query not found in LDAP: {query}')

        # Extract first values, convert from bytes
        return [{k: v[0].decode('utf-8')
                 for k, v in record[1].items()} for record in res]