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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to

### Added

- ✨(backend) populate `User.teams` from a configurable OIDC claim (`OIDC_TEAMS_CLAIM`)
- ✨(backend) allow converting a file while it is being analyzed
- ✨(frontend) add file type, contact and modification date topbar filters
- ✨(frontend) add location, file type, contact and date search filters
Expand Down
1 change: 1 addition & 0 deletions docs/env.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ This document lists all configurable environment variables for the Drive applica
| `OIDC_STORE_ID_TOKEN` | Store OIDC ID token | `True` |
| `OIDC_STORE_REFRESH_TOKEN` | Store OIDC refresh token | `False` |
| `OIDC_STORE_REFRESH_TOKEN_KEY` | Key for storing OIDC refresh token | `None` |
| `OIDC_TEAMS_CLAIM` | Name of the OIDC claim holding the user's teams/groups, stored on the user and exposed through `User.teams` for team-based access. Empty keeps `User.teams` empty. | `None` |
| `OIDC_USE_NONCE` | Use nonce for OIDC requests | `True` |
| `OIDC_USE_PKCE` | Use PKCE when interacting with OIDC server | `False` |
| `OIDC_USER_INFO` | List of OIDC user info claims | `[]` |
Expand Down
8 changes: 6 additions & 2 deletions src/backend/core/authentication/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ def get_extra_claims(self, user_info):
"""

# We need to add the claims that we want to store so that they are
# available in the post_get_or_create_user method.
claims_to_store = {claim: user_info.get(claim) for claim in settings.OIDC_STORE_CLAIMS}
# available in the post_get_or_create_user method. The team claim (if
# configured) is always stored so that ``User.teams`` can read it.
store_claims = list(settings.OIDC_STORE_CLAIMS)
if settings.OIDC_TEAMS_CLAIM and settings.OIDC_TEAMS_CLAIM not in store_claims:
store_claims.append(settings.OIDC_TEAMS_CLAIM)
claims_to_store = {claim: user_info.get(claim) for claim in store_claims}
return {
"full_name": self.compute_full_name(user_info),
"short_name": user_info.get(settings.OIDC_USERINFO_SHORTNAME_FIELD),
Expand Down
11 changes: 9 additions & 2 deletions src/backend/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,9 +365,16 @@ def send_email(self, subject, context=None, language=None):
def teams(self):
"""
Get list of teams in which the user is, as a list of strings.
Must be cached if retrieved remotely.

Teams are read from the OIDC claim named by ``settings.OIDC_TEAMS_CLAIM``,
stored on ``self.claims`` at login time. When the setting is unset the
user belongs to no team (the previous default). Cached per instance;
must be cached if ever retrieved remotely.
"""
return []
claim = settings.OIDC_TEAMS_CLAIM
if not claim:
return []
return (self.claims or {}).get(claim) or []


class UserReconciliation(BaseModel):
Expand Down
19 changes: 19 additions & 0 deletions src/backend/core/tests/authentication/test_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,3 +631,22 @@ def get_userinfo_mocked(*args):
# Verify the entitlement backend was called
mock_get_entitlements_backend.assert_called_once()
mock_entitlement_backend.can_access.assert_called_once()


@override_settings(OIDC_TEAMS_CLAIM="groups")
def test_authentication_stores_teams_claim(monkeypatch):
"""
When OIDC_TEAMS_CLAIM is set, the claim is stored on the user (even if absent
from OIDC_STORE_CLAIMS) and surfaced through ``User.teams``.
"""
klass = OIDCAuthenticationBackend()

def get_userinfo_mocked(*args):
return {"sub": "123", "email": "drive@example.com", "groups": ["team-a", "team-b"]}

monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked)

user = klass.get_or_create_user(access_token="test-token", id_token=None, payload=None)

assert user.claims.get("groups") == ["team-a", "team-b"]
assert user.teams == ["team-a", "team-b"]
22 changes: 22 additions & 0 deletions src/backend/core/tests/test_models_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from django.core import mail
from django.core.exceptions import ValidationError
from django.test.utils import override_settings

import pytest

Expand All @@ -20,6 +21,27 @@ def test_models_users_str():
assert str(user) == user.email


def test_models_users_teams_default_empty():
"""Without OIDC_TEAMS_CLAIM configured, a user belongs to no team."""
user = factories.UserFactory(claims={"groups": ["team-a", "team-b"]})
assert user.teams == []


@override_settings(OIDC_TEAMS_CLAIM="groups")
def test_models_users_teams_from_claim():
"""User.teams should read the list from the configured OIDC claim."""
user = factories.UserFactory(claims={"groups": ["team-a", "team-b"]})
assert user.teams == ["team-a", "team-b"]


@override_settings(OIDC_TEAMS_CLAIM="groups")
def test_models_users_teams_claim_absent_or_null():
"""A configured-but-missing (or null) claim should yield an empty list."""
assert factories.UserFactory(claims={}).teams == []
assert factories.UserFactory(claims={"groups": None}).teams == []
assert factories.UserFactory(claims=None).teams == []


def test_models_users_id_unique():
"""The "id" field should be unique."""
user = factories.UserFactory()
Expand Down
8 changes: 8 additions & 0 deletions src/backend/drive/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -1165,6 +1165,14 @@ class Base(Configuration):
environ_prefix=None,
)

# Name of the OIDC claim holding the user's team/group memberships. When set,
# that claim is stored (see get_extra_claims) and surfaced through
# ``User.teams`` for team-based access control. Empty (default) keeps
# ``User.teams`` empty, preserving the previous behaviour.
OIDC_TEAMS_CLAIM = values.Value(
None, environ_name="OIDC_TEAMS_CLAIM", environ_prefix=None
)

# WARNING: Enabling this setting allows multiple user accounts to share the same email
# address. This may cause security issues and is not recommended for production use when
# email is activated as fallback for identification (see previous setting).
Expand Down