• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

esnet-security / SCRAM / 14608655760

23 Apr 2025 02:31AM UTC coverage: 95.666% (-0.1%) from 95.811%
14608655760

Pull #151

github

web-flow
Merge e1c09c7a6 into 40c27ad9c
Pull Request #151: fix(login/out): use different settings for login/out redirect url if using the mozilla oidc app

52 of 55 branches covered (94.55%)

Branch coverage included in aggregate %.

3 of 7 new or added lines in 1 file covered. (42.86%)

1 existing line in 1 file now uncovered.

1184 of 1237 relevant lines covered (95.72%)

0.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.89
/config/settings/base.py
1
"""Base settings to build other settings files upon."""
2

3
import logging
1✔
4
import os
1✔
5
from pathlib import Path
1✔
6

7
import environ
1✔
8
from django.conf.global_settings import LOGIN_REDIRECT_URL
1✔
9

10
logger = logging.getLogger(__name__)
1✔
11

12
ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent.parent
1✔
13
# scram/
14
APPS_DIR = ROOT_DIR / "scram"
1✔
15
env = environ.Env()
1✔
16

17
READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=False)
1✔
18
if READ_DOT_ENV_FILE:
1✔
19
    # OS environment variables take precedence over variables from .env
20
    env.read_env(str(ROOT_DIR / ".env"))
×
21

22
# GENERAL
23
# ------------------------------------------------------------------------------
24
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
25
DEBUG = env.bool("DJANGO_DEBUG", False)
1✔
26
# Local time zone. Choices are
27
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
28
# though not all of them may be available with every OS.
29
# In Windows, this must be set to your system time zone.
30
TIME_ZONE = "UTC"
1✔
31
# https://docs.djangoproject.com/en/dev/ref/settings/#language-code
32
LANGUAGE_CODE = "en-us"
1✔
33
# https://docs.djangoproject.com/en/dev/ref/settings/#site-id
34
SITE_ID = 1
1✔
35
# https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
36
USE_I18N = True
1✔
37
# https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n
38
USE_L10N = True
1✔
39
# https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
40
USE_TZ = True
1✔
41
# https://docs.djangoproject.com/en/dev/ref/settings/#locale-paths
42
LOCALE_PATHS = [str(ROOT_DIR / "locale")]
1✔
43

44
# DATABASES
45
# ------------------------------------------------------------------------------
46
# https://docs.djangoproject.com/en/dev/ref/settings/#databases
47
DATABASES = {"default": env.db("DATABASE_URL")}
1✔
48
DATABASES["default"]["ATOMIC_REQUESTS"] = True
1✔
49

50
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
1✔
51

52
# URLS
53
# ------------------------------------------------------------------------------
54
# https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf
55
ROOT_URLCONF = "config.urls"
1✔
56
# https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
57
WSGI_APPLICATION = "config.wsgi.application"
1✔
58

59
# APPS
60
# ------------------------------------------------------------------------------
61
DJANGO_APPS = [
1✔
62
    "django.contrib.admin",
63
    "django.contrib.auth",
64
    "django.contrib.contenttypes",
65
    "django.contrib.messages",
66
    "django.contrib.sessions",
67
    "django.contrib.sites",
68
    "django.contrib.staticfiles",
69
    "django.forms",
70
]
71
THIRD_PARTY_APPS = [
1✔
72
    "channels",
73
    "corsheaders",
74
    "crispy_forms",
75
    "django_celery_beat",
76
    "django_eventstream",
77
    "netfields",
78
    "simple_history",
79
    "rest_framework",
80
    "rest_framework.authtoken",
81
]
82

83
LOCAL_APPS = [
1✔
84
    "scram.route_manager.apps.RouteManagerConfig",
85
    "scram.users.apps.UsersConfig",
86
]
87
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
88
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
1✔
89

90
# MIGRATIONS
91
# ------------------------------------------------------------------------------
92
# https://docs.djangoproject.com/en/dev/ref/settings/#migration-modules
93
MIGRATION_MODULES = {"sites": "scram.contrib.sites.migrations"}
1✔
94

95
# AUTHENTICATION
96
# ------------------------------------------------------------------------------
97
# https://docs.djangoproject.com/en/dev/ref/settings/#authentication-backends
98
AUTHENTICATION_BACKENDS = [
1✔
99
    "django.contrib.auth.backends.ModelBackend",
100
]
101
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model
102
AUTH_USER_MODEL = "users.User"
1✔
103

104
# PASSWORDS
105
# ------------------------------------------------------------------------------
106
# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers
107
PASSWORD_HASHERS = [
1✔
108
    # https://docs.djangoproject.com/en/dev/topics/auth/passwords/#using-argon2-with-django
109
    "django.contrib.auth.hashers.Argon2PasswordHasher",
110
    "django.contrib.auth.hashers.PBKDF2PasswordHasher",
111
    "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
112
    "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
113
]
114
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
115
AUTH_PASSWORD_VALIDATORS = [
1✔
116
    {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
117
    {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
118
    {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
119
    {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
120
]
121

122
# MIDDLEWARE
123
# ------------------------------------------------------------------------------
124
# https://docs.djangoproject.com/en/dev/ref/settings/#middleware
125
MIDDLEWARE = [
1✔
126
    "django.middleware.security.SecurityMiddleware",
127
    "corsheaders.middleware.CorsMiddleware",
128
    "whitenoise.middleware.WhiteNoiseMiddleware",
129
    "django.contrib.sessions.middleware.SessionMiddleware",
130
    "django.middleware.locale.LocaleMiddleware",
131
    "django.middleware.common.CommonMiddleware",
132
    "django.middleware.csrf.CsrfViewMiddleware",
133
    "django.contrib.auth.middleware.AuthenticationMiddleware",
134
    "django.contrib.messages.middleware.MessageMiddleware",
135
    "django.middleware.common.BrokenLinkEmailsMiddleware",
136
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
137
    "django_grip.GripMiddleware",
138
    "simple_history.middleware.HistoryRequestMiddleware",
139
]
140

141
# STATIC
142
# ------------------------------------------------------------------------------
143
# https://docs.djangoproject.com/en/dev/ref/settings/#static-root
144
STATIC_ROOT = str(ROOT_DIR / "staticfiles")
1✔
145
# https://docs.djangoproject.com/en/dev/ref/settings/#static-url
146
STATIC_URL = "/static/"
1✔
147
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
148
STATICFILES_DIRS = [str(APPS_DIR / "static")]
1✔
149
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
150
STATICFILES_FINDERS = [
1✔
151
    "django.contrib.staticfiles.finders.FileSystemFinder",
152
    "django.contrib.staticfiles.finders.AppDirectoriesFinder",
153
]
154

155
# MEDIA
156
# ------------------------------------------------------------------------------
157
# https://docs.djangoproject.com/en/dev/ref/settings/#media-root
158
MEDIA_ROOT = str(APPS_DIR / "media")
1✔
159
# https://docs.djangoproject.com/en/dev/ref/settings/#media-url
160
MEDIA_URL = "/media/"
1✔
161

162
# TEMPLATES
163
# ------------------------------------------------------------------------------
164
# https://docs.djangoproject.com/en/dev/ref/settings/#templates
165
TEMPLATES = [
1✔
166
    {
167
        # https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND
168
        "BACKEND": "django.template.backends.django.DjangoTemplates",
169
        # https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
170
        "DIRS": [str(APPS_DIR / "templates")],
171
        "OPTIONS": {
172
            # https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
173
            # https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types
174
            "loaders": [
175
                "django.template.loaders.filesystem.Loader",
176
                "django.template.loaders.app_directories.Loader",
177
            ],
178
            # https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
179
            "context_processors": [
180
                "django.template.context_processors.debug",
181
                "django.template.context_processors.request",
182
                "django.contrib.auth.context_processors.auth",
183
                "django.template.context_processors.i18n",
184
                "django.template.context_processors.media",
185
                "django.template.context_processors.static",
186
                "django.template.context_processors.tz",
187
                "django.contrib.messages.context_processors.messages",
188
                "scram.utils.context_processors.settings_context",
189
                "scram.route_manager.context_processors.login_logout",
190
                "scram.route_manager.context_processors.active_count",
191
            ],
192
        },
193
    },
194
]
195

196
# https://docs.djangoproject.com/en/dev/ref/settings/#form-renderer
197
FORM_RENDERER = "django.forms.renderers.TemplatesSetting"
1✔
198

199
# http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs
200
CRISPY_TEMPLATE_PACK = "bootstrap4"
1✔
201

202
# FIXTURES
203
# ------------------------------------------------------------------------------
204
# https://docs.djangoproject.com/en/dev/ref/settings/#fixture-dirs
205
FIXTURE_DIRS = (str(APPS_DIR / "fixtures"),)
1✔
206

207
# SECURITY
208
# ------------------------------------------------------------------------------
209
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-httponly
210
SESSION_COOKIE_HTTPONLY = True
1✔
211
# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly
212
CSRF_COOKIE_HTTPONLY = True
1✔
213
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter
214
SECURE_BROWSER_XSS_FILTER = True
1✔
215
# https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options
216
X_FRAME_OPTIONS = "DENY"
1✔
217

218
# EMAIL
219
# ------------------------------------------------------------------------------
220
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
221
EMAIL_BACKEND = env("DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.smtp.EmailBackend")
1✔
222
# https://docs.djangoproject.com/en/dev/ref/settings/#email-timeout
223
EMAIL_TIMEOUT = 5
1✔
224

225
# ADMIN
226
# ------------------------------------------------------------------------------
227
# Django Admin URL.
228
ADMIN_URL = "admin/"
1✔
229
# https://docs.djangoproject.com/en/dev/ref/settings/#admins
230
ADMINS = [("Sam Oehlert", "soehlert@es.net")]
1✔
231
# https://docs.djangoproject.com/en/dev/ref/settings/#managers
232
MANAGERS = ADMINS
1✔
233

234
# LOGGING
235
# ------------------------------------------------------------------------------
236
# https://docs.djangoproject.com/en/dev/ref/settings/#logging
237
# See https://docs.djangoproject.com/en/dev/topics/logging for
238
# more details on how to customize your logging configuration.
239
LOGGING = {
1✔
240
    "version": 1,
241
    "disable_existing_loggers": False,
242
    "formatters": {
243
        "verbose": {"format": "%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s"},
244
    },
245
    "handlers": {
246
        "console": {
247
            "level": "DEBUG",
248
            "class": "logging.StreamHandler",
249
            "formatter": "verbose",
250
        },
251
    },
252
    "root": {"level": "INFO", "handlers": ["console"]},
253
}
254

255
# Channels
256
# ------------------------------------------------------------------------------
257
CHANNEL_LAYERS = {
1✔
258
    "default": {
259
        "BACKEND": "channels_redis.core.RedisChannelLayer",
260
        "CONFIG": {
261
            "expiry": 86400 * 7,  # expire messages after a week (default 60s)
262
            "group_expiry": 86400 * 365 * 10,  # effectively disable removing from a group (default 1d)
263
            "hosts": [(os.environ.get("REDIS_HOST", "redis"), 6379)],
264
        },
265
    },
266
}
267

268
# Pagination
269
# -------------------------------------------------------------------------------
270
PAGINATION_SIZE = 100
1✔
271

272
# django-rest-framework
273
# -------------------------------------------------------------------------------
274
# django-rest-framework - https://www.django-rest-framework.org/api-guide/settings/
275
# Swagger related tooling
276
INSTALLED_APPS += ["drf_spectacular"]
1✔
277
REST_FRAMEWORK = {
1✔
278
    "DEFAULT_AUTHENTICATION_CLASSES": (
279
        "rest_framework.authentication.SessionAuthentication",
280
        "rest_framework.authentication.TokenAuthentication",
281
    ),
282
    "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
283
    "TEST_REQUEST_DEFAULT_FORMAT": "json",
284
    "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
285
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
286
    "PAGE_SIZE": PAGINATION_SIZE,
287
}
288

289
# django-cors-headers - https://github.com/adamchainz/django-cors-headers#setup
290
CORS_URLS_REGEX = r"^/api/.*$"
1✔
291
# Your stuff...
292
# ------------------------------------------------------------------------------
293
# Are you using local passwords or oidc?
294
AUTH_METHOD = os.environ.get("SCRAM_AUTH_METHOD", "local").lower()
1✔
295

296
OIDC_OP_JWKS_ENDPOINT = os.environ.get(
1✔
297
    "OIDC_OP_JWKS_ENDPOINT",
298
    "https://example.com/auth/realms/example/protocol/openid-connect/certs",
299
)
300
OIDC_OP_AUTHORIZATION_ENDPOINT = os.environ.get(
1✔
301
    "OIDC_OP_AUTHORIZATION_ENDPOINT",
302
    "https://example.com/auth/realms/example/protocol/openid-connect/auth",
303
)
304
OIDC_OP_TOKEN_ENDPOINT = os.environ.get(
1✔
305
    "OIDC_OP_TOKEN_ENDPOINT",
306
    "https://example.com/auth/realms/example/protocol/openid-connect/token",
307
)
308
OIDC_OP_USER_ENDPOINT = os.environ.get(
1✔
309
    "OIDC_OP_USER_ENDPOINT",
310
    "https://example.com/auth/realms/example/protocol/openid-connect/userinfo",
311
)
312
OIDC_RP_SIGN_ALGO = "RS256"
1✔
313

314
logger.info("Using AUTH METHOD=%s", AUTH_METHOD)
1✔
315
if AUTH_METHOD == "oidc":
1✔
316
    # Extend middleware to add OIDC middleware
317
    MIDDLEWARE += ["mozilla_django_oidc.middleware.SessionRefresh"]
×
318

319
    # Extend middleware to add OIDC auth backend
320
    AUTHENTICATION_BACKENDS += ["scram.route_manager.authentication_backends.ESnetAuthBackend"]
×
321

322
    # Need to point somewhere otherwise /oidc/logout/ redirects to /oidc/logout/None which 404s
323
    # https://github.com/mozilla/mozilla-django-oidc/issues/118
NEW
324
    LOGIN_REDIRECT_URL = "/"
×
325

326
    # Using `/` because named urls don't work for this package
327
    # https://github.com/mozilla/mozilla-django-oidc/issues/434
NEW
328
    LOGOUT_REDIRECT_URL = "/"
×
329

330
    # https://docs.djangoproject.com/en/dev/ref/settings/#login-url
NEW
331
    LOGIN_URL = "/"
×
332

333
    # https://docs.djangoproject.com/en/dev/ref/settings/#logout-url
NEW
334
    LOGOUT_URL = "/"
×
335

336
    OIDC_RP_CLIENT_ID = os.environ.get("OIDC_RP_CLIENT_ID")
×
337
    OIDC_RP_CLIENT_SECRET = os.environ.get("OIDC_RP_CLIENT_SECRET")
×
338

339
elif AUTH_METHOD == "local":
1✔
340
    # Points to 'route_manager:home'
341
    # https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url
342
    LOGIN_REDIRECT_URL = "route_manager:home"
1✔
343

344
    LOGOUT_REDIRECT_URL = "route_manager:home"
1✔
345
    # https://docs.djangoproject.com/en/dev/ref/settings/#login-url
346
    LOGIN_URL = "local_auth:login"
1✔
347

348
    # https://docs.djangoproject.com/en/dev/ref/settings/#logout-url
349
    LOGOUT_URL = "local_auth:logout"
1✔
350
else:
UNCOV
351
    msg = f"Invalid authentication method: {AUTH_METHOD}. Please choose 'local' or 'oidc'"
×
352
    raise ValueError(msg)
×
353

354

355
# Should we create an admin user for you
356
AUTOCREATE_ADMIN = True
1✔
357

358
# enable composite indexing on history_date and model pk (to improve as_of queries)
359
# the string is case-insensitive
360
SIMPLE_HISTORY_DATE_INDEX = "Composite"
1✔
361
SIMPLE_HISTORY_HISTORY_ID_USE_UUID = True
1✔
362
# Take in comment to show with history changes on models
363
SIMPLE_HISTORY_HISTORY_CHANGE_REASON_USE_TEXT_FIELD = True
1✔
364
SIMPLE_HISTORY_ENABLED = True
1✔
365

366
# Users in these groups have full privileges, including Django is_superuser
367
SCRAM_ADMIN_GROUPS = ["svc_scram_admin"]
1✔
368

369
# Users in these groups can create and modify entries
370
SCRAM_READWRITE_GROUPS = ["svc_scram_readwrite"]
1✔
371

372
# Users in these groups can only read entries
373
SCRAM_READONLY_GROUPS = ["svc_scram_readonly"]
1✔
374

375
# Users in these groups have no access whatsoever
376
SCRAM_DENIED_GROUPS = ["svc_scram_denied"]
1✔
377

378
# This is the set of all the groups
379
SCRAM_GROUPS = SCRAM_ADMIN_GROUPS + SCRAM_READWRITE_GROUPS + SCRAM_READONLY_GROUPS + SCRAM_DENIED_GROUPS
1✔
380

381
# How many entries to show PER Actiontype on the home page
382
RECENT_LIMIT = 10
1✔
383
# What is the largest cidr range we'll accept entries for
384
V4_MINPREFIX = 32
1✔
385
V6_MINPREFIX = 128
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc