From 09baba6daa747ba28e374ad02803f0315e9e2d95 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Mon, 8 Sep 2025 12:30:19 +0200 Subject: [PATCH 1/5] 5419: Added aakb groups and roles claims --- backend/open_webui/utils/oauth.py | 104 ++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 0cbfcb2ebf76..3982c0171ce3 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -247,6 +247,104 @@ def _normalize_token_expiry(token: dict) -> dict: return token +# PATCH OIDC +def set_aak_groups(user_data: UserInfo) -> UserInfo: + """ + Set AAK groups based on AAK claims. AAK groups need to be parsed from a collection of AAK claims, + so we cannot rely on Open WebUI's claims mapping. Parses the relevant AAK claims and adds them + to the "groups" list. This enables us to rely on Open WebUI's role management for user role assignment. + + To ensure unique group names, they are constructed as " ()". + + Example claims: + + "companyname": [ + "Aarhus Kommune" + ], + "division": [ + "Kultur og Borgerservice" + ], + "department": [ + "Borgerservice og Biblioteker" + ], + "extensionAttribute12": [ + "ITK" + ], + "Office": [ + "ITK Development" + ], + "extensionAttribute7": [ + "1001;1004;1012;1103;6530" + ] + + The ID's for the departments are given sequentially in "extensionAttribute7". Users in management postitions will + not have five levels of AAK groups. This will show in the length of "extensionAttribute7" but will not show in the + other claims. In the above example a manager will still have the "Office" claim, but it will repeat the value from + "extensionAttribute12" and "extensionAttribute7 will only contain "1001;1004;1012;1103" + + Note: ENABLE_OAUTH_GROUP_MANAGEMENT and ENABLE_OAUTH_GROUP_CREATION must be set to 'true' + + Args: + user_data (dict): The decoded OIDC token + + Returns: + The decoded OIDC token with the AAK group names added to the "groups" list. + """ + + log.debug("Running AAK Group management") + log.debug(user_data) + + user_data['groups'] = [] + + dept_ids = user_data.get("extensionAttribute7", "").split(";") + dept_depth = len(dept_ids) + + if "companyname" in user_data and dept_depth >= 1: + user_data['groups'].append(user_data.get("companyname", "") + " (" + dept_ids[0] + ")") + if "division" in user_data and dept_depth >= 2: + user_data['groups'].append(user_data.get("division", "") + " (" + dept_ids[1] + ")") + if "department" in user_data and dept_depth >= 3: + user_data['groups'].append(user_data.get("department", "") + " (" + dept_ids[2] + ")") + if "extensionAttribute12" in user_data and dept_depth >= 4: + user_data['groups'].append(user_data.get("extensionAttribute12", "") + " (" + dept_ids[3] + ")") + if "Office" in user_data and dept_depth >= 5: + user_data['groups'].append(user_data.get("Office", "") + " (" + dept_ids[4] + ")") + + log.debug(f"Using groups {user_data.get('groups', '')}.") + + return user_data + + +def set_aak_role(user_data: UserInfo) -> UserInfo: + """ + Set the AAK role based on AAK claims. For "builders" we cannot map to a native Open WebUI role. + Instead, we add the role "Builder" to the list of groups. + + Note: ENABLE_OAUTH_GROUP_MANAGEMENT and ENABLE_OAUTH_GROUP_CREATION must be set to 'true' + + Args: + user_data (dict): The decoded OIDC token + + Returns: + The decoded OIDC token with the AAK role added to the "groups" list. + """ + + log.debug("Running AAK Role management") + log.debug(user_data) + + claims_roles = user_data.get("role", "") + + log.debug(f"Using aak_claims_role {claims_roles}.") + + if "builder" in claims_roles: + user_data['groups'].append("Builder") + + log.debug(f"Using role-groups {user_data.get('groups', '')}.") + + return user_data +# //PATCH OIDC + + FERNET = None if len(OAUTH_CLIENT_INFO_ENCRYPTION_KEY) != 44: @@ -1823,6 +1921,12 @@ async def handle_callback(self, request, provider, response, db=None): log.warning(f'OAuth callback failed, user data is missing: {token}') raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED) + # PATCH OIDC + # Set AAK role and groups + user_data = set_aak_groups(user_data=user_data) + user_data = set_aak_role(user_data=user_data) + # //PATCH OIDC + # Extract the "sub" claim, using custom claim if configured if auth_config.OAUTH_SUB_CLAIM: sub = user_data.get(auth_config.OAUTH_SUB_CLAIM) From 60c27396b12208111e0f8479d70d2449ceb4422a Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Wed, 17 Dec 2025 11:40:09 +0100 Subject: [PATCH 2/5] Added "AAK_OAUTH_ENABLE_ROLE_GROUPS_MAPPING" --- backend/open_webui/config.py | 5 +++++ backend/open_webui/utils/oauth.py | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index ec541f5a336e..619dcabdba66 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -2549,6 +2549,11 @@ class BannerModel(BaseModel): ENABLE_OAUTH_GROUP_CREATION = os.getenv('ENABLE_OAUTH_GROUP_CREATION', 'False').lower() == 'true' +# PATCH OIDC +AAK_OAUTH_ENABLE_ROLE_GROUPS_MAPPING = ( + os.environ.get("AAK_OAUTH_ENABLE_ROLE_GROUPS_MAPPING", "False").lower() == "true" +) +# //PATCH OIDC oauth_group_default_share = os.getenv('OAUTH_GROUP_DEFAULT_SHARE', 'true').strip().lower() OAUTH_GROUP_DEFAULT_SHARE = 'members' if oauth_group_default_share == 'members' else oauth_group_default_share == 'true' diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 3982c0171ce3..6563ef7d9fd9 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -42,6 +42,7 @@ ENABLE_OAUTH_SIGNUP, JWT_EXPIRES_IN, OAUTH_ACCESS_TOKEN_REQUEST_INCLUDE_CLIENT_ID, + AAK_OAUTH_ENABLE_ROLE_GROUPS_MAPPING, # PATCH OIDC OAUTH_ADMIN_ROLES, OAUTH_ALLOWED_DOMAINS, OAUTH_ALLOWED_ROLES, @@ -1923,8 +1924,9 @@ async def handle_callback(self, request, provider, response, db=None): # PATCH OIDC # Set AAK role and groups - user_data = set_aak_groups(user_data=user_data) - user_data = set_aak_role(user_data=user_data) + if AAK_OAUTH_ENABLE_ROLE_GROUPS_MAPPING: + user_data = set_aak_groups(user_data=user_data) + user_data = set_aak_role(user_data=user_data) # //PATCH OIDC # Extract the "sub" claim, using custom claim if configured From d7411ace6407d3fa64dbcf35ddf821f0b3b7b46f Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Wed, 17 Dec 2025 12:44:08 +0100 Subject: [PATCH 3/5] Changed claims to be configurable --- backend/open_webui/config.py | 7 ++++ backend/open_webui/utils/oauth.py | 66 +++++++++++-------------------- 2 files changed, 30 insertions(+), 43 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 619dcabdba66..af904cfbaa5a 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -2553,6 +2553,13 @@ class BannerModel(BaseModel): AAK_OAUTH_ENABLE_ROLE_GROUPS_MAPPING = ( os.environ.get("AAK_OAUTH_ENABLE_ROLE_GROUPS_MAPPING", "False").lower() == "true" ) +# AAK Group claim configuration +AAK_OAUTH_GROUP_CLAIMS = os.environ.get( + "AAK_OAUTH_GROUP_CLAIMS", + '["companyname", "division", "department", "extensionAttribute12", "Office"]' +) +AAK_OAUTH_GROUP_ID_CLAIM = os.environ.get("AAK_OAUTH_GROUP_ID_CLAIM", "extensionAttribute7") +AAK_OAUTH_GROUP_ID_SEPARATOR = os.environ.get("AAK_OAUTH_GROUP_ID_SEPARATOR", ";") # //PATCH OIDC oauth_group_default_share = os.getenv('OAUTH_GROUP_DEFAULT_SHARE', 'true').strip().lower() diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 6563ef7d9fd9..ad80356a4f4e 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -251,37 +251,14 @@ def _normalize_token_expiry(token: dict) -> dict: # PATCH OIDC def set_aak_groups(user_data: UserInfo) -> UserInfo: """ - Set AAK groups based on AAK claims. AAK groups need to be parsed from a collection of AAK claims, - so we cannot rely on Open WebUI's claims mapping. Parses the relevant AAK claims and adds them - to the "groups" list. This enables us to rely on Open WebUI's role management for user role assignment. - - To ensure unique group names, they are constructed as " ()". - - Example claims: - - "companyname": [ - "Aarhus Kommune" - ], - "division": [ - "Kultur og Borgerservice" - ], - "department": [ - "Borgerservice og Biblioteker" - ], - "extensionAttribute12": [ - "ITK" - ], - "Office": [ - "ITK Development" - ], - "extensionAttribute7": [ - "1001;1004;1012;1103;6530" - ] - - The ID's for the departments are given sequentially in "extensionAttribute7". Users in management postitions will - not have five levels of AAK groups. This will show in the length of "extensionAttribute7" but will not show in the - other claims. In the above example a manager will still have the "Office" claim, but it will repeat the value from - "extensionAttribute12" and "extensionAttribute7 will only contain "1001;1004;1012;1103" + Set AAK groups based on configurable AAK claims. Parses the relevant AAK claims + and adds them to the "groups" list. + + Configuration via environment variables: + - AAK_OAUTH_GROUP_CLAIMS: JSON array of claim names in hierarchical order + Default: '["companyname", "division", "department", "extensionAttribute12", "Office"]' + - AAK_OAUTH_GROUP_ID_CLAIM: Claim containing separated IDs (default: extensionAttribute7) + - AAK_OAUTH_GROUP_ID_SEPARATOR: Separator for IDs (default: ;) Note: ENABLE_OAUTH_GROUP_MANAGEMENT and ENABLE_OAUTH_GROUP_CREATION must be set to 'true' @@ -291,25 +268,28 @@ def set_aak_groups(user_data: UserInfo) -> UserInfo: Returns: The decoded OIDC token with the AAK group names added to the "groups" list. """ - log.debug("Running AAK Group management") log.debug(user_data) user_data['groups'] = [] - dept_ids = user_data.get("extensionAttribute7", "").split(";") + # Parse configured claim names from JSON + try: + group_claims = json.loads(AAK_OAUTH_GROUP_CLAIMS) + except json.JSONDecodeError as e: + log.error(f"Failed to parse AAK_OAUTH_GROUP_CLAIMS: {e}") + return user_data + + # Get IDs from configured claim + dept_ids = user_data.get(AAK_OAUTH_GROUP_ID_CLAIM, "").split(AAK_OAUTH_GROUP_ID_SEPARATOR) dept_depth = len(dept_ids) - if "companyname" in user_data and dept_depth >= 1: - user_data['groups'].append(user_data.get("companyname", "") + " (" + dept_ids[0] + ")") - if "division" in user_data and dept_depth >= 2: - user_data['groups'].append(user_data.get("division", "") + " (" + dept_ids[1] + ")") - if "department" in user_data and dept_depth >= 3: - user_data['groups'].append(user_data.get("department", "") + " (" + dept_ids[2] + ")") - if "extensionAttribute12" in user_data and dept_depth >= 4: - user_data['groups'].append(user_data.get("extensionAttribute12", "") + " (" + dept_ids[3] + ")") - if "Office" in user_data and dept_depth >= 5: - user_data['groups'].append(user_data.get("Office", "") + " (" + dept_ids[4] + ")") + # Process each configured level + for level, claim_name in enumerate(group_claims): + if claim_name and claim_name in user_data and dept_depth >= (level + 1): + name = user_data.get(claim_name, "") + group_id = dept_ids[level] + user_data['groups'].append(f"{name} ({group_id})") log.debug(f"Using groups {user_data.get('groups', '')}.") From fd477ff15aa1d9a8901850c3d8834d69e290a13d Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Wed, 17 Dec 2025 13:10:07 +0100 Subject: [PATCH 4/5] Added support for setting oauth role for debugging --- backend/open_webui/config.py | 2 ++ backend/open_webui/utils/oauth.py | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index af904cfbaa5a..b02e26a0632c 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -2560,6 +2560,8 @@ class BannerModel(BaseModel): ) AAK_OAUTH_GROUP_ID_CLAIM = os.environ.get("AAK_OAUTH_GROUP_ID_CLAIM", "extensionAttribute7") AAK_OAUTH_GROUP_ID_SEPARATOR = os.environ.get("AAK_OAUTH_GROUP_ID_SEPARATOR", ";") +# Debug: Override OAuth roles for testing +AAK_OAUTH_DEBUG_FORCE_ROLE = os.environ.get("AAK_OAUTH_DEBUG_FORCE_ROLE", "") # //PATCH OIDC oauth_group_default_share = os.getenv('OAUTH_GROUP_DEFAULT_SHARE', 'true').strip().lower() diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index ad80356a4f4e..2f9acf2764ea 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -42,7 +42,10 @@ ENABLE_OAUTH_SIGNUP, JWT_EXPIRES_IN, OAUTH_ACCESS_TOKEN_REQUEST_INCLUDE_CLIENT_ID, - AAK_OAUTH_ENABLE_ROLE_GROUPS_MAPPING, # PATCH OIDC + AAK_OAUTH_GROUP_CLAIMS, # PATCH OIDC + AAK_OAUTH_GROUP_ID_CLAIM, # PATCH OIDC + AAK_OAUTH_GROUP_ID_SEPARATOR, # PATCH OIDC + AAK_OAUTH_DEBUG_FORCE_ROLE, # PATCH OIDC OAUTH_ADMIN_ROLES, OAUTH_ALLOWED_DOMAINS, OAUTH_ALLOWED_ROLES, @@ -1587,6 +1590,11 @@ async def get_user_role(self, user, user_data): elif isinstance(claim_data, int): oauth_roles = [str(claim_data)] + # Debug: Override roles if AAK_OAUTH_DEBUG_FORCE_ROLE is set + if AAK_OAUTH_DEBUG_FORCE_ROLE: + oauth_roles = [r.strip() for r in AAK_OAUTH_DEBUG_FORCE_ROLE.split(",") if r.strip()] + log.warning(f'AAK_OAUTH_DEBUG_FORCE_ROLE is set, overriding oauth_roles to: {oauth_roles}') + log.debug(f'Oauth Roles claim: {oauth_claim}') log.debug(f'User roles from oauth: {oauth_roles}') log.debug(f'Accepted user roles: {oauth_allowed_roles}') From 176a03f1cf6472c1e1ae6a71eca1665b95c79efb Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Wed, 17 Dec 2025 13:27:25 +0100 Subject: [PATCH 5/5] Re-added set_aak_groups documentation --- backend/open_webui/utils/oauth.py | 32 +++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 2f9acf2764ea..05af72dec31c 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -42,6 +42,7 @@ ENABLE_OAUTH_SIGNUP, JWT_EXPIRES_IN, OAUTH_ACCESS_TOKEN_REQUEST_INCLUDE_CLIENT_ID, + AAK_OAUTH_ENABLE_ROLE_GROUPS_MAPPING, # PATCH OIDC AAK_OAUTH_GROUP_CLAIMS, # PATCH OIDC AAK_OAUTH_GROUP_ID_CLAIM, # PATCH OIDC AAK_OAUTH_GROUP_ID_SEPARATOR, # PATCH OIDC @@ -254,8 +255,35 @@ def _normalize_token_expiry(token: dict) -> dict: # PATCH OIDC def set_aak_groups(user_data: UserInfo) -> UserInfo: """ - Set AAK groups based on configurable AAK claims. Parses the relevant AAK claims - and adds them to the "groups" list. + Set AAK groups based on AAK claims. AAK groups need to be parsed from a collection of AAK claims, + so we cannot rely on Open WebUI's claims mapping. Parses the relevant AAK claims and adds them + to the "groups" list. This enables us to rely on Open WebUI's role management for user role assignment. + To ensure unique group names, they are constructed as " ()". + + Example claims: + "companyname": [ + "Aarhus Kommune" + ], + "division": [ + "Kultur og Borgerservice" + ], + "department": [ + "Borgerservice og Biblioteker" + ], + "extensionAttribute12": [ + "ITK" + ], + "Office": [ + "ITK Development" + ], + "extensionAttribute7": [ + "1001;1004;1012;1103;6530" + ] + + The ID's for the departments are given sequentially in "extensionAttribute7". Users in management postitions will + not have five levels of AAK groups. This will show in the length of "extensionAttribute7" but will not show in the + other claims. In the above example a manager will still have the "Office" claim, but it will repeat the value from + "extensionAttribute12" and "extensionAttribute7 will only contain "1001;1004;1012;1103" Configuration via environment variables: - AAK_OAUTH_GROUP_CLAIMS: JSON array of claim names in hierarchical order