settings.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. """
  2. Django settings for glitchtip project.
  3. Generated by 'django-admin startproject' using Django 3.0rc1.
  4. For more information on this file, see
  5. https://docs.djangoproject.com/en/dev/topics/settings/
  6. For the full list of settings and their values, see
  7. https://docs.djangoproject.com/en/dev/ref/settings/
  8. """
  9. import os
  10. import sys
  11. import warnings
  12. import environ
  13. import sentry_sdk
  14. from sentry_sdk.integrations.django import DjangoIntegration
  15. from celery.schedules import crontab
  16. env = environ.Env(
  17. ALLOWED_HOSTS=(list, ["*"]),
  18. DEBUG=(bool, False),
  19. DEBUG_TOOLBAR=(bool, False),
  20. AWS_ACCESS_KEY_ID=(str, None),
  21. AWS_SECRET_ACCESS_KEY=(str, None),
  22. AWS_STORAGE_BUCKET_NAME=(str, None),
  23. AWS_S3_ENDPOINT_URL=(str, None),
  24. AWS_LOCATION=(str, None),
  25. STATIC_URL=(str, "/"),
  26. STATICFILES_STORAGE=(
  27. str,
  28. "whitenoise.storage.CompressedManifestStaticFilesStorage",
  29. ),
  30. )
  31. path = environ.Path()
  32. # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
  33. BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  34. # Quick-start development settings - unsuitable for production
  35. # See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
  36. # SECURITY WARNING: keep the secret key used in production secret!
  37. SECRET_KEY = env("SECRET_KEY")
  38. # SECURITY WARNING: don't run with debug turned on in production!
  39. DEBUG = env("DEBUG")
  40. # Enable only for running end to end testing. Debug must be True to use.
  41. ENABLE_TEST_API = env.bool("ENABLE_TEST_API", False)
  42. if DEBUG is False:
  43. ENABLE_TEST_API = False
  44. ALLOWED_HOSTS = env("ALLOWED_HOSTS")
  45. # Necessary for kubernetes health checks
  46. POD_IP = env.str("POD_IP", default=None)
  47. if POD_IP:
  48. ALLOWED_HOSTS.append(POD_IP)
  49. # Used in email and DSN generation. Set to full domain such as https://glitchtip.example.com
  50. GLITCHTIP_DOMAIN = env.url("GLITCHTIP_DOMAIN", default="http://localhost:8000")
  51. # Events and associated data older than this will be deleted from the database
  52. GLITCHTIP_MAX_EVENT_LIFE_DAYS = env.int("GLITCHTIP_MAX_EVENT_LIFE_DAYS", default=90)
  53. # For development purposes only, prints out inbound event store json
  54. EVENT_STORE_DEBUG = env.bool("EVENT_STORE_DEBUG", False)
  55. # GlitchTip can track GlitchTip's own errors.
  56. # If enabling this, use a different server to avoid infinite loops.
  57. def before_send(event, hint):
  58. """Don't log django.DisallowedHost errors in Sentry."""
  59. if "log_record" in hint:
  60. if hint["log_record"].name == "django.security.DisallowedHost":
  61. return None
  62. return event
  63. SENTRY_DSN = env.str("SENTRY_DSN", None)
  64. # Optionally allow a different DSN for the frontend
  65. SENTRY_FRONTEND_DSN = env.str("SENTRY_FRONTEND_DSN", SENTRY_DSN)
  66. if SENTRY_DSN:
  67. sentry_sdk.init(
  68. dsn=SENTRY_DSN, integrations=[DjangoIntegration()], before_send=before_send,
  69. )
  70. def show_toolbar(request):
  71. return env("DEBUG_TOOLBAR")
  72. DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": show_toolbar}
  73. DEBUG_TOOLBAR_PANELS = [
  74. "debug_toolbar.panels.versions.VersionsPanel",
  75. "debug_toolbar.panels.timer.TimerPanel",
  76. "debug_toolbar.panels.settings.SettingsPanel",
  77. "debug_toolbar.panels.headers.HeadersPanel",
  78. "debug_toolbar.panels.request.RequestPanel",
  79. "debug_toolbar.panels.sql.SQLPanel",
  80. ]
  81. # Application definition
  82. INSTALLED_APPS = [
  83. "django.contrib.admin",
  84. "django.contrib.auth",
  85. "django.contrib.contenttypes",
  86. "django.contrib.sessions",
  87. "django.contrib.messages",
  88. "django.contrib.staticfiles",
  89. "django.contrib.sites",
  90. "django.contrib.postgres",
  91. "allauth",
  92. "allauth.account",
  93. "allauth.socialaccount",
  94. "allauth.socialaccount.providers.gitlab",
  95. "allauth.socialaccount.providers.github",
  96. "allauth.socialaccount.providers.google",
  97. "allauth.socialaccount.providers.microsoft",
  98. "anymail",
  99. "corsheaders",
  100. "django_celery_results",
  101. "django_filters",
  102. "debug_toolbar",
  103. "rest_framework",
  104. "drf_yasg",
  105. "dj_rest_auth",
  106. "dj_rest_auth.registration",
  107. "storages",
  108. "glitchtip",
  109. "alerts",
  110. "api_tokens",
  111. "organizations_ext",
  112. "event_store",
  113. "issues",
  114. "users",
  115. "user_reports",
  116. "projects",
  117. "teams",
  118. ]
  119. MIDDLEWARE = [
  120. "django.middleware.security.SecurityMiddleware",
  121. "django.contrib.sessions.middleware.SessionMiddleware",
  122. "corsheaders.middleware.CorsMiddleware",
  123. "csp.middleware.CSPMiddleware",
  124. "django.middleware.clickjacking.XFrameOptionsMiddleware",
  125. "whitenoise.middleware.WhiteNoiseMiddleware",
  126. "debug_toolbar.middleware.DebugToolbarMiddleware",
  127. "django.middleware.common.CommonMiddleware",
  128. "django.middleware.csrf.CsrfViewMiddleware",
  129. "django.contrib.auth.middleware.AuthenticationMiddleware",
  130. "django.contrib.messages.middleware.MessageMiddleware",
  131. "django.middleware.clickjacking.XFrameOptionsMiddleware",
  132. "glitchtip.middleware.proxy.DecompressBodyMiddleware",
  133. ]
  134. ROOT_URLCONF = "glitchtip.urls"
  135. TEMPLATES = [
  136. {
  137. "BACKEND": "django.template.backends.django.DjangoTemplates",
  138. "DIRS": [path("dist"), path("templates")],
  139. "APP_DIRS": True,
  140. "OPTIONS": {
  141. "context_processors": [
  142. "django.template.context_processors.debug",
  143. "django.template.context_processors.request",
  144. "django.contrib.auth.context_processors.auth",
  145. "django.contrib.messages.context_processors.messages",
  146. ],
  147. },
  148. },
  149. ]
  150. WSGI_APPLICATION = "glitchtip.wsgi.application"
  151. CORS_ORIGIN_ALLOW_ALL = env.bool("CORS_ORIGIN_ALLOW_ALL", True)
  152. CORS_ORIGIN_WHITELIST = env.tuple("CORS_ORIGIN_WHITELIST", str, default=())
  153. SECURE_BROWSER_XSS_FILTER = True
  154. CSP_STYLE_SRC = ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"]
  155. CSP_STYLE_SRC_ELEM = ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"]
  156. CSP_FONT_SRC = ["'self'", "https://fonts.gstatic.com"]
  157. # Redoc requires blob
  158. CSP_WORKER_SRC = ["'self'", "blob:"]
  159. # GlitchTip can record it's own errors
  160. CSP_CONNECT_SRC = ["'self'", "https://app.glitchtip.com"]
  161. # Needed for Matomo and Stripe for SaaS use cases. Both are disabled by default.
  162. CSP_SCRIPT_SRC = ["'self'", "https://matomo.glitchtip.com", "https://js.stripe.com"]
  163. CSP_IMG_SRC = ["'self'", "https://matomo.glitchtip.com"]
  164. CSP_FRAME_SRC = ["'self'", "https://js.stripe.com"]
  165. # Consider tracking CSP reports with GlitchTip itself
  166. CSP_REPORT_URI = env.tuple("CSP_REPORT_URI", str, None)
  167. SECURE_HSTS_SECONDS = env.int("SECURE_HSTS_SECONDS", 0)
  168. SECURE_HSTS_PRELOAD = env.bool("SECURE_HSTS_PRELOAD", False)
  169. SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool("SECURE_HSTS_INCLUDE_SUBDOMAINS", False)
  170. SESSION_COOKIE_SECURE = env.bool("SESSION_COOKIE_SECURE", False)
  171. SESSION_COOKIE_SAMESITE = env.str("SESSION_COOKIE_SAMESITE", "Lax")
  172. ENVIRONMENT = env.str("ENVIRONMENT", None)
  173. GLITCHTIP_VERSION = env.str("GLITCHTIP_VERSION", "dev")
  174. DEFAULT_FROM_EMAIL = env.str("DEFAULT_FROM_EMAIL", "webmaster@localhost")
  175. ANYMAIL = {
  176. "MAILGUN_API_KEY": env.str("MAILGUN_API_KEY", None),
  177. "MAILGUN_SENDER_DOMAIN": env.str("MAILGUN_SENDER_DOMAIN", None),
  178. }
  179. ACCOUNT_EMAIL_SUBJECT_PREFIX = ""
  180. # Database
  181. # https://docs.djangoproject.com/en/dev/ref/settings/#databases
  182. DATABASES = {
  183. "default": env.db(default="postgres://postgres:postgres@postgres:5432/postgres")
  184. }
  185. # We need to support both url and broken out host to support helm redis chart
  186. REDIS_HOST = env.str("REDIS_HOST", None)
  187. if REDIS_HOST:
  188. REDIS_PORT = env.str("REDIS_PORT", "6379")
  189. REDIS_DATABASE = env.str("REDIS_DATABASE", "0")
  190. REDIS_PASSWORD = env.str("REDIS_PASSWORD", None)
  191. if REDIS_PASSWORD:
  192. REDIS_URL = (
  193. f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_DATABASE}"
  194. )
  195. else:
  196. REDIS_URL = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DATABASE}"
  197. else:
  198. REDIS_URL = env.str("REDIS_URL", "redis://redis:6379/0")
  199. CELERY_BROKER_URL = REDIS_URL
  200. CELERY_BROKER_TRANSPORT_OPTIONS = {
  201. "fanout_prefix": True,
  202. "fanout_patterns": True,
  203. }
  204. CELERY_RESULT_BACKEND = "django-db"
  205. CELERY_CACHE_BACKEND = "django-cache"
  206. CELERY_BEAT_SCHEDULE = {
  207. "send-alert-notifications": {
  208. "task": "alerts.tasks.process_alerts",
  209. "schedule": 60,
  210. },
  211. "cleanup-old-events": {
  212. "task": "issues.tasks.cleanup_old_events",
  213. "schedule": crontab(hour=6),
  214. },
  215. "set-organization-throttle": {
  216. "task": "organizations_ext.tasks.set_organization_throttle",
  217. "schedule": crontab(hour=7),
  218. },
  219. }
  220. CACHES = {"default": {"BACKEND": "redis_cache.RedisCache", "LOCATION": REDIS_URL}}
  221. # Password validation
  222. # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
  223. AUTH_PASSWORD_VALIDATORS = [
  224. {
  225. "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
  226. },
  227. {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",},
  228. {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",},
  229. {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",},
  230. ]
  231. # Internationalization
  232. # https://docs.djangoproject.com/en/dev/topics/i18n/
  233. LANGUAGE_CODE = "en-us"
  234. TIME_ZONE = "UTC"
  235. USE_I18N = True
  236. USE_L10N = True
  237. USE_TZ = True
  238. SITE_ID = 1
  239. # Static files (CSS, JavaScript, Images)
  240. # https://docs.djangoproject.com/en/dev/howto/static-files/
  241. if DEBUG:
  242. STATIC_URL = "/static/"
  243. else: # This is needed for angular cli
  244. STATIC_URL = "/"
  245. MEDIA_URL = "media/" # Not used, can be anything except the STATIC_URL. Cannot be "" nor "/", but must end with a slash
  246. AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID")
  247. AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY")
  248. AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME")
  249. AWS_S3_ENDPOINT_URL = env("AWS_S3_ENDPOINT_URL")
  250. AWS_LOCATION = env("AWS_LOCATION")
  251. STATICFILES_DIRS = [
  252. "assets",
  253. "dist",
  254. ]
  255. STATIC_ROOT = path("static/")
  256. STATICFILES_STORAGE = env("STATICFILES_STORAGE")
  257. EMAIL_BACKEND = env.str(
  258. "EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend"
  259. )
  260. AUTH_USER_MODEL = "users.User"
  261. ACCOUNT_AUTHENTICATION_METHOD = "email"
  262. ACCOUNT_EMAIL_REQUIRED = True
  263. ACCOUNT_USERNAME_REQUIRED = False
  264. ACCOUNT_USER_MODEL_USERNAME_FIELD = None
  265. INVITATION_BACKEND = "organizations_ext.invitation_backend.InvitationBackend"
  266. OLD_PASSWORD_FIELD_ENABLED = True
  267. LOGOUT_ON_PASSWORD_CHANGE = False
  268. REST_AUTH_SERIALIZERS = {
  269. "USER_DETAILS_SERIALIZER": "users.serializers.UserSerializer",
  270. "TOKEN_SERIALIZER": "users.serializers.NoopTokenSerializer",
  271. "PASSWORD_RESET_SERIALIZER": "users.serializers.PasswordSetResetSerializer",
  272. }
  273. REST_AUTH_REGISTER_SERIALIZERS = {
  274. "REGISTER_SERIALIZER": "users.serializers.RegisterSerializer",
  275. }
  276. REST_AUTH_TOKEN_CREATOR = "users.utils.noop_token_creator"
  277. # By default (False) only the first user may register and create an organization
  278. # Other users must be invited. Intended for private instances
  279. ENABLE_OPEN_USER_REGISTRATION = env.bool("ENABLE_OPEN_USER_REGISTRATION", False)
  280. AUTHENTICATION_BACKENDS = (
  281. # Needed to login by username in Django admin, regardless of `allauth`
  282. "django.contrib.auth.backends.ModelBackend",
  283. # `allauth` specific authentication methods, such as login by e-mail
  284. "allauth.account.auth_backends.AuthenticationBackend",
  285. )
  286. DEFAULT_RENDERER_CLASSES = ("rest_framework.renderers.JSONRenderer",)
  287. if DEBUG:
  288. DEFAULT_RENDERER_CLASSES = DEFAULT_RENDERER_CLASSES + (
  289. "rest_framework.renderers.BrowsableAPIRenderer",
  290. )
  291. REST_FRAMEWORK = {
  292. "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated",],
  293. "DEFAULT_PAGINATION_CLASS": "glitchtip.pagination.LinkHeaderPagination",
  294. "PAGE_SIZE": 50,
  295. "DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",),
  296. "DEFAULT_RENDERER_CLASSES": DEFAULT_RENDERER_CLASSES,
  297. }
  298. DRF_YASG_EXCLUDE_VIEWS = [
  299. "users.views.SocialAccountDisconnectView",
  300. ]
  301. SWAGGER_SETTINGS = {
  302. "DEFAULT_AUTO_SCHEMA_CLASS": "glitchtip.yasg.SquadSwaggerAutoSchema",
  303. }
  304. LOGGING = {
  305. "version": 1,
  306. "disable_existing_loggers": False,
  307. "handlers": {"null": {"class": "logging.NullHandler",},},
  308. "loggers": {
  309. "django.security.DisallowedHost": {"handlers": ["null"], "propagate": False,},
  310. },
  311. }
  312. def organization_request_callback(request):
  313. """ Gets an organization instance from the id passed through ``request``"""
  314. user = request.user
  315. if user:
  316. return user.organizations_ext_organization.filter(
  317. owner__organization_user__user=user
  318. ).first()
  319. # Set to track activity with Matomo
  320. MATOMO_URL = env.str("MATOMO_URL", default=None)
  321. MATOMO_SITE_ID = env.str("MATOMO_SITE_ID", default=None)
  322. # Is running unit test
  323. TESTING = len(sys.argv) > 1 and sys.argv[1] == "test"
  324. # Max events per month for free tier
  325. BILLING_FREE_TIER_EVENTS = env.int("BILLING_FREE_TIER_EVENTS", 1000)
  326. DJSTRIPE_SUBSCRIBER_MODEL = "organizations_ext.Organization"
  327. DJSTRIPE_SUBSCRIBER_MODEL_REQUEST_CALLBACK = organization_request_callback
  328. DJSTRIPE_USE_NATIVE_JSONFIELD = True
  329. BILLING_ENABLED = False
  330. STRIPE_LIVE_MODE = env.bool("STRIPE_LIVE_MODE", False)
  331. if env.str("STRIPE_TEST_PUBLIC_KEY", None) or env.str("STRIPE_LIVE_PUBLIC_KEY", None):
  332. BILLING_ENABLED = True
  333. INSTALLED_APPS.append("djstripe")
  334. INSTALLED_APPS.append("djstripe_ext")
  335. STRIPE_TEST_PUBLIC_KEY = env.str("STRIPE_TEST_PUBLIC_KEY", None)
  336. STRIPE_TEST_SECRET_KEY = env.str("STRIPE_TEST_SECRET_KEY", None)
  337. STRIPE_LIVE_PUBLIC_KEY = env.str("STRIPE_LIVE_PUBLIC_KEY", None)
  338. STRIPE_LIVE_SECRET_KEY = env.str("STRIPE_LIVE_SECRET_KEY", None)
  339. DJSTRIPE_WEBHOOK_SECRET = env.str("DJSTRIPE_WEBHOOK_SECRET", None)
  340. elif TESTING:
  341. # Must run tests with djstripe enabled
  342. BILLING_ENABLED = True
  343. INSTALLED_APPS.append("djstripe")
  344. INSTALLED_APPS.append("djstripe_ext")
  345. STRIPE_TEST_PUBLIC_KEY = "fake"
  346. STRIPE_TEST_SECRET_KEY = "sk_test_fake" # nosec
  347. DJSTRIPE_WEBHOOK_SECRET = "whsec_fake" # nosec
  348. if TESTING:
  349. CELERY_TASK_ALWAYS_EAGER = True
  350. STATICFILES_STORAGE = None
  351. # https://github.com/evansd/whitenoise/issues/215
  352. warnings.filterwarnings(
  353. "ignore", message="No directory at", module="whitenoise.base"
  354. )