From ba591d8c41b3de649ef854d3d92faa787bcbf010 Mon Sep 17 00:00:00 2001 From: Taehong Gu Date: Wed, 11 Jun 2025 23:55:31 +0900 Subject: [PATCH] Configure LDAP group synchronization with Open WebUI --- backend/open_webui/config.py | 20 +++++ backend/open_webui/main.py | 9 ++ backend/open_webui/routers/auths.py | 98 ++++++++++++++++++++-- backend/open_webui/utils/access_control.py | 2 +- 4 files changed, 123 insertions(+), 6 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index b48ba4f2e..6a9030432 100644 --- a/backend/open_webui/config.py +++ b/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"), +) + diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index b6f26a827..84c6b6caa 100644 --- a/backend/open_webui/main.py +++ b/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 diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 070b203ba..7414e3a86 100644 --- a/backend/open_webui/routers/auths.py +++ b/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", diff --git a/backend/open_webui/utils/access_control.py b/backend/open_webui/utils/access_control.py index 1699cfaa7..c93574527 100644 --- a/backend/open_webui/utils/access_control.py +++ b/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