|
17 | 17 | import structlog |
18 | 18 | from decouple import config |
19 | 19 |
|
| 20 | +# Import authorization settings |
| 21 | +from authorization.keycloak_settings import * |
| 22 | + |
20 | 23 | from .cache_settings import * |
21 | 24 |
|
22 | 25 | env = environ.Env(DEBUG=(bool, False)) |
23 | 26 | DEBUG = env.bool("DEBUG", default=True) |
24 | | - |
25 | | - |
26 | 27 | # Build paths inside the project like this: BASE_DIR / 'subdir'. |
27 | 28 | BASE_DIR = Path(__file__).resolve().parent.parent |
28 | 29 | environ.Env.read_env(os.path.join(BASE_DIR, ".env")) |
|
43 | 44 |
|
44 | 45 | # Data indexing database |
45 | 46 | DATA_DB_NAME = env("DATA_DB_NAME", default=str(BASE_DIR / "data.sqlite3")) |
46 | | -DATA_DB_USER = env("DB_USER", default=DB_USER) |
47 | | -DATA_DB_PASSWORD = env("DB_PASSWORD", default=DB_PASSWORD) |
48 | | -DATA_DB_HOST = env("DB_HOST", default=DB_HOST) |
49 | | -DATA_DB_PORT = env("DB_PORT", default=DB_PORT) |
| 47 | +DATA_DB_USER = env("DATA_DB_USER", default=DB_USER) |
| 48 | +DATA_DB_PASSWORD = env("DATA_DB_PASSWORD", default=DB_PASSWORD) |
| 49 | +DATA_DB_HOST = env("DATA_DB_HOST", default=DB_HOST) |
| 50 | +DATA_DB_PORT = env("DATA_DB_PORT", default=DB_PORT) |
50 | 51 |
|
51 | 52 | # SECURITY WARNING: don't run with debug turned on in production! |
52 | 53 |
|
|
64 | 65 |
|
65 | 66 | CSRF_TRUSTED_ORIGINS = whitelisted_urls |
66 | 67 |
|
67 | | -# CORS settings |
68 | | -if DEBUG: |
69 | | - # In development, allow all origins |
70 | | - CORS_ORIGIN_ALLOW_ALL = True |
71 | | -else: |
72 | | - # In production, only allow whitelisted origins |
73 | | - CORS_ORIGIN_ALLOW_ALL = False |
74 | | - CORS_ALLOWED_ORIGINS = whitelisted_urls |
| 68 | +# Explicitly disable automatic URL normalization to prevent redirects |
| 69 | +APPEND_SLASH = False |
75 | 70 |
|
76 | | -# Common CORS settings |
| 71 | +# Disable trailing slash redirects for GraphQL |
| 72 | +STRICT_URL_HANDLING = True |
| 73 | + |
| 74 | +# Disable HTTPS redirects - this is critical |
| 75 | +SECURE_SSL_REDIRECT = False |
| 76 | +SECURITY_MIDDLEWARE_REDIRECT_HTTPS = False |
| 77 | +SECURE_PROXY_SSL_HEADER = None |
| 78 | +# Maximally permissive CORS settings to fix issues |
| 79 | +CORS_ORIGIN_ALLOW_ALL = True |
77 | 80 | CORS_ALLOW_CREDENTIALS = True |
78 | | -CORS_ALLOW_METHODS = [ |
79 | | - "DELETE", |
80 | | - "GET", |
81 | | - "OPTIONS", |
82 | | - "PATCH", |
83 | | - "POST", |
84 | | - "PUT", |
85 | | -] |
| 81 | +CORS_ALLOW_METHODS = ["*"] |
| 82 | +CORS_ALLOW_HEADERS = ["*"] |
| 83 | +CORS_EXPOSE_HEADERS = ["*"] |
86 | 84 |
|
87 | | -CORS_ALLOW_HEADERS = [ |
88 | | - "accept", |
89 | | - "accept-encoding", |
90 | | - "authorization", |
91 | | - "content-type", |
92 | | - "dnt", |
93 | | - "origin", |
94 | | - "user-agent", |
95 | | - "x-csrftoken", |
96 | | - "x-requested-with", |
97 | | - "referer", |
98 | | - "organization", |
99 | | - "dataspace", |
100 | | - "token", |
101 | | -] |
| 85 | +# Apply CORS to all URLs including redirects |
| 86 | +CORS_URLS_REGEX = r".*" |
102 | 87 |
|
| 88 | +# CORS preflight settings |
| 89 | +CORS_PREFLIGHT_MAX_AGE = 86400 |
| 90 | +# Common CORS settings |
| 91 | +CORS_ALLOW_CREDENTIALS = True |
| 92 | +# CORS_ALLOW_METHODS = [ |
| 93 | +# "DELETE", |
| 94 | +# "GET", |
| 95 | +# "OPTIONS", |
| 96 | +# "PATCH", |
| 97 | +# "POST", |
| 98 | +# "PUT", |
| 99 | +# ] |
| 100 | + |
| 101 | +# CORS_ALLOW_HEADERS = [ |
| 102 | +# "accept", |
| 103 | +# "accept-encoding", |
| 104 | +# "authorization", |
| 105 | +# "content-type", |
| 106 | +# "dnt", |
| 107 | +# "origin", |
| 108 | +# "user-agent", |
| 109 | +# "x-csrftoken", |
| 110 | +# "x-requested-with", |
| 111 | +# "referer", |
| 112 | +# "organization", |
| 113 | +# "dataspace", |
| 114 | +# "token", |
| 115 | +# "x-keycloak-token", # Add Keycloak token header |
| 116 | +# ] |
103 | 117 | # Application definition |
104 | 118 |
|
105 | 119 | INSTALLED_APPS = [ |
|
109 | 123 | "django.contrib.sessions", |
110 | 124 | "django.contrib.messages", |
111 | 125 | "django.contrib.staticfiles", |
112 | | - "corsheaders", # django-cors-headers package |
| 126 | + "corsheaders", |
| 127 | + "authorization.apps.AuthorizationConfig", |
113 | 128 | "api.apps.ApiConfig", |
114 | 129 | "strawberry_django", |
115 | 130 | "rest_framework", |
| 131 | + "rest_framework_simplejwt", |
116 | 132 | "django_elasticsearch_dsl", |
117 | 133 | "django_elasticsearch_dsl_drf", |
| 134 | + "actstream", |
118 | 135 | ] |
119 | 136 |
|
120 | 137 | MIDDLEWARE = [ |
| 138 | + "corsheaders.middleware.CorsMiddleware", # CORS middleware must be first |
121 | 139 | "django.middleware.security.SecurityMiddleware", |
122 | 140 | "django.contrib.sessions.middleware.SessionMiddleware", |
123 | | - "corsheaders.middleware.CorsMiddleware", |
124 | 141 | "django.middleware.common.CommonMiddleware", |
125 | 142 | "django.middleware.csrf.CsrfViewMiddleware", |
126 | 143 | "django.contrib.auth.middleware.AuthenticationMiddleware", |
127 | 144 | "django.contrib.messages.middleware.MessageMiddleware", |
128 | 145 | "django.middleware.clickjacking.XFrameOptionsMiddleware", |
129 | 146 | "api.utils.middleware.ContextMiddleware", |
| 147 | + "api.middleware.request_validator.RequestValidationMiddleware", |
| 148 | + "api.middleware.logging.StructuredLoggingMiddleware", |
130 | 149 | ] |
131 | 150 |
|
132 | | -# Add debug toolbar middleware first if in debug mode |
| 151 | +# Add debug toolbar middleware if in debug mode |
133 | 152 | if DEBUG: |
134 | | - MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware") |
| 153 | + MIDDLEWARE.insert(1, "debug_toolbar.middleware.DebugToolbarMiddleware") |
135 | 154 |
|
136 | | -# Add our custom middleware |
137 | 155 | MIDDLEWARE += [ |
138 | 156 | "api.middleware.rate_limit.rate_limit_middleware", |
139 | | - "api.middleware.request_validator.RequestValidationMiddleware", |
140 | | - "api.middleware.logging.StructuredLoggingMiddleware", |
| 157 | + "authorization.middleware.KeycloakAuthenticationMiddleware", |
| 158 | + "authorization.middleware.activity_consent.ActivityConsentMiddleware", |
141 | 159 | ] |
142 | 160 |
|
143 | 161 | ROOT_URLCONF = "DataSpace.urls" |
|
164 | 182 | "FIELD_DESCRIPTION_FROM_HELP_TEXT": True, |
165 | 183 | "TYPE_DESCRIPTION_FROM_MODEL_DOCSTRING": True, |
166 | 184 | "GENERATE_ENUMS_FROM_CHOICES": True, |
| 185 | + "DEFAULT_PERMISSION_CLASSES": ["authorization.graphql_permissions.AllowAny"], |
167 | 186 | } |
168 | 187 |
|
169 | 188 | # Database |
|
220 | 239 | # Static files (CSS, JavaScript, Images) |
221 | 240 | # https://docs.djangoproject.com/en/4.0/howto/static-files/ |
222 | 241 |
|
223 | | -STATIC_URL = "static/" |
| 242 | +# This STATIC_URL setting is overridden below |
224 | 243 | MEDIA_URL = "public/" |
225 | 244 | MEDIA_ROOT = os.path.join(BASE_DIR, "files", "public") |
226 | 245 |
|
|
229 | 248 |
|
230 | 249 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" |
231 | 250 | DJANGO_ALLOW_ASYNC_UNSAFE = True |
| 251 | + |
| 252 | +# Custom User model |
| 253 | +AUTH_USER_MODEL = "authorization.User" |
| 254 | + |
| 255 | +# Authentication backends |
| 256 | +AUTHENTICATION_BACKENDS = [ |
| 257 | + "authorization.backends.KeycloakAuthenticationBackend", |
| 258 | + "django.contrib.auth.backends.ModelBackend", |
| 259 | +] |
232 | 260 | ELASTICSEARCH_DSL = { |
233 | 261 | "default": { |
234 | 262 | "hosts": f"http://{os.getenv('ELASTICSEARCH_USERNAME', 'elastic')}:{os.getenv('ELASTICSEARCH_PASSWORD', 'changeme')}@elasticsearch:9200", |
|
252 | 280 | # Django REST Framework settings |
253 | 281 | REST_FRAMEWORK = { |
254 | 282 | "DEFAULT_PERMISSION_CLASSES": [ |
255 | | - "rest_framework.permissions.AllowAny", # Allow unauthenticated access by default |
| 283 | + "rest_framework.permissions.AllowAny", # Allow public access by default |
256 | 284 | ], |
257 | 285 | "DEFAULT_AUTHENTICATION_CLASSES": [ |
| 286 | + "authorization.authentication.KeycloakAuthentication", |
| 287 | + "rest_framework_simplejwt.authentication.JWTAuthentication", |
258 | 288 | "rest_framework.authentication.SessionAuthentication", |
259 | 289 | "rest_framework.authentication.BasicAuthentication", |
260 | 290 | ], |
|
381 | 411 |
|
382 | 412 | # Security settings |
383 | 413 | SECURE_BROWSER_XSS_FILTER = True |
384 | | -SECURE_CONTENT_TYPE_NOSNIFF = True |
| 414 | +# Disable content type sniffing to fix MIME type issues |
| 415 | +SECURE_CONTENT_TYPE_NOSNIFF = False |
385 | 416 | X_FRAME_OPTIONS = "DENY" |
386 | 417 | SECURE_HSTS_SECONDS = 31536000 |
387 | 418 | SECURE_HSTS_INCLUDE_SUBDOMAINS = True |
388 | 419 | SECURE_HSTS_PRELOAD = True |
389 | 420 |
|
390 | 421 | if not DEBUG: |
391 | | - SECURE_SSL_REDIRECT = True |
| 422 | + # SECURE_SSL_REDIRECT = True |
392 | 423 | SESSION_COOKIE_SECURE = True |
393 | 424 | CSRF_COOKIE_SECURE = True |
| 425 | + |
| 426 | + |
| 427 | +# Static files configuration |
| 428 | +# Make sure this is an absolute path |
| 429 | +STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") |
| 430 | + |
| 431 | +# Make sure the URL starts and ends with a slash |
| 432 | +STATIC_URL = "/static/" |
| 433 | + |
| 434 | +# Additional locations of static files - where Django will look for static files |
| 435 | +STATICFILES_DIRS = [ |
| 436 | + os.path.join(BASE_DIR, "static"), |
| 437 | +] |
| 438 | + |
| 439 | +# Make sure Django can find admin static files |
| 440 | +STATICFILES_FINDERS = [ |
| 441 | + "django.contrib.staticfiles.finders.FileSystemFinder", |
| 442 | + "django.contrib.staticfiles.finders.AppDirectoriesFinder", |
| 443 | +] |
| 444 | + |
| 445 | +# Always use WhiteNoise for static files in both development and production |
| 446 | +# Insert WhiteNoise middleware after security middleware |
| 447 | +MIDDLEWARE.insert( |
| 448 | + MIDDLEWARE.index("django.middleware.security.SecurityMiddleware") + 1, |
| 449 | + "whitenoise.middleware.WhiteNoiseMiddleware", |
| 450 | +) |
| 451 | + |
| 452 | +# Use the simplest WhiteNoise storage configuration to avoid MIME type issues |
| 453 | +STATICFILES_STORAGE = "whitenoise.storage.StaticFilesStorage" |
| 454 | + |
| 455 | +# Disable compression to avoid MIME type issues |
| 456 | +WHITENOISE_ENABLE_COMPRESSION = False |
| 457 | + |
| 458 | +# Don't add content-type headers (let the browser determine them) |
| 459 | +WHITENOISE_ADD_HEADERS = False |
| 460 | + |
| 461 | +# Don't use the manifest feature which can cause issues with file references |
| 462 | +WHITENOISE_USE_FINDERS = True |
| 463 | + |
| 464 | +# Don't use the root directory feature which can cause conflicts |
| 465 | +# WHITENOISE_ROOT = os.path.join(BASE_DIR, "static") |
| 466 | + |
| 467 | +# Django Activity Stream settings |
| 468 | +ACTSTREAM_SETTINGS = { |
| 469 | + "MANAGER": "actstream.managers.ActionManager", |
| 470 | + "FETCH_RELATIONS": True, |
| 471 | + "USE_PREFETCH": True, |
| 472 | + "USE_JSONFIELD": True, |
| 473 | + "GFK_FETCH_DEPTH": 1, |
| 474 | +} |
| 475 | + |
| 476 | +# Activity Stream Consent Settings |
| 477 | +ACTIVITY_CONSENT = { |
| 478 | + # If True, user consent is required for activity tracking |
| 479 | + # If False, consent is assumed and all activities are tracked |
| 480 | + "REQUIRE_CONSENT": env.bool("ACTIVITY_REQUIRE_CONSENT", default=True), |
| 481 | + # Default consent setting for new users |
| 482 | + "DEFAULT_CONSENT": env.bool("ACTIVITY_DEFAULT_CONSENT", default=False), |
| 483 | + # If True, anonymous activities are tracked (when consent is not required) |
| 484 | + "TRACK_ANONYMOUS": env.bool("ACTIVITY_TRACK_ANONYMOUS", default=False), |
| 485 | + # Maximum age of activities to keep (in days, 0 means keep forever) |
| 486 | + "MAX_AGE_DAYS": env.int("ACTIVITY_MAX_AGE_DAYS", default=0), |
| 487 | +} |
0 commit comments