Metadata-Version: 2.5
Name: netbox-oidc-group-sync
Version: 0.1.0
Summary: A python-social-auth pipeline step that syncs NetBox Django groups and superuser status from an OIDC 'groups' claim
Project-URL: Homepage, https://github.com/dcode/netbox-oidc-group-sync
Project-URL: Documentation, https://dcode.github.io/netbox-oidc-group-sync/
Project-URL: Issues, https://github.com/dcode/netbox-oidc-group-sync/issues
Project-URL: Source, https://github.com/dcode/netbox-oidc-group-sync
Project-URL: Changelog, https://github.com/dcode/netbox-oidc-group-sync/releases
Author-email: Derek Ditch <dcode@users.noreply.github.com>
License-Expression: MIT
License-File: LICENSE
Keywords: authentik,django,netbox,oidc,social-auth,sso
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: Django
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: django>=4.2
Requires-Dist: social-auth-core>=4.5
Provides-Extra: dev
Requires-Dist: mypy>=1.15; extra == 'dev'
Requires-Dist: pre-commit>=4.2; extra == 'dev'
Requires-Dist: pytest-cov>=6.0; extra == 'dev'
Requires-Dist: pytest-django>=4.9; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.11; extra == 'dev'
Description-Content-Type: text/markdown

# netbox-oidc-group-sync

A [python-social-auth](https://github.com/python-social-auth/social-core) pipeline step that syncs NetBox
Django groups and `is_superuser`/`is_staff` status from an OIDC `groups` claim.

## Why this exists

NetBox Community ships two independent group-sync code paths under `netbox.authentication`:

- `RemoteUserBackend.configure_groups()` implements dynamic, claim-based group sync (with optional
  auto-create) plus superuser evaluation from `REMOTE_AUTH_SUPERUSER_GROUPS` -- but it's only ever invoked
  from `RemoteUserBackend.authenticate()`, Django's HTTP-header remote-auth path (a `REMOTE_USER` header set
  by an upstream reverse proxy). It is **never reached by a social-auth/OIDC login**.
- `user_default_groups_handler`, the step actually wired into `SOCIAL_AUTH_PIPELINE` for social-auth backends
  (including OIDC), only assigns a *static* `REMOTE_AUTH_DEFAULT_GROUPS` list. It never reads a claim and
  never touches `is_superuser`.

So if you're using `social_core.backends.open_id_connect.OpenIdConnectAuth` (or another social-auth OIDC
backend) as your NetBox login method, setting `REMOTE_AUTH_GROUP_SYNC_ENABLED` / `AUTO_CREATE_GROUPS` /
`SUPERUSER_GROUPS` in your NetBox configuration has **no effect whatsoever** -- those settings are consumed
exclusively by the header-based backend. NetBox Labs' Enterprise product solves this with a proprietary
pipeline step (`nbc_auth_extensions.azure_authentication.azuread_map_groups`, [Enterprise-only, Entra
ID-specific](https://netboxlabs.com/docs/enterprise/nbe-oidc-sso/#microsoft-entra-id-group-mapping)).

`sync_groups` closes that gap for Community: dropped into `SOCIAL_AUTH_PIPELINE` in place of
`user_default_groups_handler`, it re-implements `configure_groups()`'s logic against the OIDC response
instead of an HTTP header, reusing the exact same `REMOTE_AUTH_*` settings NetBox already defines -- no new
configuration surface.

## Installation

```sh
pip install netbox-oidc-group-sync
```

Then, in your NetBox `configuration.py` (or an `extraConfig` block if you're deploying via the
[netbox-chart](https://github.com/netbox-community/netbox-chart) Helm chart):

```python
SOCIAL_AUTH_PIPELINE = (
    "social_core.pipeline.social_auth.social_details",
    "social_core.pipeline.social_auth.social_uid",
    "social_core.pipeline.social_auth.social_user",
    "social_core.pipeline.user.get_username",
    "social_core.pipeline.user.create_user",
    "social_core.pipeline.social_auth.associate_user",
    "netbox_oidc_group_sync.sync_groups",  # replaces netbox.authentication.user_default_groups_handler
    "social_core.pipeline.social_auth.load_extra_data",
    "social_core.pipeline.user.user_details",
)
```

The package itself needs to be on the NetBox pod's Python path -- if you're running the stock
`netboxcommunity/netbox` image, that means building a derivative image that `pip install`s it (see
[`docs/installation.md`](docs/installation.md) for a working Dockerfile example).

## Configuration

Reuses NetBox's own settings -- nothing new to configure beyond what a `RemoteUserBackend` deployment would
already set:

| Setting | Effect |
|---|---|
| `REMOTE_AUTH_GROUP_SYNC_ENABLED` | Master on/off switch. `sync_groups` no-ops entirely when falsy. |
| `REMOTE_AUTH_GROUP_HEADER` | The key to look up in the OIDC claims/userinfo `response` dict for the user's group list. Named for its original HTTP-header use case; repurposed here as a claim key, which doesn't conflict with anything since it has no effect on social-auth logins upstream. |
| `REMOTE_AUTH_AUTO_CREATE_GROUPS` | Create a Django `Group` for a claimed group name that doesn't exist yet, instead of skipping it with a logged error. |
| `REMOTE_AUTH_SUPERUSER_GROUPS` | Group names that grant `is_superuser`/`is_staff` when present in the user's synced claim groups. |
| `REMOTE_AUTH_SUPERUSERS` | Usernames that are always superusers, regardless of group membership. |

Group membership is a **full sync**, not additive: a user's Django groups are set to exactly what the claim
says on every login (matching `configure_groups()`'s own semantics), including clearing all groups -- and
revoking superuser status -- if the claim comes back empty or absent.

## Development

```sh
uv sync
uv run pre-commit install
uv run pytest --cov=src --cov-report=term-missing
```

Docs are built with [Zensical](https://github.com/squidfunk/zensical):

```sh
uvx zensical serve
```
