Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions backend/open_webui/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2549,6 +2549,20 @@ 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"
)
# 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", ";")
# 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()
OAUTH_GROUP_DEFAULT_SHARE = 'members' if oauth_group_default_share == 'members' else oauth_group_default_share == 'true'
Expand Down
122 changes: 122 additions & 0 deletions backend/open_webui/utils/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
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,
Expand Down Expand Up @@ -247,6 +252,111 @@ 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 "<aak_department_name> (<aak_department_id>)".

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
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'

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'] = []

# 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)

# 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', '')}.")

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:
Expand Down Expand Up @@ -1508,6 +1618,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}')
Expand Down Expand Up @@ -1823,6 +1938,13 @@ 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
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
if auth_config.OAUTH_SUB_CLAIM:
sub = user_data.get(auth_config.OAUTH_SUB_CLAIM)
Expand Down