Skip to content

Auto create SSO groups feature flag#8515

Closed
AlexanderGrooff wants to merge 10 commits into
opsmill:stablefrom
AlexanderGrooff:auto-create-sso-groups
Closed

Auto create SSO groups feature flag#8515
AlexanderGrooff wants to merge 10 commits into
opsmill:stablefrom
AlexanderGrooff:auto-create-sso-groups

Conversation

@AlexanderGrooff

@AlexanderGrooff AlexanderGrooff commented Mar 2, 2026

Copy link
Copy Markdown

Why

I'm missing the ability to automatically create groups coming from SSO. This PR adds feature flag sso_generate_groups to automatically create groups that are imported from SSO, and adds the ability to filter it based on a pattern defined in sso_generate_groups_filter.

What changed

Adds a new block in the authentication code to import groups from the SSO provider when the feature flag is enabled.
I've also changed the documentation to reflect these changes.

The current behaviour doesn't change. It only gets picked up when the feature flag is enabled.

How to review

How to test

I've added testcases, but you can also try importing SSO groups when the feature flag is enabled, that way you can see automatic groups being created.

Impact & rollout

  • Config/env changes: new flags INFRAHUB_SECURITY_SSO_GENERATE_GROUPS and INFRAHUB_SECURITY_SSO_GENERATE_GROUPS_FILTER
  • Deployment notes: safe to deploy

Checklist

  • Tests added/updated
  • Changelog entry added (uv run towncrier create ...)
  • External docs updated (if user-facing or ops-facing change)
  • Internal .md docs updated (internal knowledge and AI code tools knowledge)

Summary by CodeRabbit

  • New Features

    • Automatic creation of account groups for SSO users from identity provider groups, preserving existing groups and attaching memberships.
    • Concurrency-safe provisioning to avoid duplicate group creation.
    • New config options to enable auto-generation and provide regex filter(s) for extracting group names; invalid patterns are rejected.
    • Auto-created groups have no permissions by default
  • Documentation

    • Guides and reference updated with instructions, examples, and notes on auto-group creation and filters
  • Tests

    • New unit and functional tests covering auto-creation, filtering, membership, and validation

@AlexanderGrooff AlexanderGrooff requested review from a team as code owners March 2, 2026 14:52
@coderabbitai

coderabbitai Bot commented Mar 2, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds automatic SSO-driven account group provisioning and related configuration. Introduces sso_generate_groups and sso_generate_groups_filter in SecuritySettings with validation. Adds helpers _extract_effective_sso_group_names and _load_or_create_sso_groups, and updates the SSO signin flow to compute effective group names (optional regex filtering), load existing groups, and create missing groups with concurrency-safe handling before attaching memberships. Adds unit and functional tests for generation and filtering, and updates SSO guides and reference docs with configuration and examples.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Auto create SSO groups feature flag' accurately summarizes the main change: introducing a feature flag to enable automatic SSO group creation.
Description check ✅ Passed The description adequately covers key sections including Why (problem and solution), What changed (feature flags and documentation), How to test (test cases added), and Impact & rollout (config/env changes and deployment safety).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can customize the tone of the review comments and chat replies.

Configure the tone_instructions setting to customize the tone of the review comments and chat replies. For example, you can set the tone to Act like a strict teacher, Act like a pirate and more.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
backend/tests/unit/test_sso_generate_groups.py (1)

21-62: Consider using pytest fixtures or monkeypatch for config cleanup.

The try/finally pattern for restoring config values works but is verbose and error-prone if an exception occurs before the try block. Using monkeypatch.setattr would automatically restore values after each test.

♻️ Example using monkeypatch
async def test_groups_not_created_when_disabled(
    self, db: InfrahubDatabase, default_branch: Branch, register_core_models_schema, existing_group, monkeypatch
) -> None:
    """Verify that groups are NOT created when sso_generate_groups is disabled (default)"""
    monkeypatch.setattr(config.SETTINGS.security, "sso_generate_groups", False)

    await signin_sso_account(
        db=db, account_name="test-user", sso_groups=["existing-group", "new-group-1", "new-group-2"]
    )

    groups = await NodeManager.query(db=db, schema=CoreAccountGroup)
    group_names = {g.name.value for g in groups}

    assert "existing-group" in group_names
    assert "new-group-1" not in group_names
    assert "new-group-2" not in group_names
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/unit/test_sso_generate_groups.py` around lines 21 - 62, Replace
the try/finally config toggling in tests test_groups_not_created_when_disabled
and test_groups_created_when_enabled by using pytest's monkeypatch to set and
auto-restore config.SETTINGS.security.sso_generate_groups; specifically, add the
monkeypatch fixture to each test signature and call
monkeypatch.setattr(config.SETTINGS.security, "sso_generate_groups", False) (or
True) before calling signin_sso_account so the original value is restored
automatically instead of manually saving/restoring in the test.
backend/tests/functional/api/test_sso_groups.py (1)

19-25: Inconsistent schema usage in fixture.

The fixture uses CoreAccountGroup (a protocol) as the schema, but auth.py and the unit tests use InfrahubKind.ACCOUNTGROUP. Consider using InfrahubKind.ACCOUNTGROUP for consistency.

♻️ Proposed fix for consistency
+from infrahub.core.constants import InfrahubKind
+
 class TestSSOAutoGroupCreation:
     `@pytest.fixture`
     async def existing_group(self, db: InfrahubDatabase) -> CoreAccountGroup:
         """Create an existing group for testing."""
-        group = await Node.init(db=db, schema=CoreAccountGroup)
+        group = await Node.init(db=db, schema=InfrahubKind.ACCOUNTGROUP)
         await group.new(db=db, name="ExistingGroup")
         await group.save(db=db)
         return group
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/functional/api/test_sso_groups.py` around lines 19 - 25, The
existing_group fixture uses the protocol type CoreAccountGroup as the Node.init
schema which is inconsistent with other code; update the fixture to call
Node.init with schema=InfrahubKind.ACCOUNTGROUP (and adjust any type
hints/returns if necessary) so Node.init(db=db,
schema=InfrahubKind.ACCOUNTGROUP) is used instead of schema=CoreAccountGroup,
keeping the fixture name existing_group and the surrounding calls (new, save)
unchanged.
docs/docs/guides/sso.mdx (1)

447-447: Consider renaming to avoid step number confusion.

The main SSO setup guide already has "Step 4: Validate the SSO configuration" at line 300. This nested "Step 4" within the "Group mapping" subsection could confuse readers. Consider renaming to something like "Enable automatic group creation (optional)" without the "Step 4" prefix, or use a different numbering scheme for the group mapping substeps.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/docs/guides/sso.mdx` at line 447, The heading "#### Step 4: Enable
automatic group creation (optional)" conflicts with the main guide's "Step 4:
Validate the SSO configuration"; update the group mapping subsection header to
remove the "Step 4:" prefix or use a distinct substep label (e.g., "Enable
automatic group creation (optional)" or "Group mapping — Step 1") so numbering
doesn't collide, and ensure any related internal anchors or references that use
the exact heading text are updated accordingly (search for the exact string
"Step 4: Enable automatic group creation (optional)" and the main header "Step
4: Validate the SSO configuration" to confirm uniqueness).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/infrahub/auth.py`:
- Around line 166-171: Wrap the call to re.match(filter_pattern, group_name) in
a try/except that catches re.error, logs a warning including the offending
filter_pattern and exception, and then skips processing that group (continue);
update the block around filter_pattern / match / effective_name in auth.py (the
branch that computes effective_name from match.group(1)) to use this try/except
so an invalid sso_generate_groups_filter cannot raise and break SSO login.
- Around line 157-177: The code can create duplicate groups because
existing_group_names is built from raw SSO names but new groups are created from
the transformed effective_name; fix by resolving the effective_name before
deciding if a group exists and checking the DB for that effective_name as well:
for each group_name from sso_groups (inside the loop where
sso_generate_groups_filter and match logic live) compute effective_name, skip if
no match, then check both existing_group_names and created_group_names AND
perform an existence check against the Infrahub store (e.g., query for name ==
effective_name or use an existence/get method) before calling Node.init / new /
save for InfrahubKind.ACCOUNTGROUP to avoid creating a duplicate of an
already-present transformed name.

In `@backend/infrahub/config.py`:
- Around line 762-769: Add a Pydantic validator for the
sso_generate_groups_filter field that attempts to compile the provided string
with re.compile and raises a ValidationError with message "Invalid regex
pattern" on failure; locate the model that defines sso_generate_groups and
sso_generate_groups_filter and add a method decorated with
`@validator`("sso_generate_groups_filter", pre=True, always=False) (or the
equivalent pydantic v2 validator) to skip validation for None and run
re.compile(value) for non-empty strings, raising ValueError("Invalid regex
pattern") if compilation fails so the unit test in test_sso_generate_groups.py
passes.

In `@backend/tests/unit/test_sso_generate_groups.py`:
- Around line 149-159: Add a pydantic validator on the SecuritySettings model
for the sso_generate_groups_filter field that checks each provided pattern
(supporting str or list[str]) can compile via re.compile and on failure raise a
ValueError with the message "Invalid regex pattern" so that creating
SecuritySettings(sso_generate_groups_filter="[invalid(regex") triggers a
pydantic.ValidationError; update or add the validator function (e.g.,
`@validator`("sso_generate_groups_filter", pre=True, each_item=True) def
validate_sso_generate_groups_filter(...)) to perform the re.compile check and
return the original value when valid.

---

Nitpick comments:
In `@backend/tests/functional/api/test_sso_groups.py`:
- Around line 19-25: The existing_group fixture uses the protocol type
CoreAccountGroup as the Node.init schema which is inconsistent with other code;
update the fixture to call Node.init with schema=InfrahubKind.ACCOUNTGROUP (and
adjust any type hints/returns if necessary) so Node.init(db=db,
schema=InfrahubKind.ACCOUNTGROUP) is used instead of schema=CoreAccountGroup,
keeping the fixture name existing_group and the surrounding calls (new, save)
unchanged.

In `@backend/tests/unit/test_sso_generate_groups.py`:
- Around line 21-62: Replace the try/finally config toggling in tests
test_groups_not_created_when_disabled and test_groups_created_when_enabled by
using pytest's monkeypatch to set and auto-restore
config.SETTINGS.security.sso_generate_groups; specifically, add the monkeypatch
fixture to each test signature and call
monkeypatch.setattr(config.SETTINGS.security, "sso_generate_groups", False) (or
True) before calling signin_sso_account so the original value is restored
automatically instead of manually saving/restoring in the test.

In `@docs/docs/guides/sso.mdx`:
- Line 447: The heading "#### Step 4: Enable automatic group creation
(optional)" conflicts with the main guide's "Step 4: Validate the SSO
configuration"; update the group mapping subsection header to remove the "Step
4:" prefix or use a distinct substep label (e.g., "Enable automatic group
creation (optional)" or "Group mapping — Step 1") so numbering doesn't collide,
and ensure any related internal anchors or references that use the exact heading
text are updated accordingly (search for the exact string "Step 4: Enable
automatic group creation (optional)" and the main header "Step 4: Validate the
SSO configuration" to confirm uniqueness).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9301129 and c353fc6.

📒 Files selected for processing (6)
  • backend/infrahub/auth.py
  • backend/infrahub/config.py
  • backend/tests/functional/api/test_sso_groups.py
  • backend/tests/unit/test_sso_generate_groups.py
  • docs/docs/guides/sso.mdx
  • docs/docs/reference/sso.mdx

Comment thread backend/infrahub/auth.py Outdated
Comment thread backend/infrahub/auth.py Outdated
Comment thread backend/infrahub/config.py
Comment on lines +149 to +159
class TestSSOGenerateGroupsFilterValidation:
def test_invalid_regex_pattern_raises_error(self):
"""Verify that invalid regex patterns raise a validation error"""
from pydantic import ValidationError

from infrahub.config import SecuritySettings

with pytest.raises(ValidationError) as exc_info:
SecuritySettings(sso_generate_groups_filter="[invalid(regex")

assert "Invalid regex pattern" in str(exc_info.value)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Test will fail: missing config validation implementation.

This test expects SecuritySettings to raise a ValidationError with "Invalid regex pattern" when given an invalid regex, but the sso_generate_groups_filter field in config.py has no validator to enforce this. The test will fail until the validator is added.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/unit/test_sso_generate_groups.py` around lines 149 - 159, Add a
pydantic validator on the SecuritySettings model for the
sso_generate_groups_filter field that checks each provided pattern (supporting
str or list[str]) can compile via re.compile and on failure raise a ValueError
with the message "Invalid regex pattern" so that creating
SecuritySettings(sso_generate_groups_filter="[invalid(regex") triggers a
pydantic.ValidationError; update or add the validator function (e.g.,
`@validator`("sso_generate_groups_filter", pre=True, each_item=True) def
validate_sso_generate_groups_filter(...)) to perform the re.compile check and
return the original value when valid.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
backend/infrahub/config.py (1)

771-785: Add a docstring to the new validator method.

Please document accepted value shapes and failure behavior for this validator in Google-style format.

As per coding guidelines, "Always use triple quotes (""") for Python docstrings" and "Follow Google-style docstring format in Python."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/infrahub/config.py` around lines 771 - 785, Add a Google-style
docstring (triple-quoted) to the validator method
validate_sso_generate_groups_filter_regex (decorated with `@field_validator`) that
documents accepted input shapes (str, list[str], or None), explains that each
string is treated as a regular expression and will be compiled, and describes
failure behavior (raises ValueError when any pattern fails to compile, original
re.error chained). Keep the docstring brief and include Args and Raises sections
following Google-style conventions.
backend/tests/unit/test_sso_generate_groups.py (1)

22-326: Add explicit -> None return annotations to async test methods.

Most new async tests omit return type annotations; please annotate them consistently for mypy/ruff alignment.

🧩 Example pattern to apply across the file
-    async def test_groups_not_created_when_disabled(
+    async def test_groups_not_created_when_disabled(
         self, db: InfrahubDatabase, default_branch: Branch, register_core_models_schema, existing_group
-    ):
+    ) -> None:

As per coding guidelines, "Use type hints for all function parameters and return values in Python."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/unit/test_sso_generate_groups.py` around lines 22 - 326,
Several async test functions in this file (e.g.,
test_groups_not_created_when_disabled, test_groups_created_when_enabled,
test_filter_allows_matching_groups, test_no_filter_allows_all_groups,
test_filter_with_complex_pattern,
test_filter_does_not_duplicate_existing_extracted_group,
test_filter_extracts_group_name_from_capture_group,
test_filter_without_capture_group_uses_original_name,
test_filter_deduplicates_extracted_names, test_filter_skips_non_matching_groups)
are missing explicit return type annotations; update each async def signature to
include "-> None" (e.g., async def test_groups_not_created_when_disabled(...) ->
None:) to satisfy type-checker/style rules, leaving bodies unchanged. Ensure you
only modify the function signatures for the listed async test_* functions and
preserve existing imports and try/finally blocks.
backend/infrahub/auth.py (1)

142-174: Add a docstring for the new helper function.

Please add a concise Google-style docstring describing filter semantics and return behavior.

As per coding guidelines, "Always use triple quotes (""") for Python docstrings" and "Follow Google-style docstring format in Python."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/infrahub/auth.py` around lines 142 - 174, Add a Google-style
triple-quoted docstring to the _extract_effective_sso_group_names function that
succinctly describes its purpose, input parameters, filter semantics
(filter_pattern may be a string, list of strings, or None; patterns are compiled
as regular expressions and matched with re.match semantics; if a regex contains
a capture group the first group is used as the effective name, otherwise the
whole group_name is used), return type (set[str] of effective group names), and
behavior on invalid regex (logs a warning and returns an empty set). Keep it
concise and use parameter/returns sections consistent with Google-style
docstrings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/infrahub/auth.py`:
- Around line 216-235: The check-then-create loop for SSO groups is not
race-safe: concurrent requests can both see a missing name and create
duplicates; update the logic around Node.init/Node.new/Node.save so creation is
done in a DB-safe way—either perform the work inside a single transaction and
use a DB upsert/INSERT ... ON CONFLICT DO NOTHING (or SELECT ... FOR UPDATE) to
avoid duplicates, or keep the optimistic flow but catch
unique-constraint/IntegrityError from Node.save, then re-query
existing_group_names and, if the save failed due to duplicate, discard that
created name and continue; after handling conflicts always run the
NodeManager.query(CoreAccountGroup, filters={"name__values": ...}) to fetch the
canonical rows for infrahub_groups.

In `@docs/docs/guides/sso.mdx`:
- Around line 491-492: Replace the phrase "regex capture groups" with "regular
expression (regex) capture groups" on first use and remove the word "just" from
the sentence so it reads something like: "If your identity provider sends groups
with prefixes or paths (like `ldap/groups/network_automation`), you can use
regular expression (regex) capture groups to extract the relevant part as the
group name." Update the sentence where "regex capture groups" appears (the line
referencing `ldap/groups/network_automation`) to implement this wording and
maintain a professional, neutral tone.

---

Nitpick comments:
In `@backend/infrahub/auth.py`:
- Around line 142-174: Add a Google-style triple-quoted docstring to the
_extract_effective_sso_group_names function that succinctly describes its
purpose, input parameters, filter semantics (filter_pattern may be a string,
list of strings, or None; patterns are compiled as regular expressions and
matched with re.match semantics; if a regex contains a capture group the first
group is used as the effective name, otherwise the whole group_name is used),
return type (set[str] of effective group names), and behavior on invalid regex
(logs a warning and returns an empty set). Keep it concise and use
parameter/returns sections consistent with Google-style docstrings.

In `@backend/infrahub/config.py`:
- Around line 771-785: Add a Google-style docstring (triple-quoted) to the
validator method validate_sso_generate_groups_filter_regex (decorated with
`@field_validator`) that documents accepted input shapes (str, list[str], or
None), explains that each string is treated as a regular expression and will be
compiled, and describes failure behavior (raises ValueError when any pattern
fails to compile, original re.error chained). Keep the docstring brief and
include Args and Raises sections following Google-style conventions.

In `@backend/tests/unit/test_sso_generate_groups.py`:
- Around line 22-326: Several async test functions in this file (e.g.,
test_groups_not_created_when_disabled, test_groups_created_when_enabled,
test_filter_allows_matching_groups, test_no_filter_allows_all_groups,
test_filter_with_complex_pattern,
test_filter_does_not_duplicate_existing_extracted_group,
test_filter_extracts_group_name_from_capture_group,
test_filter_without_capture_group_uses_original_name,
test_filter_deduplicates_extracted_names, test_filter_skips_non_matching_groups)
are missing explicit return type annotations; update each async def signature to
include "-> None" (e.g., async def test_groups_not_created_when_disabled(...) ->
None:) to satisfy type-checker/style rules, leaving bodies unchanged. Ensure you
only modify the function signatures for the listed async test_* functions and
preserve existing imports and try/finally blocks.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e73427 and 2e2982b.

📒 Files selected for processing (5)
  • backend/infrahub/auth.py
  • backend/infrahub/config.py
  • backend/tests/unit/test_sso_generate_groups.py
  • docs/docs/guides/sso.mdx
  • docs/docs/reference/sso.mdx
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/docs/reference/sso.mdx

Comment thread backend/infrahub/auth.py Outdated
Comment thread docs/docs/guides/sso.mdx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/tests/unit/test_sso_generate_groups.py (1)

31-47: Prefer scoped patching over mutating shared global settings in tests.

Directly assigning to config.SETTINGS.security relies on global mutable state. This is brittle for parallel or interleaved test execution. Use patch.object(...) context managers for per-test isolation.

♻️ Suggested pattern
+from unittest.mock import patch
...
-        original_value = config.SETTINGS.security.sso_generate_groups
-        config.SETTINGS.security.sso_generate_groups = False
-
-        try:
+        with patch.object(config.SETTINGS.security, "sso_generate_groups", False):
             await signin_sso_account(
                 db=db, account_name="test-user", sso_groups=["existing-group", "new-group-1", "new-group-2"]
             )
-
-            groups = await NodeManager.query(db=db, schema=CoreAccountGroup)
-            group_names = {g.name.value for g in groups}
-
-            assert "existing-group" in group_names
-            assert "new-group-1" not in group_names
-            assert "new-group-2" not in group_names
-        finally:
-            config.SETTINGS.security.sso_generate_groups = original_value
+            groups = await NodeManager.query(db=db, schema=CoreAccountGroup)
+            group_names = {g.name.value for g in groups}
+            assert "existing-group" in group_names
+            assert "new-group-1" not in group_names
+            assert "new-group-2" not in group_names
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/unit/test_sso_generate_groups.py` around lines 31 - 47, The
test currently mutates global state by assigning
config.SETTINGS.security.sso_generate_groups directly; replace this with a
scoped patch (e.g., use unittest.mock.patch.object or pytest.MonkeyPatch) to
temporarily set config.SETTINGS.security.sso_generate_groups for the duration of
the test that calls signin_sso_account and then queries
NodeManager.query(CoreAccountGroup); patching ensures isolation and automatic
restore instead of manual try/finally mutation of
config.SETTINGS.security.sso_generate_groups.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/infrahub/auth.py`:
- Around line 166-167: The code currently uses "if match.lastindex:" before
doing effective_group_names.add(match.group(1)), which still allows None to be
added when group(1) is an optional capture; update both occurrences (the block
around match.lastindex leading to effective_group_names.add(...) and the similar
logic in the 217-224 region) to explicitly check the captured value is not None
(e.g., val = match.group(1); if match.lastindex and val is not None:
effective_group_names.add(val)) so only actual captured strings are added.

In `@backend/tests/unit/test_sso_generate_groups.py`:
- Around line 171-205: The test functions
test_invalid_regex_pattern_raises_error, test_valid_regex_pattern_accepted,
test_valid_regex_pattern_list_accepted, test_none_filter_accepted, and
test_invalid_regex_pattern_in_list_raises_error are missing explicit return type
annotations; update each function signature to include "-> None" (e.g., def
test_invalid_regex_pattern_raises_error() -> None:) to comply with the project's
typing standards and ensure all sync test methods declare return types.

---

Nitpick comments:
In `@backend/tests/unit/test_sso_generate_groups.py`:
- Around line 31-47: The test currently mutates global state by assigning
config.SETTINGS.security.sso_generate_groups directly; replace this with a
scoped patch (e.g., use unittest.mock.patch.object or pytest.MonkeyPatch) to
temporarily set config.SETTINGS.security.sso_generate_groups for the duration of
the test that calls signin_sso_account and then queries
NodeManager.query(CoreAccountGroup); patching ensures isolation and automatic
restore instead of manual try/finally mutation of
config.SETTINGS.security.sso_generate_groups.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fc750956-81a8-4de6-8051-0169f6707211

📥 Commits

Reviewing files that changed from the base of the PR and between 2e2982b and 28027ee.

📒 Files selected for processing (4)
  • backend/infrahub/auth.py
  • backend/infrahub/config.py
  • backend/tests/unit/test_sso_generate_groups.py
  • docs/docs/guides/sso.mdx
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/infrahub/config.py

Comment thread backend/infrahub/auth.py Outdated
Comment thread backend/tests/unit/test_sso_generate_groups.py Outdated
@codspeed-hq

codspeed-hq Bot commented Mar 5, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 12 untouched benchmarks


Comparing AlexanderGrooff:auto-create-sso-groups (1908d40) with stable (298fd37)

Open in CodSpeed

@gmazoyer gmazoyer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR is a pretty good start, clean separation of _extract_effective_sso_group_names as a testable helper, pragmatic concurrency handling for group creation, and thorough documentation with good coverage across unit and functional tests 👍

Users are never removed from groups when the IdP stops sending a claim. With auto-generation this becomes more significant since groups proliferate automatically and stale memberships accumulate silently. Worth documenting as a known limitation or addressing in a follow-up PR.

Comment thread backend/tests/unit/test_sso_generate_groups.py Outdated
Comment thread backend/infrahub/auth.py Outdated
Comment on lines +166 to +169
if match.lastindex:
effective_group_names.add(match.group(1))
else:
effective_group_names.add(group_name)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not verified this entirely but it seems this could lead to unexpected results (which could probably be highlighted by running test_filter_with_complex_pattern). I think that as regexp such as ^(dev|prod)-.*-team$ would produce groups like dev and prod instead f groups like dev-foo-team and prod-bar-team.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense right? If you want to capture a part of a group name, using regex groups is the way to go.

The reason I do this is because syncing LDAP groups to SSO causes groupnames to appear like ldap/groups/team_name_1. In that case, I want to capture only team_name_1 using a pattern like ^ldap\/groups\/(.*)

Comment thread backend/infrahub/auth.py
effective_group_names: set[str] = set()
for group_name in sso_groups:
for compiled_filter in compiled_filters:
match = compiled_filter.match(group_name)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code uses compiled_filter.match(group_name) which anchors at the start of the string. A filter like groups/(\w+) would* not* match ldap/groups/network because the pattern is expected to match from position 0. This should either use re.search and document that patterns are searched within the string or document explicitly that patterns are anchored to the start (like adding "Patterns are matched from the start of the group name" to the docs).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import re

path = "ldap/groups/admins"
match = re.search(r"ldap/groups/(\w+)", path)

match.group(0)  # "ldap/groups/admins" — the full match
match.group(1)  # "admins" — the captured part

Also the current capture would match pattern containing - e.g. full-admin

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to keep using match to prevent matching unwanted cases through search. I've updated the docs to match this.

What do you think?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
backend/infrahub/auth.py (1)

232-243: ⚠️ Potential issue | 🟠 Major

Catch the HFID uniqueness conflict in this retry path.

Line 232 only retries ValidationError, but create_node() can also surface duplicate-name conflicts as HFIDViolatedError. In that case concurrent SSO logins still fail here instead of reloading the group that won the race.

🛠️ Minimal fix
-        except ValidationError:
+        except (ValidationError, HFIDViolatedError):
             concurrent_groups = await NodeManager.query(
                 db=db,
                 schema=CoreAccountGroup,
                 filters={"name__values": [effective_name]},
                 prefetch_relationships=True,
             )

You'll also need to add HFIDViolatedError to the exception imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/infrahub/auth.py` around lines 232 - 243, The except block currently
only handles ValidationError but create_node() can also raise HFIDViolatedError;
update the exception handler to catch both ValidationError and
HFIDViolatedError, perform the same retry logic that queries
NodeManager.query(schema=CoreAccountGroup,
filters={"name__values":[effective_name]}, prefetch_relationships=True), extend
infrahub_groups with any concurrent_groups and update existing_group_names from
group.name.value, and re-raise only if no concurrent_groups are found; also add
HFIDViolatedError to the exception imports so the symbol is available.
🧹 Nitpick comments (1)
backend/tests/unit/test_sso_generate_groups.py (1)

55-89: This test never reaches the conflict-reload branch.

Because create_node on Line 77 always succeeds, the fallback query path for concurrent creation is never exercised here. Please add a companion case that makes create_node raise the uniqueness-conflict exception and asserts the already-created group is reloaded instead of failing the login.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/unit/test_sso_generate_groups.py` around lines 55 - 89, Add a
companion unit case to simulate a uniqueness-conflict during safe creation:
duplicate the test_signin_uses_safe_create_path_for_missing_groups setup but
patch infrahub.auth.create_node to raise the uniqueness-conflict exception on
its first await (then succeed or remain failing as appropriate), keep query
side_effect such that the first two queries return [] and the third returns the
already-created fake_group, call signin_sso_account the same way, and assert
that after the create_node raises the uniqueness error the code falls back to
re-querying and ultimately loads the existing group (verify create_node was
awaited and that NodeManager.query / Node.init or the
fake_db.schema.get_node_schema were called as expected). Ensure you reference
the same symbols: signin_sso_account, create_node, NodeManager.query, Node.init,
and the uniqueness-conflict exception you use in production.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/docs/guides/sso.mdx`:
- Around line 372-373: The in-page anchor in the paragraph uses the wrong slug:
update the link target in the sentence currently pointing to
"#step-4-enable-automatic-group-creation-optional" so it matches the generated
heading slug "#enable-automatic-group-creation-optional" (or alternatively
change the subsection heading to include "Step 4" so its slug becomes
"#step-4-enable-automatic-group-creation-optional"); locate the link text in
docs/guides/sso.mdx and adjust either the href or the heading text to make the
two slugs identical.

---

Duplicate comments:
In `@backend/infrahub/auth.py`:
- Around line 232-243: The except block currently only handles ValidationError
but create_node() can also raise HFIDViolatedError; update the exception handler
to catch both ValidationError and HFIDViolatedError, perform the same retry
logic that queries NodeManager.query(schema=CoreAccountGroup,
filters={"name__values":[effective_name]}, prefetch_relationships=True), extend
infrahub_groups with any concurrent_groups and update existing_group_names from
group.name.value, and re-raise only if no concurrent_groups are found; also add
HFIDViolatedError to the exception imports so the symbol is available.

---

Nitpick comments:
In `@backend/tests/unit/test_sso_generate_groups.py`:
- Around line 55-89: Add a companion unit case to simulate a uniqueness-conflict
during safe creation: duplicate the
test_signin_uses_safe_create_path_for_missing_groups setup but patch
infrahub.auth.create_node to raise the uniqueness-conflict exception on its
first await (then succeed or remain failing as appropriate), keep query
side_effect such that the first two queries return [] and the third returns the
already-created fake_group, call signin_sso_account the same way, and assert
that after the create_node raises the uniqueness error the code falls back to
re-querying and ultimately loads the existing group (verify create_node was
awaited and that NodeManager.query / Node.init or the
fake_db.schema.get_node_schema were called as expected). Ensure you reference
the same symbols: signin_sso_account, create_node, NodeManager.query, Node.init,
and the uniqueness-conflict exception you use in production.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c715a40b-cacf-47e3-b459-b2d120950d1e

📥 Commits

Reviewing files that changed from the base of the PR and between 28027ee and 119e4c1.

📒 Files selected for processing (5)
  • backend/infrahub/auth.py
  • backend/infrahub/config.py
  • backend/tests/functional/api/test_sso_groups.py
  • backend/tests/unit/test_sso_generate_groups.py
  • docs/docs/guides/sso.mdx
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/infrahub/config.py
  • backend/tests/functional/api/test_sso_groups.py

Comment thread docs/docs/guides/sso.mdx Outdated
@AlexanderGrooff AlexanderGrooff force-pushed the auto-create-sso-groups branch from 2b989fe to 1908d40 Compare March 16, 2026 14:50
@github-actions

github-actions Bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

This PR has been inactive for 2 weeks and has been marked as stale.

If you're still working on this, please:

  • Push new commits, or
  • Leave a comment to indicate this PR is still active

The stale label will be removed automatically when there's new activity.

@github-actions github-actions Bot added the stale Marks stale issues and pull requests label Apr 2, 2026
@wvandeun

wvandeun commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

@AlexanderGrooff thanks for the contribution. We have not forgotten about this. We like the overall direction this is going. But before we can decide on the final implementation, we need to get additional requirements.

We'll get back to you ASAP.

@github-actions github-actions Bot removed the stale Marks stale issues and pull requests label Apr 10, 2026
@BeArchiTek

Copy link
Copy Markdown
Contributor

Closing this PR as the engineering team redid the feature as part of 1.10 delivery

@BeArchiTek BeArchiTek closed this Jun 15, 2026
@AlexanderGrooff AlexanderGrooff deleted the auto-create-sso-groups branch June 15, 2026 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants