From 7c8ddf17d46e271cd17723e7a222d767df6435a4 Mon Sep 17 00:00:00 2001
From: Jose Manuel Lopez Lujan <jm.lopez@utoronto.ca>
Date: Tue, 12 May 2020 15:29:34 -0400
Subject: [PATCH] Adds get_groups method to provide an interface for listing
 all available groups in the ldap directory based on the filter defined in
 LDAP_GROUPS_OBJECT_FILTER

The get_groups method has two arguments fields and dn_only
to customize the result. i.e.
get_groups(fields=['cn', 'description'])
provide a list of dict with included attributes. if only
get_groups(dn_only=True) is provided, a list of available
groups dn is returned. If no users in directory, an empty
list is returned.
---
 docs/index.rst                   |  2 ++
 examples/basic_auth/app_oldap.py | 10 ++++----
 examples/groups/app_oldap.py     |  8 ++++---
 flask_simpleldap/__init__.py     | 41 ++++++++++++++++++++++++++++++--
 4 files changed, 52 insertions(+), 9 deletions(-)

diff --git a/docs/index.rst b/docs/index.rst
index ab72749..2ab3e2c 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -80,6 +80,8 @@ directives:
                                    Default: '(&(objectclass=Person)(userPrincipalName=%s))'
 ``LDAP_USER_GROUPS_FIELD``         The field to return when searching for a user's
                                    groups. Default: 'memberOf'.
+``LDAP_GROUPS_OBJECT_FILTER``      The filter to use when searching for groups objects.
+                                   Default: 'objectclass=Group'
 ``LDAP_GROUP_FIELDS``              ``list`` of fields to return when searching for a group's
                                    object details. Default: ``list`` (all).
 ``LDAP_GROUP_OBJECT_FILTER``       The filter to use when searching for a group object.
diff --git a/examples/basic_auth/app_oldap.py b/examples/basic_auth/app_oldap.py
index 4155130..99aa789 100644
--- a/examples/basic_auth/app_oldap.py
+++ b/examples/basic_auth/app_oldap.py
@@ -15,10 +15,12 @@ app.config['LDAP_OPENLDAP'] = True
 app.config['LDAP_OBJECTS_DN'] = 'dn'
 app.config['LDAP_USER_OBJECT_FILTER'] = '(&(objectclass=inetOrgPerson)(uid=%s))'
 
-# Groups
-app.config['LDAP_GROUP_MEMBERS_FIELD'] = "uniquemember"
-app.config['LDAP_GROUP_OBJECT_FILTER'] = "(&(objectclass=groupOfUniqueNames)(cn=%s))"
-app.config['LDAP_GROUP_MEMBER_FILTER'] = "(&(cn=*)(objectclass=groupOfUniqueNames)(uniquemember=%s))"
+# Groups configuration
+app.config['LDAP_GROUP_MEMBERS_FIELD'] = 'uniquemember'
+app.config['LDAP_GROUP_OBJECT_FILTER'] = '(&(objectclass=groupOfUniqueNames)(cn=%s))'
+app.config['LDAP_GROUPS_OBJECT_FILTER'] = 'objectclass=groupOfUniqueNames'
+app.config['LDAP_GROUP_FIELDS'] = ['cn', 'entryDN', 'member', 'description']
+app.config['LDAP_GROUP_MEMBER_FILTER'] = '(&(cn=*)(objectclass=groupOfUniqueNames)(member=%s))'
 app.config['LDAP_GROUP_MEMBER_FILTER_FIELD'] = "cn"
 
 ldap = LDAP(app)
diff --git a/examples/groups/app_oldap.py b/examples/groups/app_oldap.py
index c4de23c..36677f1 100644
--- a/examples/groups/app_oldap.py
+++ b/examples/groups/app_oldap.py
@@ -15,9 +15,11 @@ app.config['LDAP_PASSWORD'] = 'password'
 app.config['LDAP_USER_OBJECT_FILTER'] = '(&(objectclass=inetOrgPerson)(uid=%s))'
 
 # Group configuration
-app.config['LDAP_GROUP_MEMBERS_FIELD'] = "uniquemember"
-app.config['LDAP_GROUP_OBJECT_FILTER'] = "(&(objectclass=groupOfUniqueNames)(uniquemember=%s))"
-app.config['LDAP_GROUP_MEMBER_FILTER'] = "(&(cn=*)(objectclass=groupOfUniqueNames)(uniquemember=%s))"
+app.config['LDAP_GROUP_MEMBERS_FIELD'] = 'uniquemember'
+app.config['LDAP_GROUP_OBJECT_FILTER'] = '(&(objectclass=groupOfUniqueNames)(cn=%s))'
+app.config['LDAP_GROUPS_OBJECT_FILTER'] = 'objectclass=groupOfUniqueNames'
+app.config['LDAP_GROUP_FIELDS'] = ['cn', 'entryDN', 'member', 'description']
+app.config['LDAP_GROUP_MEMBER_FILTER'] = '(&(cn=*)(objectclass=groupOfUniqueNames)(member=%s))'
 app.config['LDAP_GROUP_MEMBER_FILTER_FIELD'] = "cn"
 
 ldap = LDAP(app)
diff --git a/flask_simpleldap/__init__.py b/flask_simpleldap/__init__.py
index 73ab24c..0562ef3 100644
--- a/flask_simpleldap/__init__.py
+++ b/flask_simpleldap/__init__.py
@@ -49,6 +49,7 @@ class LDAP(object):
                               '(&(objectclass=Person)(userPrincipalName=%s))')
         app.config.setdefault('LDAP_USER_GROUPS_FIELD', 'memberOf')
         app.config.setdefault('LDAP_GROUP_FIELDS', [])
+        app.config.setdefault('LDAP_GROUPS_OBJECT_FILTER', 'objectclass=Group')
         app.config.setdefault('LDAP_GROUP_OBJECT_FILTER',
                               '(&(objectclass=Group)(userPrincipalName=%s))')
         app.config.setdefault('LDAP_GROUP_MEMBERS_FIELD', 'member')
@@ -170,13 +171,13 @@ class LDAP(object):
             if not dn_only:
                 fields = current_app.config['LDAP_USER_FIELDS']
             query_filter = query_filter or \
-                           current_app.config['LDAP_USER_OBJECT_FILTER']
+                current_app.config['LDAP_USER_OBJECT_FILTER']
             query = ldap_filter.filter_format(query_filter, (user,))
         elif group is not None:
             if not dn_only:
                 fields = current_app.config['LDAP_GROUP_FIELDS']
             query_filter = query_filter or \
-                           current_app.config['LDAP_GROUP_OBJECT_FILTER']
+                current_app.config['LDAP_GROUP_OBJECT_FILTER']
             query = ldap_filter.filter_format(query_filter, (group,))
         conn = self.bind
         try:
@@ -201,6 +202,42 @@ class LDAP(object):
         except ldap.LDAPError as e:
             raise LDAPException(self.error(e.args))
 
+    def get_groups(self, fields=None, dn_only=False):
+        """Returns a ``list`` with the groups in base dn
+        or an empty``list`` if unsuccessful.
+
+        LDAP query setting is ``LDAP_GROUPS_OBJECT_FILTER``
+
+        :param fields: list of group fields to retrieve.
+         if ``None`` or empty, default group fields is used
+        :type fields: list
+        :param bool dn_only: If we should only retrieve the object's
+            distinguished name or not. Default: ``False``.
+        """
+        conn = self.bind
+        try:
+            fields = fields or current_app.config['LDAP_GROUP_FIELDS']
+            if current_app.config['LDAP_OPENLDAP']:
+                records = conn.search_s(
+                    current_app.config['LDAP_BASE_DN'], ldap.SCOPE_SUBTREE,
+                    current_app.config['LDAP_GROUPS_OBJECT_FILTER'],
+                    fields)
+            else:
+                records = conn.search_s(
+                    current_app.config['LDAP_BASE_DN'], ldap.SCOPE_SUBTREE,
+                    current_app.config['LDAP_GROUPS_OBJECT_FILTER'],
+                    fields)
+            conn.unbind_s()
+            if records:
+                if dn_only:
+                    return [r[0] for r in records]
+                else:
+                    return [r[1] for r in records]
+            else:
+                return []
+        except ldap.LDAPError as e:
+            raise LDAPException(self.error(e.args))
+
     def get_user_groups(self, user):
         """Returns a ``list`` with the user's groups or ``None`` if
         unsuccessful.
-- 
GitLab