Forráskód Böngészése

Merge pull request #14898 from jackthgu/ldap-group-sync

feat: Configure LDAP group synchronization with Open WebUI
Tim Jaeryang Baek 3 hónapja
szülő
commit
0d00971dad

+ 20 - 0
backend/open_webui/config.py

@@ -3075,3 +3075,23 @@ LDAP_VALIDATE_CERT = PersistentConfig(
 LDAP_CIPHERS = PersistentConfig(
     "LDAP_CIPHERS", "ldap.server.ciphers", os.environ.get("LDAP_CIPHERS", "ALL")
 )
+
+# For LDAP Group Management
+ENABLE_LDAP_GROUP_MANAGEMENT = PersistentConfig(
+    "ENABLE_LDAP_GROUP_MANAGEMENT",
+    "ldap.group.enable_management",
+    os.environ.get("ENABLE_LDAP_GROUP_MANAGEMENT", "False").lower() == "true",
+)
+
+ENABLE_LDAP_GROUP_CREATION = PersistentConfig(
+    "ENABLE_LDAP_GROUP_CREATION",
+    "ldap.group.enable_creation",
+    os.environ.get("ENABLE_LDAP_GROUP_CREATION", "False").lower() == "true",
+)
+
+LDAP_ATTRIBUTE_FOR_GROUPS = PersistentConfig(
+    "LDAP_ATTRIBUTE_FOR_GROUPS",
+    "ldap.server.attribute_for_groups",
+    os.environ.get("LDAP_ATTRIBUTE_FOR_GROUPS", "memberOf"),
+)
+

+ 9 - 0
backend/open_webui/main.py

@@ -349,6 +349,10 @@ from open_webui.config import (
     LDAP_CA_CERT_FILE,
     LDAP_VALIDATE_CERT,
     LDAP_CIPHERS,
+    # LDAP Group Management
+    ENABLE_LDAP_GROUP_MANAGEMENT,
+    ENABLE_LDAP_GROUP_CREATION,
+    LDAP_ATTRIBUTE_FOR_GROUPS,
     # Misc
     ENV,
     CACHE_DIR,
@@ -676,6 +680,11 @@ app.state.config.LDAP_CA_CERT_FILE = LDAP_CA_CERT_FILE
 app.state.config.LDAP_VALIDATE_CERT = LDAP_VALIDATE_CERT
 app.state.config.LDAP_CIPHERS = LDAP_CIPHERS
 
+# For LDAP Group Management
+app.state.config.ENABLE_LDAP_GROUP_MANAGEMENT = ENABLE_LDAP_GROUP_MANAGEMENT
+app.state.config.ENABLE_LDAP_GROUP_CREATION = ENABLE_LDAP_GROUP_CREATION
+app.state.config.LDAP_ATTRIBUTE_FOR_GROUPS = LDAP_ATTRIBUTE_FOR_GROUPS
+
 
 app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
 app.state.AUTH_TRUSTED_NAME_HEADER = WEBUI_AUTH_TRUSTED_NAME_HEADER

+ 93 - 5
backend/open_webui/routers/auths.py

@@ -228,14 +228,25 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
         if not connection_app.bind():
             raise HTTPException(400, detail="Application account bind failed")
 
+        ENABLE_LDAP_GROUP_MANAGEMENT = request.app.state.config.ENABLE_LDAP_GROUP_MANAGEMENT
+        LDAP_ATTRIBUTE_FOR_GROUPS = request.app.state.config.LDAP_ATTRIBUTE_FOR_GROUPS
+        
+        search_attributes = [
+            f"{LDAP_ATTRIBUTE_FOR_USERNAME}",
+            f"{LDAP_ATTRIBUTE_FOR_MAIL}",
+            "cn",
+        ]
+        
+        if ENABLE_LDAP_GROUP_MANAGEMENT:
+            search_attributes.append(f"{LDAP_ATTRIBUTE_FOR_GROUPS}")
+            log.info(f"LDAP Group Management enabled. Adding {LDAP_ATTRIBUTE_FOR_GROUPS} to search attributes")
+
+        log.info(f"LDAP search attributes: {search_attributes}")
+
         search_success = connection_app.search(
             search_base=LDAP_SEARCH_BASE,
             search_filter=f"(&({LDAP_ATTRIBUTE_FOR_USERNAME}={escape_filter_chars(form_data.user.lower())}){LDAP_SEARCH_FILTERS})",
-            attributes=[
-                f"{LDAP_ATTRIBUTE_FOR_USERNAME}",
-                f"{LDAP_ATTRIBUTE_FOR_MAIL}",
-                "cn",
-            ],
+            attributes=search_attributes,
         )
 
         if not search_success or not connection_app.entries:
@@ -258,6 +269,60 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
         cn = str(entry["cn"])
         user_dn = entry.entry_dn
 
+        user_groups = []
+        if ENABLE_LDAP_GROUP_MANAGEMENT and LDAP_ATTRIBUTE_FOR_GROUPS in entry:
+            group_dns = entry[LDAP_ATTRIBUTE_FOR_GROUPS]
+            log.info(f"LDAP raw group DNs for user {username}: {group_dns}")
+            
+            if group_dns:
+                log.info(f"LDAP group_dns original: {group_dns}")
+                log.info(f"LDAP group_dns type: {type(group_dns)}")
+                log.info(f"LDAP group_dns length: {len(group_dns)}")
+                
+                if hasattr(group_dns, 'value'):
+                    group_dns = group_dns.value
+                    log.info(f"Extracted .value property: {group_dns}")
+                elif hasattr(group_dns, '__iter__') and not isinstance(group_dns, (str, bytes)):
+                    group_dns = list(group_dns)
+                    log.info(f"Converted to list: {group_dns}")
+                elif not isinstance(group_dns, list):
+                    group_dns = [group_dns]
+                
+                if isinstance(group_dns, list):
+                    group_dns = [str(item) for item in group_dns]
+                else:
+                    group_dns = [str(group_dns)]
+                
+                log.info(f"LDAP group_dns after processing - type: {type(group_dns)}, length: {len(group_dns)}")
+                
+                for i, group_dn in enumerate(group_dns):
+                    group_dn_str = str(group_dn)
+                    log.info(f"Processing group DN #{i+1}: {group_dn_str}")
+                    
+                    try:
+                        cn_part = None
+                        dn_parts = group_dn_str.split(',')
+                        log.debug(f"DN parts: {dn_parts}")
+                        
+                        for part in dn_parts:
+                            part = part.strip()
+                            if part.upper().startswith('CN='):
+                                cn_part = part[3:]  
+                                break
+                        
+                        if cn_part:
+                            user_groups.append(cn_part)
+                        else:
+                            log.warning(f"Could not extract CN from group DN: {group_dn_str}")
+                    except Exception as e:
+                        log.warning(f"Failed to extract group name from DN {group_dn_str}: {e}")
+                
+                log.info(f"LDAP groups for user {username}: {user_groups} (total: {len(user_groups)})")
+            else:
+                log.info(f"No groups found for user {username}")
+        elif ENABLE_LDAP_GROUP_MANAGEMENT:
+            log.warning(f"LDAP Group Management enabled but {LDAP_ATTRIBUTE_FOR_GROUPS} attribute not found in user entry")
+
         if username == form_data.user.lower():
             connection_user = Connection(
                 server,
@@ -333,6 +398,29 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
                     user.id, request.app.state.config.USER_PERMISSIONS
                 )
 
+                if ENABLE_LDAP_GROUP_MANAGEMENT and user_groups and request.app.state.config.ENABLE_LDAP_GROUP_CREATION:
+                    from open_webui.models.groups import GroupForm
+                    existing_groups = Groups.get_groups()
+                    existing_group_names = [grp.name for grp in existing_groups]
+                    log.info(f"Existing groups: {existing_group_names}")
+                    
+                    for i, g in enumerate(user_groups):
+                        if not any(grp.name == g for grp in existing_groups):
+                            try:
+                                Groups.insert_new_group(user.id, GroupForm(name=g, description=f"{LDAP_SERVER_LABEL}"))
+                                log.info(f"Successfully created group '{g}'")
+                            except Exception as e:
+                                log.error(f"Failed to create group '{g}': {e}")
+                        else:
+                            log.info(f"Group {g} already exists")
+
+                if ENABLE_LDAP_GROUP_MANAGEMENT and user_groups and user.role != "admin":
+                    try:
+                        Groups.sync_user_groups_by_group_names(user.id, user_groups)
+                        log.info(f"Successfully synced groups for user {user.id}: {user_groups}")
+                    except Exception as e:
+                        log.error(f"Failed to sync groups for user {user.id}: {e}")
+
                 return {
                     "token": token,
                     "token_type": "Bearer",

+ 1 - 1
backend/open_webui/utils/access_control.py

@@ -60,7 +60,7 @@ def get_permissions(
 
     # Combine permissions from all user groups
     for group in user_groups:
-        group_permissions = group.permissions
+        group_permissions = group.permissions or {}
         permissions = combine_permissions(permissions, group_permissions)
 
     # Ensure all fields from default_permissions are present and filled in