Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
0fe4f4f
Add SCIM
matmair Aug 25, 2026
fd84e05
also test scim
matmair Aug 25, 2026
eaa9246
extend testing with conformance suite
matmair Aug 25, 2026
ba5792c
fix test suite results
matmair Aug 25, 2026
aa959b7
coverage completion
matmair Aug 26, 2026
4701385
fil test gaps
matmair Aug 26, 2026
5f3b64d
add missing schema values
matmair Aug 26, 2026
96be708
fix more type issues
matmair Aug 26, 2026
731485a
Merge branch 'master' into feat(backend)--add-scim
matmair Aug 27, 2026
43864f5
fix error view
matmair Aug 27, 2026
21c081d
remove route from OpenAPI
matmair Aug 27, 2026
9c6c185
Merge branch 'master' into feat(backend)--add-scim
matmair Aug 28, 2026
1c302ba
[FR] Make OIDC configurable via API/while running
matmair Aug 28, 2026
52b8373
User permissions check for Attachment API (#12689)
matmair Aug 28, 2026
8c55ec2
enable oidc proivder by default
matmair Aug 28, 2026
2273258
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair Aug 28, 2026
4b02594
add .well-known entry for OIDC server
matmair Aug 28, 2026
ec920f0
Merge branch 'master' into feat(backend)--add-scim
matmair Aug 29, 2026
cd53ea4
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair Aug 29, 2026
a8426c6
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair Aug 29, 2026
71b1501
add default client registration
matmair Aug 29, 2026
f3eddd9
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair Aug 30, 2026
2aa0c1d
add changelog entry
matmair Aug 30, 2026
21116f9
add codeowners
matmair Aug 30, 2026
4e39854
Merge branch 'feat(backend)--add-scim' of https://github.com/matmair/…
matmair Aug 30, 2026
1783d12
remove redundant info
matmair Aug 30, 2026
fd85ce1
re-name and restructure panel
matmair Aug 30, 2026
12e5e19
add a smal explainer section on top
matmair Aug 30, 2026
0100230
flesh out section a bit more
matmair Aug 30, 2026
fca2fee
change name
matmair Aug 30, 2026
0995463
basic sso panel
matmair Aug 30, 2026
8eb154d
refactor rendering
matmair Aug 30, 2026
dbc3a43
add api and mgmt functions
matmair Aug 30, 2026
4e15dda
add create/delete options
matmair Aug 30, 2026
c082267
simplify test
matmair Aug 30, 2026
5621702
handle secret generation
matmair Aug 30, 2026
0473b35
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair Aug 31, 2026
6b8c15b
fix merge
matmair Aug 31, 2026
fc3141e
update coverage
matmair Aug 31, 2026
1818909
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair Aug 31, 2026
c610bfe
add test
matmair Aug 31, 2026
b3a6bc1
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair Aug 31, 2026
6b40dbb
[FR] Make OIDC SSO configurable via API/while running
matmair Sep 1, 2026
f5b1f11
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair Sep 1, 2026
d5df906
add mgmt surfacce
matmair Sep 1, 2026
c97dfb1
Merge branch 'master' into matmair/issue4715
matmair Sep 1, 2026
78c24ad
bump API version
matmair Sep 1, 2026
bd1b9ef
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair Sep 1, 2026
6a4e4f8
update override behaviour
matmair Sep 2, 2026
7c759cd
Merge branch 'master' into matmair/issue4715
matmair Sep 2, 2026
9bb7dce
ensure registrations are unique
matmair Sep 2, 2026
da2df38
Merge branch 'master' into default-enable-oidc
matmair Sep 2, 2026
24b3116
add test
matmair Sep 2, 2026
db40a59
Merge branch 'master' into default-enable-oidc
matmair Sep 2, 2026
3328c6d
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair Sep 3, 2026
0a18383
small doc fixes
matmair Sep 3, 2026
ba2c100
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair Sep 3, 2026
65cfeda
reduce user friction
matmair Sep 3, 2026
aa0fc2a
add better labels
matmair Sep 3, 2026
054684c
make it more clear that this is only shown once
matmair Sep 3, 2026
b6e1648
add regenerate function
matmair Sep 3, 2026
9c95f3a
update tests
matmair Sep 3, 2026
56e1d72
simplify test calls
matmair Sep 3, 2026
831fa1a
fix test
matmair Sep 3, 2026
2aeb171
[FR] Make OIDC SSO configurable via API/while running
matmair Sep 3, 2026
f655918
clean up docs
matmair Sep 3, 2026
4a1bc02
add general docs to frontend
matmair Sep 3, 2026
a3bf544
update texts
matmair Sep 3, 2026
9bc6da4
rename provider buttons
matmair Sep 3, 2026
9c0cc30
add specific oidc dialog
matmair Sep 3, 2026
729db22
Merge branch 'default-enable-oidc' of https://github.com/matmair/Inve…
matmair Sep 4, 2026
26358ab
Merge branch 'master' of https://github.com/inventree/InvenTree into …
matmair Sep 4, 2026
7a20a70
Merge branch 'master' into matmair/issue4715
matmair Sep 4, 2026
219d072
Merge branch 'master' into matmair/issue4715
matmair Sep 5, 2026
cbf10ee
Merge branch 'master' into matmair/issue4715
matmair Sep 5, 2026
1dee3d7
Merge branch 'master' into matmair/issue4715
matmair Sep 6, 2026
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
Binary file not shown.
58 changes: 23 additions & 35 deletions docs/docs/settings/SSO.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@ InvenTree provides the possibility to use 3rd party services to authenticate use

## SSO Configuration

The basic requirements for configuring SSO are outlined below:
The basic steps for configuring SSO are:

1. Enable backend for each required SSO provider(s) in the [config file or environment variables](../start/config.md#single-sign-on).
1. Create an external *app* with your provider of choice
1. Add the required client configurations as a *Social application* in the [Database Admin interface](./db_admin.md).
1. Configure the *callback* URL for the external app.
1. Enable SSO for the users in the [global settings](../settings/global.md).
1. Configure [e-mail](../settings/email.md).
1. Add the backend for the intended SSO provider(s) in the [config file](../start/config.md#configuration-file) or environment variables.
2. Create an external *app* with the provider of choice
3. Add the required client configurations as a *Social application* in the [Database Admin interface](./db_admin.md).
4. Configure the *callback* URL for the external app.
5. Enable SSO for the users in the [global settings](../settings/global.md).
6. Configure [e-mail](../settings/email.md).

!!! info "Two-step setup"
Provider modules are enabled in `config.yaml` (or environment variables). Client IDs, secrets, and site assignments are **not** configured there — they must be added as *Social applications* in the [Database Admin interface](./db_admin.md). SSO providers cannot be configured via the InvenTree API.
Provider modules are enabled in `config.yaml` (or environment variables). Client IDs, secrets, and site assignments are configured there or in the database via the Admin Center or the [Database Admin interface](./db_admin.md).

### Enable Provider Backends
### Add Provider Backends

The first step is to ensure that the required provider modules are installed, via your installation [configuration file](../start/config.md#single-sign-on).
The first step is to ensure that the required provider modules are installed, via the installations [configuration file](../start/config.md#single-sign-on).

There are two variables in the configuration file which define the operation of SSO:

Expand All @@ -41,14 +41,10 @@ In the example below, SSO provider modules are activated for *google*, *github*

{{ image("settings/sso_config.png", "SSO Config") }}

!!! info "Provider Module Format"
Note that the provider modules specified in `social_backends` must be prefixed with `allauth.socialaccounts.providers`

!!! warning "Provider Documentation"
We do not provide any specific documentation for each provider module. Please refer to the [django-allauth documentation](https://docs.allauth.org/en/latest/socialaccount/providers/index.html) for more information.

!!! tip "Restart Server"
As the [configuration file](../start/config.md) is only read when the server is launched, ensure you restart the server after editing the file.
As the [configuration file](../start/config.md) is only read when the server is launched, ensure you restart the server after editing the file.

### Create Provider App

Expand All @@ -64,40 +60,32 @@ In general, the external app will generate a *key* and *secret* pair - although

### Add Client Configurations

Once your external SSO app has been created, you need to create a new *Social application* entry in the [Database Admin interface](./db_admin.md) (under **Social accounts** → **Social applications** — not in the Admin Center).

#### Create Social Application

Select **Add social application** (top right of the social applications list). Social applications are listed under the **Social accounts** section of the Database Admin — not in the InvenTree Admin Center settings screens.
Once you have added the provider, you need to create a new *Social application* entry in the Admin Center (under Identity Federation / SSO ) or in the [Database Admin interface](./db_admin.md) (under **Social accounts** → **Social applications**).

{{ image("settings/social_account_add.png", "Database Admin — Social applications section") }}
#### Admin Database Interface

#### Configure Social Application
1. Select **Add social application** (top right of the social applications list). Social applications are listed under the **Social accounts** section.

Configure the social application entry with the app details:
2. Configure the social application entry with the specifics provider details:

{{ image("settings/social_application_configure.png", "Configure Social Application") }}
{{ image("settings/social_application_configure.png", "Sample Social Application Configuration") }}

- Select the *provider* type as required
- Provide a *name* for the application (note that this should match the *name* used for any custom settings provided in the configuration file)
- Add client and secret data for your external SSO app
- Provide a *name* for the social application (note that this must match the *name* used for any custom settings provided in the configuration file)
- Add client and secret data from your external SSO provider / application
- Add the *site* which you want to provide access for this SSO app
- Save the new application entry when configuration is finished
- Save the new entry

!!! warning "Site Selection"
You *must* assign the new application to at least one available site domain

!!! tip "Fix Your Mistakes"
You can always return to edit or adjust the social application details later

!!! success "Multiple Applications"
To provide support for multiple SSO applications, simply repeat this process and create another social application entry
Multiple SSO applications can be configured by repeating this process and creating multiple entries.

### Configure Callback URL

The external SSO application must be provided with a *callback* URL - a URL by which it can communicate with the InvenTree server. The specific *name* that the external SSO application uses for this callback URL may vary, with some authentication applications referring to it with other names such as *reply* or *redirect*.
Most external SSO providers must be provided with a *callback* URL - a URL by which it can communicate with the InvenTree server. The specific *name* that the external SSO application uses for this callback URL may vary, with some authentication applications referring to it with other names such as *reply* or *redirect*.

In any case, the URL is is specific to your installation and the SSO provider. The general pattern for this URL is: `{% raw %}<hostname>/accounts/<provider>/login/callback/{% endraw %}`.
In any case, the URL is is specific to your installation and the SSO provider. The general pattern for this URL is: `{% raw %}<hostname>/accounts/<provider>/login/callback/{% endraw %}` but can vary. Read the specific provider documentation by django-allauth for exact information.

!!! success "Works for Local Installs"
Your server does not need to be "public facing" for this to work. For example the URL `http://localhost:1234/accounts/github/login/callback/` would be perfectly valid!
Expand All @@ -114,7 +102,7 @@ Now that the social application is created, you need to enable SSO authenticatio

In the [settings screen](./global.md), navigate to the *Login Settings* panel. Here you will see the required configuration options to enable SSO:

{{ image("settings/sso_settings.png", "SSO Settings") }}
{{ image("settings/social_account_add.png", "Database Admin — Social applications section") }}

| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
Expand Down
5 changes: 4 additions & 1 deletion src/backend/InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
"""InvenTree API version information."""

# InvenTree API version
INVENTREE_API_VERSION = 542
INVENTREE_API_VERSION = 543
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""

INVENTREE_API_TEXT = """

v543 -> 2026-09-05 : https://github.com/inventree/InvenTree/pull/12762
- Adds admin APIs for managing SSO applications

v542 -> 2026-09-03 : https://github.com/inventree/InvenTree/pull/12731
- Adds management APIs for oAuth2 provider applications

Expand Down
1 change: 1 addition & 0 deletions src/backend/InvenTree/InvenTree/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,3 +492,4 @@ def get_field_info(self, field):


InvenTreeMetadata.label_lookup[DependentField] = 'dependent field'
InvenTreeMetadata.label_lookup[serializers.JSONField] = 'json'
20 changes: 13 additions & 7 deletions src/backend/InvenTree/InvenTree/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,11 +789,6 @@
# If a SITE_ID is specified
SITE_ID = get_setting('INVENTREE_SITE_ID', 'site_id', 1 if SITE_MULTI else None)

# Load the allauth social backends
SOCIAL_BACKENDS = get_setting(
'INVENTREE_SOCIAL_BACKENDS', 'social_backends', [], typecast=list
)

if not SITE_MULTI:
INSTALLED_APPS.remove('django.contrib.sites')

Expand Down Expand Up @@ -989,8 +984,16 @@
FRONTEND_SETTINGS = config.get_frontend_settings(debug=DEBUG)
FRONTEND_URL_BASE = FRONTEND_SETTINGS['base_url']

# Load the allauth social backends
SOCIAL_BACKENDS = get_setting(
'INVENTREE_SOCIAL_BACKENDS', 'social_backends', [], typecast=list
)

DEFAULT_SOCIAL = ['saml', 'openid_connect']
_SOCIAL_BACKENDS = {*DEFAULT_SOCIAL, *SOCIAL_BACKENDS}

# region auth
for app in SOCIAL_BACKENDS: # pragma: no cover
for app in _SOCIAL_BACKENDS: # pragma: no cover
# Ensure that the app starts with 'allauth.socialaccount.providers'
social_prefix = 'allauth.socialaccount.providers.'

Expand All @@ -999,9 +1002,12 @@

INSTALLED_APPS.append(app)

SOCIALACCOUNT_PROVIDERS = get_setting(
SOCIALACCOUNT_PROVIDERS = {a: {} for a in DEFAULT_SOCIAL}
_PROVIDER_SETTINGS = get_setting(
'INVENTREE_SOCIAL_PROVIDERS', 'social_providers', None, typecast=dict
)
if _PROVIDER_SETTINGS and isinstance(_PROVIDER_SETTINGS, dict):
SOCIALACCOUNT_PROVIDERS.update(_PROVIDER_SETTINGS)

SOCIALACCOUNT_STORE_TOKENS = True

Expand Down
72 changes: 72 additions & 0 deletions src/backend/InvenTree/common/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import django_filters.rest_framework.filters as rest_filters
import django_q.models
import django_q.tasks
from allauth.socialaccount import providers
from allauth.socialaccount.models import SocialApp
from django_filters.rest_framework.filterset import FilterSet
from djmoney.contrib.exchange.models import ExchangeBackend, Rate
from drf_spectacular.utils import (
Expand Down Expand Up @@ -1765,6 +1767,76 @@ def create(self, request, *args, **kwargs):
return Response({'status': 'ok'})


class SocialAppSerializer(serializers.ModelSerializer):
"""Serializer for SocialApp records."""

provider = serializers.ChoiceField(label=_('Provider'), choices=[])
name = serializers.CharField(
label=_('Name'),
help_text=_(
'Human friendly name for the application - will be displayed to users'
),
)
provider_id = serializers.CharField(
label=_('Provider ID'),
help_text=_(
'Unique identifier - required for generic providers that can be configured multiple times such as SAML or OpenID Connect'
),
required=False,
allow_blank=True,
)

class Meta:
"""Meta options for SocialAppSerializer."""

model = SocialApp
fields = [
'id',
'name',
'provider',
'provider_id',
'client_id',
'secret',
'settings',
]
read_only_fields = ['id']

def __init__(self, *args, **kwargs):
"""Populate provider choices from the active allauth registry."""
super().__init__(*args, **kwargs)
self.fields['provider'].choices = providers.registry.as_choices()

def validate_provider(self, value):
"""Ensure the selected provider is supported by the active allauth registry."""
if value not in [provider[0] for provider in providers.registry.as_choices()]:
raise serializers.ValidationError(_('Provider is not supported'))
return value

def validate(self, data):
"""Ensure that the provider is unique across all SocialApp records."""
provider = data.get('provider', None)
if (
provider
and SocialApp.objects.filter(provider=provider).exists()
and provider not in ('saml', 'openid_connect')
):
raise serializers.ValidationError({
'provider': _('A SocialApp with this provider already exists')
})

return data


class SocialAppViewSet(CleanModelViewSet):
"""Manage a SocialApp (client side) application."""

queryset = SocialApp.objects.all()
serializer_class = SocialAppSerializer


admin_router.register('sso', SocialAppViewSet, basename='api-sso')


class ApplicationViewSet(CleanModelViewSet):
"""Manage a oAuth2 (provider side) application."""

Expand Down
43 changes: 43 additions & 0 deletions src/backend/InvenTree/common/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from django.test.utils import override_settings
from django.urls import reverse

from allauth.socialaccount import providers
from PIL import Image
from taggit.models import Tag

Expand Down Expand Up @@ -56,6 +57,48 @@ def test_data_output_list(self):
self.assertEqual(len(response.data), 5)


class SocialAppAPITests(InvenTreeAPITestCase):
"""Tests for the SocialApp API serializer."""

roles = 'all'

def test_provider_choices_and_validation(self):
"""Provider choices should come from the allauth registry and reject invalid values."""
from common.api import SocialAppSerializer

available = [provider[0] for provider in providers.registry.as_choices()]

serializer = SocialAppSerializer()
provider_field = serializer.fields['provider']

self.assertDictEqual(
dict(provider_field.choices), dict(providers.registry.as_choices())
)
self.assertCountEqual(available, list(provider_field.choices.keys()))

url = reverse('api-sso-list')
options = self.options(url)
actions = options.data['actions']['GET']

self.assertIn('provider', actions)
self.assertCountEqual(
[choice['value'] for choice in actions['provider']['choices']], available
)
self.assertEqual(
{
choice['value']: choice['display_name']
for choice in actions['provider']['choices']
},
dict(providers.registry.as_choices()),
)

invalid = SocialAppSerializer(
data={'name': 'Bad Provider', 'provider': 'not-a-provider'}
)
self.assertFalse(invalid.is_valid())
self.assertIn('provider', invalid.errors)


class ParameterAPITests(InvenTreeAPITestCase):
"""Tests for the Parameter API."""

Expand Down
1 change: 1 addition & 0 deletions src/frontend/lib/enums/ApiEndpoints.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ export enum ApiEndpoints {
scim_generate = 'admin/scim/generate/',
scim_disable = 'admin/scim/disable/',
config_list = 'admin/config/',
sso_list = 'admin/sso/',
parameter_list = 'parameter/',
parameter_template_list = 'parameter/template/',
tag_list = 'tag/',
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/lib/types/Forms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ export type ApiFormFieldType = {
| 'nested object'
| 'dependent field'
| 'table'
| 'tags';
| 'tags'
| 'json';
api_url?: string;
pk_field?: string;
model?: ModelType;
Expand Down
10 changes: 10 additions & 0 deletions src/frontend/src/components/forms/fields/ApiFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ChoiceField } from './ChoiceField';
import DateField from './DateField';
import { DependentField } from './DependentField';
import IconField from './IconField';
import { JsonField } from './JsonField';
import { NestedObjectField } from './NestedObjectField';
import NumberField from './NumberField';
import { RelatedModelField } from './RelatedModelField';
Expand Down Expand Up @@ -295,6 +296,15 @@ export function ApiFormField({
return (
<TagsField controller={controller} definition={fieldDefinition} />
);
case 'json':
return (
<JsonField
controller={controller}
definition={fieldDefinition}
fieldName={fieldName}
onChange={onChange}
/>
);
default:
return (
<Alert color='red' title={t`Error`}>
Expand Down
Loading
Loading