Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/samlv2 #549

Merged
merged 124 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
124 commits
Select commit Hold shift + click to select a range
36e0c7c
WIP
eric-intuitem Jun 2, 2024
0567ebe
WIP
eric-intuitem Jun 2, 2024
6859646
feat: saml
Mohamed-Hacene Jun 11, 2024
ce0380a
Merge branch 'feat/samlv2' of github.com:intuitem/ciso-assistant-comm…
Mohamed-Hacene Jun 11, 2024
7e77e53
SAML PoC
nas-tabchiche Jun 11, 2024
1633a3d
Add CRUD endpoints for identity providers
nas-tabchiche Jun 11, 2024
d7a0842
Put identity-providers endpoints in core urls
nas-tabchiche Jun 12, 2024
8f41fbb
Rewrite IdentityProvider model
nas-tabchiche Jun 12, 2024
fc97257
Allow CRUD operations on identity providers from the frontend
nas-tabchiche Jun 12, 2024
0a18721
Merge branch 'main' into feat/samlv2
nas-tabchiche Jun 12, 2024
de65349
Improve frontend IdP CRUD
nas-tabchiche Jun 12, 2024
1d23051
Serialize proper provider display name
nas-tabchiche Jun 12, 2024
ac260c3
Display provider name in model table
nas-tabchiche Jun 12, 2024
e7a68e8
feat: entra config
Mohamed-Hacene Jun 12, 2024
dea7394
Remove dead code
nas-tabchiche Jun 13, 2024
43cae5f
Update SocialAccountAdapter to use IdentityProvider model instead of …
nas-tabchiche Jun 13, 2024
c80a342
Refactor IdentityProviderWriteSerializer
nas-tabchiche Jun 13, 2024
c6ab52c
Use TextArea for x509cert field
nas-tabchiche Jun 13, 2024
c700f2a
Update IdentityProviderSchema
nas-tabchiche Jun 13, 2024
487a4fd
Update identity provider ModelTable
nas-tabchiche Jun 13, 2024
0a1c7ae
Properly display localized table headins in ModelTable
nas-tabchiche Jun 13, 2024
b004856
Display fallback label on TextArea
nas-tabchiche Jun 13, 2024
39f47e4
feat: add lxml dependency
Mohamed-Hacene Jun 13, 2024
f95c4eb
Properly get email address from SAML response
nas-tabchiche Jun 13, 2024
0278e90
Update IdentityProvider serializers
nas-tabchiche Jun 13, 2024
ca923f0
Update IdentityProviderSchema
nas-tabchiche Jun 13, 2024
71ad06e
Use m translation instead of localItems
nas-tabchiche Jun 13, 2024
0390b61
feat: handle sso failure
Mohamed-Hacene Jun 13, 2024
5fc50ba
Merge branch 'main' into poc/saml
Mohamed-Hacene Jun 13, 2024
67c8f89
style: improve sso button
Mohamed-Hacene Jun 13, 2024
26ccce1
chore: update translations with Fink 🐦
Mohamed-Hacene Jun 13, 2024
6ad7bc2
chore: update translations with Fink 🐦
Mohamed-Hacene Jun 13, 2024
4836cb0
fix: synchronize language
Mohamed-Hacene Jun 13, 2024
822b4c7
Fix SAML urlpatterns
nas-tabchiche Jun 14, 2024
85c220e
Create settings app
nas-tabchiche Jun 14, 2024
bc8fab9
Create SSOSettings proxy model
nas-tabchiche Jun 14, 2024
320d32b
WIP
nas-tabchiche Jun 14, 2024
597f675
Merge branch 'main' into feat/samlv2
nas-tabchiche Jun 18, 2024
07d5888
Implement proper API routing for settings
nas-tabchiche Jun 18, 2024
c1dd0a6
Refactor SSOSettings model
nas-tabchiche Jun 18, 2024
bdeec8d
Add view_globalsettings permission to Administrator role
nas-tabchiche Jun 18, 2024
0718cd1
Fix urlpatterns
nas-tabchiche Jun 18, 2024
01e5b94
Fix settings serialization
nas-tabchiche Jun 18, 2024
3a0fe23
Fix settings ModelForm and schema
nas-tabchiche Jun 18, 2024
ec43792
Supply default value for GlobalSettings.value
nas-tabchiche Jun 18, 2024
d51d485
Fix SSO Settings form
nas-tabchiche Jun 18, 2024
bb9aa81
Make SSOSettings a regular unmanaged model instead of an unmanaged pr…
nas-tabchiche Jun 18, 2024
98971b1
Add django-allauth and lxml dependencies
nas-tabchiche Jun 18, 2024
22e16ff
chore: Run ruff formatter
nas-tabchiche Jun 18, 2024
554ee4c
Remove test_saml.py
nas-tabchiche Jun 18, 2024
af015aa
Add django-allauth[saml] dependency
nas-tabchiche Jun 18, 2024
75f603d
chore: Run prettier
nas-tabchiche Jun 18, 2024
5cdc3db
Initialize STATIC_URL outside of debug
nas-tabchiche Jun 19, 2024
4b3f7d1
Throw a 404 if SSO is not set up
nas-tabchiche Jun 19, 2024
c2eacc7
Add is_enabled field so SSOSettings
nas-tabchiche Jun 19, 2024
2bf19bf
Properly add form fields with prop hidden
nas-tabchiche Jun 19, 2024
f526303
Add +layout.svelte
nas-tabchiche Jun 19, 2024
45bc243
Display relevant fields and localize SSOSettings model form
nas-tabchiche Jun 19, 2024
dc6ba1c
Merge branch 'main' into feat/samlv2
nas-tabchiche Jun 19, 2024
f608269
fix: overwrite is_safe_url
Mohamed-Hacene Jun 19, 2024
9929cd4
feat: style ui and overwrite is_safe_url
Mohamed-Hacene Jun 19, 2024
1b595ea
Conditionally require fields on SSOSettings model form
nas-tabchiche Jun 19, 2024
c416b22
Get SSO Info from /settings/sso/info endpoint
nas-tabchiche Jun 19, 2024
85b58eb
Initialize SSOSettings object on startup
nas-tabchiche Jun 19, 2024
cdd7cbd
Do not create SSOSettings object if it already exists
nas-tabchiche Jun 19, 2024
467f42a
chore: clean settings.py
Mohamed-Hacene Jun 19, 2024
3710f7b
chore: run format
Mohamed-Hacene Jun 19, 2024
c48adf5
feat: catch errors and cleaning
Mohamed-Hacene Jun 19, 2024
2bd6d05
Add start-caddy.sh
eric-intuitem Jun 19, 2024
22d412f
feat: rename settings app by global_settings
Mohamed-Hacene Jun 20, 2024
dd56b8a
feat: add sso endpoints for proxy
Mohamed-Hacene Jun 20, 2024
4a2f580
feat: update docker compose script
Mohamed-Hacene Jun 20, 2024
a2bc3e6
chore: run format
Mohamed-Hacene Jun 20, 2024
ac52f4f
feat: track Caddyfile
Mohamed-Hacene Jun 20, 2024
c9775a6
chore: cleaning
Mohamed-Hacene Jun 20, 2024
972fa69
fix: models creation order
Mohamed-Hacene Jun 20, 2024
83f6f10
chore: run format
Mohamed-Hacene Jun 20, 2024
1149f60
Default setLanguageTag result to english to avoid it being undefined
nas-tabchiche Jun 20, 2024
14482a1
chore: Merge migrations
nas-tabchiche Jun 20, 2024
1a9e298
fix: try to fix ci
Mohamed-Hacene Jun 20, 2024
d876874
Merge branch 'feat/samlv2' of github.com:intuitem/ciso-assistant-comm…
Mohamed-Hacene Jun 20, 2024
ce324d0
chore: run format
Mohamed-Hacene Jun 20, 2024
94eeb3d
fix: add page proxy settings for ci
Mohamed-Hacene Jun 20, 2024
c322c9b
Revert "fix: add page proxy settings for ci"
Mohamed-Hacene Jun 20, 2024
8e2e937
chore: Remove dead code
nas-tabchiche Jun 21, 2024
1a99f48
Add trailing slash to provider redirect URL
nas-tabchiche Jun 21, 2024
539e4ac
Re-enable CSRF middleware
nas-tabchiche Jun 21, 2024
2b23d98
fix: add trailing slash to provider redirect URL in Caddyfile
Mohamed-Hacene Jun 21, 2024
872949b
style: improve settings UI
Mohamed-Hacene Jun 21, 2024
7a1770f
chore: update translations with Fink 🐦
Mohamed-Hacene Jun 21, 2024
d1b8e5d
chore: run format
Mohamed-Hacene Jun 21, 2024
f411451
style: improve sso tab
Mohamed-Hacene Jun 21, 2024
e2c2305
Hide unneeded form fields
nas-tabchiche Jun 21, 2024
5641072
Merge branch 'feat/samlv2' of github.com:intuitem/ciso-assistant-comm…
nas-tabchiche Jun 21, 2024
886d4c1
Remove default values from SSO settings schema
nas-tabchiche Jun 21, 2024
177ddbf
Manage form tainting
nas-tabchiche Jun 21, 2024
6aa6d1b
Supply default value to provider field in SSOSettings
nas-tabchiche Jun 21, 2024
4889250
Update functional tests
nas-tabchiche Jun 21, 2024
4e61b7c
Use +layout.svelte instead of SettingsLayout component
nas-tabchiche Jun 21, 2024
a30431a
Merge branch 'main' into feat/samlv2
nas-tabchiche Jun 21, 2024
58de2bc
Disable SSO settings form fields if sso is disabled
nas-tabchiche Jun 21, 2024
b13e093
Rename IdentityProvider to SSOSettings
nas-tabchiche Jun 21, 2024
d3d30ea
Log SSOSettings object creation
nas-tabchiche Jun 21, 2024
36c6bc2
Add is_sso field to User model
nas-tabchiche Jun 21, 2024
c597099
Display SSO tag before user email if is_sso==true
nas-tabchiche Jun 21, 2024
fda1627
Set User.is_sso to True if account was logged in through SSO and acco…
nas-tabchiche Jun 21, 2024
75eb759
Get user first_name and last_name from SAML response claims
nas-tabchiche Jun 21, 2024
17fe8b5
chore: Run ruff format
nas-tabchiche Jun 21, 2024
d68210d
Lower label opacity if field is disabled
nas-tabchiche Jun 21, 2024
02f1a77
Remove unused route
nas-tabchiche Jun 21, 2024
976c1a3
Improve errors management
ab-smith Jun 21, 2024
6508f6c
Formatter
ab-smith Jun 21, 2024
29a42a7
Generate Caddyfile on docker compose run
nas-tabchiche Jun 21, 2024
dd98bca
Fix db dump
nas-tabchiche Jun 21, 2024
f40418e
chore: run ruff formatter
nas-tabchiche Jun 21, 2024
f8c85ed
put back the caddyfile for API flavor
ab-smith Jun 21, 2024
9f415ef
clean up
ab-smith Jun 21, 2024
773e735
clean up
ab-smith Jun 21, 2024
7b45b98
set is_sso properly
ab-smith Jun 21, 2024
387bde6
Missing proxy
ab-smith Jun 21, 2024
96df5ef
fixup
ab-smith Jun 21, 2024
23af964
fixup
ab-smith Jun 21, 2024
3ea3fe6
fix migrations
ab-smith Jun 21, 2024
dde8fb7
format
ab-smith Jun 21, 2024
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ The docker-compose.yml highlights a relevant configuration with a Caddy proxy in
Set DJANGO_DEBUG=False for security reason.

> [!NOTE]
> The frontend cannot infer the host automatically, so you need to either set the ORIGIN variable, or the HOST_HEADER and PROTOCOL_HEADER variables. Please see [the sveltekit doc](https://kit.svelte.dev/docs/adapter-node#environment-variables-origin-protocolheader-hostheader-and-port-header) on this tricky issue.
> The frontend cannot infer the host automatically, so you need to either set the ORIGIN variable, or the HOST_HEADER and PROTOCOL_HEADER variables. Please see [the sveltekit doc](https://kit.svelte.dev/docs/adapter-node#environment-variables-origin-protocolheader-hostheader-and-port-header) on this tricky issue. Beware that this approach does not work with "npm run dev", which should not be a worry for production.

> [!NOTE]
> Caddy needs to receive a SNI header. Therefore, for your public URL (the one declared in CISO_ASSISTANT_URL), you need to use a FQDN, not an IP address, as the SNI is not transmitted by a browser if the host is an IP address. Another tricky issue!
Expand Down
47 changes: 42 additions & 5 deletions backend/ciso_assistant/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
CORS are not managed by backend, so CORS library is not used

if "POSTGRES_NAME" environment variable defined, the database engine is posgresql
and the other env variables are POSGRES_USER, POSTGRES_PASSWORD, DB_HOST, DB_PORT
and the other env variables are POSTGRES_USER, POSTGRES_PASSWORD, DB_HOST, DB_PORT
else it is sqlite, and no env variable is required

"""
Expand Down Expand Up @@ -122,6 +122,7 @@ def set_ciso_assistant_url(_, __, event_dict):
"django_structlog",
"tailwind",
"iam",
"global_settings",
"core",
"cal",
"django_filters",
Expand All @@ -131,6 +132,11 @@ def set_ciso_assistant_url(_, __, event_dict):
"rest_framework",
"knox",
"drf_spectacular",
"allauth",
"allauth.account",
"allauth.headless",
"allauth.socialaccount",
"allauth.socialaccount.providers.saml",
]

MIDDLEWARE = [
Expand All @@ -143,13 +149,13 @@ def set_ciso_assistant_url(_, __, event_dict):
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"django_structlog.middlewares.RequestMiddleware",
# "debug_toolbar.middleware.DebugToolbarMiddleware",
# "pyinstrument.middleware.ProfilerMiddleware",
"allauth.account.middleware.AccountMiddleware",
]

ROOT_URLCONF = "ciso_assistant.urls"
LOGIN_REDIRECT_URL = "home"
LOGOUT_REDIRECT_URL = "login"
# we leave these for the API UI tools - even if Django templates and Admin are not used anymore
LOGIN_REDIRECT_URL = "/api"
LOGOUT_REDIRECT_URL = "/api"

AUTH_TOKEN_TTL = int(
os.environ.get("AUTH_TOKEN_TTL", default=60 * 60)
Expand Down Expand Up @@ -201,6 +207,9 @@ def set_ciso_assistant_url(_, __, event_dict):
"MIN_REFRESH_INTERVAL": 60,
}

# Empty outside of debug mode so that allauth middleware does not raise an error
STATIC_URL = ""

if DEBUG:
REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"].append(
"rest_framework.renderers.BrowsableAPIRenderer"
Expand Down Expand Up @@ -333,3 +342,31 @@ def set_ciso_assistant_url(_, __, event_dict):
"SERVE_INCLUDE_SCHEMA": False,
# OTHER SETTINGS
}

# SSO with allauth

ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = "email"

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

ACCOUNT_ADAPTER = "iam.adapter.MyAccountAdapter"
SOCIALACCOUNT_ADAPTER = "iam.adapter.MySocialAccountAdapter"

SOCIALACCOUNT_EMAIL_AUTHENTICATION = True
SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True

HEADLESS_ONLY = True

HEADLESS_FRONTEND_URLS = {
"socialaccount_login_error": CISO_ASSISTANT_URL + "/login",
}

SOCIALACCOUNT_PROVIDERS = {
"saml": {
"EMAIL_AUTHENTICATION": True,
"VERIFIED_EMAIL": True,
},
}
54 changes: 54 additions & 0 deletions backend/core/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import os
from django.core.management import call_command

from structlog import get_logger

logger = get_logger(__name__)

READER_PERMISSIONS_LIST = [
"view_project",
Expand Down Expand Up @@ -245,6 +248,8 @@
"delete_loadedlibrary",
"backup",
"restore",
"view_globalsettings",
"change_globalsettings",
]


Expand All @@ -255,7 +260,9 @@ def startup(sender: AppConfig, **kwargs):
Create superuser if CISO_ASSISTANT_SUPERUSER_EMAIL defined
"""
from django.contrib.auth.models import Permission
from allauth.socialaccount.providers.saml.provider import SAMLProvider
from iam.models import Folder, Role, RoleAssignment, User, UserGroup
from global_settings.models import GlobalSettings

print("startup handler: initialize database")

Expand Down Expand Up @@ -355,6 +362,53 @@ def startup(sender: AppConfig, **kwargs):
except Exception as e:
print(e) # NOTE: Add this exception in the logger

default_attribute_mapping = SAMLProvider.default_attribute_mapping

settings = {
"attribute_mapping": {
"uid": default_attribute_mapping["uid"],
"email_verified": default_attribute_mapping["email_verified"],
"email": default_attribute_mapping["email"],
},
"idp": {
"entity_id": "",
"metadata_url": "",
"sso_url": "",
"slo_url": "",
"x509cert": "",
},
"sp": {
"entity_id": "ciso-assistant",
},
"advanced": {
"allow_repeat_attribute_name": True,
"allow_single_label_domains": False,
"authn_request_signed": False,
"digest_algorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"logout_request_signed": False,
"logout_response_signed": False,
"metadata_signed": False,
"name_id_encrypted": False,
"reject_deprecated_algorithm": True,
"reject_idp_initiated_sso": True,
"signature_algorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"want_assertion_encrypted": False,
"want_assertion_signed": False,
"want_attribute_statement": True,
"want_message_signed": False,
"want_name_id": False,
"want_name_id_encrypted": False,
},
}

if not GlobalSettings.objects.filter(name=GlobalSettings.Names.SSO).exists():
logger.info("SSO settings not found, creating default settings")
sso_settings = GlobalSettings.objects.create(
name=GlobalSettings.Names.SSO,
value={"client_id": "0", "settings": settings},
)
logger.info("SSO settings created", settings=sso_settings.value)

call_command("storelibraries")


Expand Down
4 changes: 3 additions & 1 deletion backend/core/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ def has_object_permission(self, request: Request, view, obj):
if not perms:
return False
_codename = perms[0].split(".")[1]
if request.method in ["GET", "OPTIONS", "HEAD"] and obj.is_published:
if request.method in ["GET", "OPTIONS", "HEAD"] and getattr(
obj, "is_published", False
):
return True
perm = Permission.objects.get(codename=_codename)
# special case of risk acceptance approval
Expand Down
1 change: 1 addition & 0 deletions backend/core/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ class Meta:
"is_active",
"date_joined",
"user_groups",
"is_sso",
]


Expand Down
9 changes: 8 additions & 1 deletion backend/core/urls.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from iam.sso.views import SSOSettingsViewSet
from .views import *
from library.views import StoredLibraryViewSet, LoadedLibraryViewSet
from iam.sso.saml.views import FinishACSView


from django.urls import include, path
Expand Down Expand Up @@ -42,11 +44,11 @@
router.register(r"stored-libraries", StoredLibraryViewSet, basename="stored-libraries")
router.register(r"loaded-libraries", LoadedLibraryViewSet, basename="loaded-libraries")


urlpatterns = [
path("", include(router.urls)),
path("iam/", include("iam.urls")),
path("serdes/", include("serdes.urls")),
path("settings/", include("global_settings.urls")),
path("csrf/", get_csrf_token, name="get_csrf_token"),
path("build/", get_build, name="get_build"),
path("license/", license, name="license"),
Expand All @@ -55,6 +57,11 @@
path("agg_data/", get_agg_data, name="get_agg_data"),
path("composer_data/", get_composer_data, name="get_composer_data"),
path("i18n/", include("django.conf.urls.i18n")),
path(
"accounts/saml/", include("iam.sso.saml.urls")
), # NOTE: This has to be placed before the allauth urls, otherwise our ACS implementation will not be used
path("accounts/", include("allauth.urls")),
path("_allauth/", include("allauth.headless.urls")),
]

if DEBUG:
Expand Down
6 changes: 4 additions & 2 deletions backend/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class BaseModelViewSet(viewsets.ModelViewSet):
search_fields = ["name", "description"]
model: models.Model

serializers_module = "core.serializers"

def get_queryset(self):
if not self.model:
return None
Expand Down Expand Up @@ -91,7 +93,7 @@ def get_serializer_class(self):
return super().get_serializer_class()

# Dynamically import the serializer module and get the serializer class
serializer_module = importlib.import_module("core.serializers")
serializer_module = importlib.import_module(self.serializers_module)
serializer_class = getattr(serializer_module, serializer_name)

return serializer_class
Expand Down Expand Up @@ -135,7 +137,7 @@ class Meta:
@action(detail=True, name="Get write data")
def object(self, request, pk):
serializer_name = f"{self.model.__name__}WriteSerializer"
serializer_module = importlib.import_module("core.serializers")
serializer_module = importlib.import_module(self.serializers_module)
serializer_class = getattr(serializer_module, serializer_name)

return Response(serializer_class(super().get_object()).data)
Expand Down
Empty file.
8 changes: 8 additions & 0 deletions backend/global_settings/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import os
from django.apps import AppConfig
from django.db.models.signals import post_migrate


class SettingsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "global_settings"
65 changes: 65 additions & 0 deletions backend/global_settings/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Generated by Django 5.0.6 on 2024-06-20 16:48

import django.db.models.deletion
import iam.models
import uuid
from django.db import migrations, models


class Migration(migrations.Migration):
initial = True

dependencies = [
("iam", "0003_alter_folder_updated_at_alter_role_updated_at_and_more"),
]

operations = [
migrations.CreateModel(
name="GlobalSettings",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"created_at",
models.DateTimeField(auto_now_add=True, verbose_name="Created at"),
),
(
"updated_at",
models.DateTimeField(auto_now=True, verbose_name="Updated at"),
),
(
"is_published",
models.BooleanField(default=False, verbose_name="published"),
),
(
"name",
models.CharField(
choices=[("general", "General"), ("sso", "SSO")],
default="general",
max_length=30,
unique=True,
),
),
("value", models.JSONField(default=dict)),
(
"folder",
models.ForeignKey(
default=iam.models.Folder.get_root_folder,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_folder",
to="iam.folder",
),
),
],
options={
"abstract": False,
},
),
]
Empty file.
28 changes: 28 additions & 0 deletions backend/global_settings/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from django.db import models

from iam.models import FolderMixin
from core.base_models import AbstractBaseModel


class GlobalSettings(AbstractBaseModel, FolderMixin):
"""
Global settings for the application.
New setting categories should only be added through data migrations.
"""

class Names(models.TextChoices):
GENERAL = "general", "General"
SSO = "sso", "SSO"

# Name of the setting category.
name = models.CharField(
max_length=30,
unique=True,
choices=Names,
default=Names.GENERAL,
)
# Value of the setting.
value = models.JSONField(default=dict)

def __str__(self):
return self.name
27 changes: 27 additions & 0 deletions backend/global_settings/routers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from rest_framework.routers import Route, DynamicRoute, SimpleRouter


class DefaultSettingsRouter(SimpleRouter):
"""
A custom router for settings views.
"""

routes = [
Route(
url=r"^{prefix}{trailing_slash}$",
mapping={
"get": "retrieve",
"put": "update",
"patch": "partial_update",
},
name="{basename}-detail",
detail=True,
initkwargs={"suffix": "Instance"},
),
DynamicRoute(
url=r"^{prefix}/{url_path}{trailing_slash}$",
name="{basename}-{url_name}",
detail=True,
initkwargs={},
),
]
Loading
Loading