Beispiel #1
0
class SentryMixin(ConfigMixin):
    """
    Configure Sentry.io error reporting.

    This requires the `sentry-sdk` package to be installed.

    The `DJANGO_SENTRY_DSN` environment variable should be externally set to a Sentry DSN.

    The `DJANGO_SENTRY_ENVIRONMENT`, `DJANGO_SENTRY_RELEASE`, and `DJANGO_SENTRY_TRACES_SAMPLE_RATE`
    environment variables may also be set, if desired.
    """
    @staticmethod
    def mutate_configuration(
            configuration: Type[ComposedConfiguration]) -> None:
        configuration.INSTALLED_APPS += [
            'composed_configuration.sentry.apps.SentryConfig',
        ]

    SENTRY_DSN = values.Value(None)

    SENTRY_ENVIRONMENT = values.Value(None)

    SENTRY_RELEASE = values.Value(None)

    # None is a valid default value, but if this is set via environment variable,
    # the value must be interpretable as a float
    SENTRY_TRACES_SAMPLE_RATE = values.FloatValue(None)
class AWS(object):
    "AWS configuration"

    SPOT_INSTANCES = values.BooleanValue(default=True)
    CORE_SPOT_BID = values.FloatValue(default=0.84)

    AWS_CONFIG = {
        # AWS EC2 configuration
        'AWS_REGION': 'us-west-2',

        # EMR configuration
        # Master and slave instance types should be the same as the telemetry
        # setup bootstrap action depends on it to autotune the cluster.
        'MASTER_INSTANCE_TYPE': 'c3.4xlarge',
        'WORKER_INSTANCE_TYPE': 'c3.4xlarge',
        'USE_SPOT_INSTANCES': SPOT_INSTANCES,
        'CORE_SPOT_BID': CORE_SPOT_BID,
        # available EMR releases, to be used as choices for Spark jobs and clusters
        # forms. Please keep the latest (newest) as the first item
        'EMR_RELEASES': (
            '5.0.0',
            '4.5.0',
        ),
        'SPARK_INSTANCE_PROFILE': 'telemetry-spark-cloudformation-'
        'TelemetrySparkInstanceProfile-1SATUBVEXG7E3',
        'SPARK_EMR_BUCKET': 'telemetry-spark-emr-2',
        'INSTANCE_APP_TAG': 'telemetry-analysis-worker-instance',
        'EMAIL_SOURCE': '*****@*****.**',
        'MAX_CLUSTER_SIZE': 30,

        # Tags for accounting purposes
        'ACCOUNTING_APP_TAG': 'telemetry-analysis',
        'ACCOUNTING_TYPE_TAG': 'worker',

        # Buckets for storing S3 data
        'CODE_BUCKET': 'telemetry-analysis-code-2',
        'PUBLIC_DATA_BUCKET': 'telemetry-public-analysis-2',
        'PRIVATE_DATA_BUCKET': 'telemetry-private-analysis-2',
        'LOG_BUCKET': 'telemetry-analysis-logs-2'
    }
    PUBLIC_DATA_URL = 'https://s3-{}.amazonaws.com/{}/'.format(
        AWS_CONFIG['AWS_REGION'], AWS_CONFIG['PUBLIC_DATA_BUCKET'])
    PUBLIC_NB_URL = 'https://nbviewer.jupyter.org/url/s3-{}.amazonaws.com/{}/'.format(
        AWS_CONFIG['AWS_REGION'], AWS_CONFIG['PUBLIC_DATA_BUCKET'])
Beispiel #3
0
class CeleryMixin:
    CELERY_ACCEPT_CONTENT = values.ListValue(['application/json'])
    CELERY_ENABLE_UTC = values.BooleanValue(True)
    CELERY_IMPORTS = values.ListValue([])
    CELERY_INCLUDE = values.ListValue([])
    CELERY_TIMEZONE = values.Value('UTC')

    CELERYBEAT_MAX_LOOP_INTERVAL = values.Value(0)
    CELERYBEAT_SCHEDULE = {}
    CELERYBEAT_SCHEDULER = values.Value('celery.beat:PersistentScheduler')
    CELERYBEAT_SCHEDULE_FILENAME = values.Value('celerybeat-schedule')
    CELERYBEAT_SYNC_EVERY = values.PositiveIntegerValue(0)

    BROKER_URL = values.Value(None)
    # BROKER_TRANSPORT
    BROKER_TRANSPORT_OPTIONS = {}
    BROKER_CONNECTION_TIMEOUT = values.FloatValue(4.0)
    BROKER_CONNECTION_RETRY = values.BooleanValue(True)
    BROKER_CONNECTION_MAX_RETRIES = values.PositiveIntegerValue(100)
    BROKER_FAILOVER_STRATEGY = values.Value('round-robin')
    BROKER_HEARTBEAT = values.FloatValue(120.0)
    BROKER_LOGIN_METHOD = values.Value('AMQPLAIN')
    BROKER_POOL_LIMIT = values.PositiveIntegerValue(10)
    BROKER_USE_SSL = values.BooleanValue(False)

    # CELERY_CACHE_BACKEND no longer used
    CELERY_CACHE_BACKEND_OPTIONS = {}

    CASSANDRA_COLUMN_FAMILY = values.Value(None)
    CASSANDRA_ENTRY_TTL = values.PositiveIntegerValue(None)
    CASSANDRA_KEYSPACE = values.Value(None)
    CASSANDRA_PORT = values.PositiveIntegerValue(9042)
    CASSANDRA_READ_CONSISTENCY = values.Value(None)
    CASSANDRA_OPTIONS = {}

    S3_ACCESS_KEY_ID = values.Value(None)
    S3_SECRET_ACCESS_KEY = values.Value(None)
    S3_BUCKET = values.Value(None)
    S3_BASE_PATH = values.Value(None)
    S3_ENDPOINT_URL = values.Value(None)
    S3_REGION = values.Value(None)

    CELERY_COUCHBASE_BACKEND_SETTINGS = {}
    CELERY_ARANGODB_BACKEND_SETTINGS = {}
    CELERY_MONGODB_BACKEND_SETTINGS = {}

    CELERY_EVENT_QUEUE_EXPIRES = values.FloatValue(60.0)
    CELERY_EVENT_QUEUE_TTL = values.FloatValue(5.0)
    CELERY_EVENT_QUEUE_PREFIX = values.Value('celeryev')
    CELERY_EVENT_SERIALIZER = values.Value('json')

    CELERY_REDIS_DB = values.Value(None)
    CELERY_REDIS_HOST = values.Value(None)
    CELERY_REDIS_MAX_CONNECTIONS = values.PositiveIntegerValue(None)
    CELERY_REDIS_PASSWORD = values.Value(None)
    CELERY_REDIS_PORT = values.PositiveIntegerValue(None)
    CELERY_REDIS_BACKEND_USE_SSL = values.BooleanValue(False)

    CELERY_RESULT_BACKEND = values.Value(None)
    CELERY_MAX_CACHED_RESULTS = values.BooleanValue(False)
    CELERY_MESSAGE_COMPRESSION = values.Value(None)

    CELERY_RESULT_EXCHANGE = values.Value(None)
    CELERY_RESULT_EXCHANGE_TYPE = values.Value(None)
    # CELERY_RESULT_EXPIRES timedelta 1 day.
    CELERY_RESULT_PERSISTENT = values.BooleanValue(False)
    CELERY_RESULT_SERIALIZER = values.Value('json')
    CELERY_RESULT_DBURI = values.Value(None)
    CELERY_RESULT_ENGINE_OPTIONS = {}
    # _DB_SHORT_LIVED_SESSIONS
    CELERY_RESULT_DB_TABLE_NAMES = values.ListValue([])
    CELERY_SECURITY_CERTIFICATE = values.Value(None)
    CELERY_SECURITY_CERT_STORE = values.Value(None)
    CELERY_SECURITY_KEY = values.Value(None)

    CELERY_ACKS_LATE = values.BooleanValue(False)
    CELERY_ACKS_ON_FAILURE_OR_TIMEOUT = values.BooleanValue(True)
    CELERY_ALWAYS_EAGER = values.BooleanValue(False)
    CELERY_ANNOTATIONS = None  # dict/list
    CELERY_COMPRESSION = values.Value(None)
    CELERY_CREATE_MISSING_QUEUES = values.BooleanValue(True)
    CELERY_DEFAULT_DELIVERY_MODE = values.Value('persistent')
    # CELERY_DEFAULT_EXCHANGE
    CELERY_DEFAULT_EXCHANGE_TYPE = values.Value('direct')
    CELERY_DEFAULT_QUEUE = values.Value('celery')
    CELERY_DEFAULT_RATE_LIMIT = values.Value(None)
    # CELERY_DEFAULT_ROUTING_KEY str
    CELERY_EAGER_PROPAGATES = values.BooleanValue(False)
    CELERY_IGNORE_RESULT = values.BooleanValue(False)
    CELERY_PUBLISH_RETRY = values.BooleanValue(True)
    # CELERY_PUBLISH_RETRY_POLICY
    CELERY_QUEUES = None
    CELERY_ROUTES = None
    CELERY_SEND_SENT_EVENT = values.BooleanValue(False)
    CELERY_SERIALIZER = values.Value('json')
    CELERYD_SOFT_TIME_LIMIT = values.PositiveIntegerValue(None)
    CELERYD_TIME_LIMIT = values.PositiveIntegerValue(None)
    CELERY_TRACK_STARTED = values.BooleanValue(False)

    CELERYD_AGENT = values.Value(None)
    CELERYD_AUTOSCALER = values.Value('celery.worker.autoscale:Autoscaler')
    CELERYD_CONCURRENCY = values.PositiveIntegerValue(None)
    CELERYD_CONSUMER = values.Value('celery.worker.consumer:Consumer')

    CELERY_WORKER_DIRECT = values.BooleanValue(False)
    CELERY_DISABLE_RATE_LIMITS = values.BooleanValue(False)
    CELERY_ENABLE_REMOTE_CONTROL = values.BooleanValue(True)
    CELERYD_HIJACK_ROOT_LOGGER = values.BooleanValue(True)
    # CELERYD_LOG_COLOR
    CELERYD_LOG_FORMAT = values.Value(
        '[%(asctime)s: %(levelname)s/%(processName)s] %(message)s')
    CELERYD_WORKER_LOST_WAIT = values.FloatValue(10.0)
    CELERYD_MAX_TASKS_PER_CHILD = values.PositiveIntegerValue(None)
    CELERYD_POOL = values.Value('prefork')
    # CELERYD_POOL_PUTLOCKS ?
    CELERYD_POOL_RESTARTS = values.BooleanValue(False)
    CELERYD_PREFETCH_MULTIPLIER = values.PositiveIntegerValue(4)
    CELERYD_REDIRECT_STDOUTS = values.BooleanValue(True)
    CELERYD_REDIRECT_STDOUTS_LEVEL = values.Value('WARNING')
    CELERY_SEND_EVENTS = values.BooleanValue(False)
    CELERYD_STATE_DB = values.Value(None)
    CELERYD_TASK_LOG_FORMAT = values.Value("""[%(asctime)s: %(levelname)s/%(processName)s]
[%(task_name)s(%(task_id)s)] %(message)s""")
    CELERYD_TIMER = values.Value(None)
    CELERYD_TIMER_PRECISION = values.FloatValue(1.0)
class Settings(Configuration):
    ACCESS_TOKEN_LIFETIME_MINUTES = values.FloatValue()
    ADMIN_HEADER_COLOR = values.Value()
    ADMIN_HEADER_TITLE = "{environment}{title}".format(
        environment=ENVIRONMENT or '{DJANGO_ENVIRONMENT}',
        title=values.Value('{DJANGO_ADMIN_HEADER_TITLE}',
                           environ_name='ADMIN_HEADER_TITLE'),
    )
    ADMINS = values.SingleNestedListValue([])
    ALLOWED_HOSTS = values.ListValue([])
    ANYMAIL = values.DictValue()
    AUTH_PASSWORD_VALIDATORS = [
        {
            'NAME':
            'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
        },
        {
            'NAME':
            'django.contrib.auth.password_validation.MinimumLengthValidator',
        },
        {
            'NAME':
            'django.contrib.auth.password_validation.CommonPasswordValidator',
        },
        {
            'NAME':
            'django.contrib.auth.password_validation.NumericPasswordValidator',
        },
    ]
    AUTH_USER_MODEL = "main.User"
    CORS_ORIGIN_WHITELIST = values.ListValue([])
    DATABASE_ENGINE = values.Value(environ_required=True)
    DATABASE_NAME = values.Value(environ_required=True)
    DATABASE_USER = values.Value()
    DATABASE_PASSWORD = values.Value()
    DATABASE_HOST = values.Value()
    DATABASE_PORT = values.Value()

    @property
    def DATABASES(self):
        return {
            'default': {
                'ENGINE': self.DATABASE_ENGINE,
                'NAME': self.DATABASE_NAME,
                'USER': self.DATABASE_USER,
                'PASSWORD': self.DATABASE_PASSWORD,
                'HOST': self.DATABASE_HOST,
                'PORT': self.DATABASE_PORT,
            }
        }

    # SECURITY WARNING: don't run with debug turned on in production!
    DEBUG = values.BooleanValue(False)
    DEFAULT_FROM_EMAIL = values.Value()
    ELASTICSEARCH_HOST = values.Value()
    ELASTICSEARCH_PORT = values.Value()

    @property
    def ELASTICSEARCH_DSL(self):
        # https://django-configurations.readthedocs.io/en/stable/patterns/#property-settings
        return {
            'default': {
                'hosts':
                'http://{}:{}'.format(self.ELASTICSEARCH_HOST,
                                      self.ELASTICSEARCH_PORT)
            }
        }

    EMAIL_BACKEND = values.Value()
    EMAIL_HOST = values.Value()
    EMAIL_HOST_PASSWORD = values.Value()
    EMAIL_HOST_USER = values.Value()
    EMAIL_PORT = values.Value()
    EMAIL_USE_TLS = values.BooleanValue(False)
    INSTALLED_APPS = [
        # Django apps
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        # Third party
        'anymail',
        'django_extensions',
        'django_elasticsearch_dsl',
        'rest_framework',
        'rest_framework_simplejwt.token_blacklist',
        'corsheaders',
        'django_filters',
        # Local apps
        'main.apps.MainConfig'
    ]
    LANGUAGE_CODE = values.Value('en-us')
    LOGGING = logging_config()
    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'whitenoise.middleware.WhiteNoiseMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'corsheaders.middleware.CorsMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
        'main.middleware.timezone_middleware.TimezoneMiddleware',
    ]
    RECAPTCHA_PRIVATE_KEY = values.SecretValue()
    RECAPTCHA_URL = values.Value(
        default="https://www.google.com/recaptcha/api/siteverify")
    REST_FRAMEWORK = {
        'DATETIME_FORMAT':
        '%Y-%m-%d %H:%M:%S',
        'DEFAULT_AUTHENTICATION_CLASSES':
        ('rest_framework_simplejwt.authentication.JWTAuthentication', ),
        'DEFAULT_PERMISSION_CLASSES':
        ('rest_framework.permissions.IsAuthenticated', ),
        'DEFAULT_RENDERER_CLASSES':
        ('rest_framework.renderers.JSONRenderer', ),
        'DEFAULT_VERSIONING_CLASS':
        'rest_framework.versioning.NamespaceVersioning',
    }
    ROOT_URLCONF = 'storageofknowledge.urls'
    SECRET_KEY = values.SecretValue()
    SERVER_EMAIL = values.Value()
    SESSION_COOKIE_AGE = values.IntegerValue(5 * 60)  # 5 minutes
    SESSION_SAVE_EVERY_REQUEST = values.BooleanValue(True)

    @property
    def SIMPLE_JWT(self):
        # https://django-configurations.readthedocs.io/en/stable/patterns/#property-settings
        return {
            'ROTATE_REFRESH_TOKENS':
            True,
            'ACCESS_TOKEN_LIFETIME':
            timedelta(minutes=self.ACCESS_TOKEN_LIFETIME_MINUTES)
        }

    STATIC_ROOT = values.Value(None)
    STATIC_URL = '/static/'
    STATICFILES_DIRS = values.SingleNestedListValue([])
    STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [(os.path.join(BASE_DIR, "templates"))],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                    'django.template.context_processors.media',
                    'main.context_processors.from_settings',
                ],
            },
        },
    ]
    TIME_ZONE = 'UTC'
    USE_I18N = True
    USE_L10N = True
    USE_TZ = True
    USER_CONFIRMATION_LIFETIME_HOURS = values.FloatValue()
    WSGI_APPLICATION = 'storageofknowledge.wsgi.application'
Beispiel #5
0
class Base(Configuration):
    """Base configuration every configuration (aka environment) should inherit from.

    It depends on an environment variable that SHOULD be defined:
    - DJANGO_SECRET_KEY

    You may also want to override default configuration by setting the following
    environment variables:
    - DJANGO_DEBUG
    """

    DATA_DIR = values.Value(os.path.join("/", "data"))

    # Static files (CSS, JavaScript, Images)
    STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"), )
    STATIC_URL = "/static/"
    MEDIA_URL = "/media/"
    # Allow to configure location of static/media files for non-Docker installation
    MEDIA_ROOT = values.Value(os.path.join(str(DATA_DIR), "media"))
    STATIC_ROOT = values.Value(os.path.join(str(DATA_DIR), "static"))

    SECRET_KEY = values.SecretValue()

    DEBUG = values.BooleanValue(False)

    DATABASES = {
        "default": {
            "ENGINE":
            values.Value(
                "django.db.backends.postgresql",
                environ_name="DATABASE_ENGINE",
                environ_prefix=None,
            ),
            "NAME":
            values.Value("marsha",
                         environ_name="POSTGRES_DB",
                         environ_prefix=None),
            "USER":
            values.Value("marsha_user",
                         environ_name="POSTGRES_USER",
                         environ_prefix=None),
            "PASSWORD":
            values.Value("pass",
                         environ_name="POSTGRES_PASSWORD",
                         environ_prefix=None),
            "HOST":
            values.Value("localhost",
                         environ_name="POSTGRES_HOST",
                         environ_prefix=None),
            "PORT":
            values.Value(5432,
                         environ_name="POSTGRES_PORT",
                         environ_prefix=None),
        }
    }

    ALLOWED_HOSTS = []

    SITE_ID = 1

    SECURE_BROWSER_XSS_FILTER = True
    SECURE_CONTENT_TYPE_NOSNIFF = True
    X_FRAME_OPTIONS = "DENY"
    SECURE_REFERRER_POLICY = "same-origin"
    SILENCED_SYSTEM_CHECKS = values.ListValue([])
    CSRF_TRUSTED_ORIGINS = values.ListValue([])

    # Application definition

    INSTALLED_APPS = [
        "django.contrib.admin.apps.SimpleAdminConfig",
        "django.contrib.auth",
        "django.contrib.contenttypes",
        "django.contrib.sessions",
        "django.contrib.messages",
        "django.contrib.sites",
        "django.contrib.staticfiles",
        "django_extensions",
        "dockerflow.django",
        "waffle",
        "rest_framework",
        "corsheaders",
        "marsha.core.apps.CoreConfig",
        "marsha.bbb.apps.BbbConfig",
        "marsha.websocket.apps.WebsocketConfig",
        "channels",
    ]

    MIDDLEWARE = [
        "corsheaders.middleware.CorsMiddleware",
        "django.middleware.security.SecurityMiddleware",
        "whitenoise.middleware.WhiteNoiseMiddleware",
        "django.contrib.sessions.middleware.SessionMiddleware",
        "django.middleware.common.CommonMiddleware",
        "django.middleware.csrf.CsrfViewMiddleware",
        "django.contrib.auth.middleware.AuthenticationMiddleware",
        "django.contrib.messages.middleware.MessageMiddleware",
        "django.middleware.clickjacking.XFrameOptionsMiddleware",
        "dockerflow.django.middleware.DockerflowMiddleware",
        "waffle.middleware.WaffleMiddleware",
    ]

    ROOT_URLCONF = "marsha.urls"

    TEMPLATES = [{
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ]
        },
    }]

    AUTH_USER_MODEL = "core.User"

    ASGI_APPLICATION = "marsha.asgi.application"

    # For sentinels:
    # CHANNEL_LAYERS = {
    #     "default": {
    #         "BACKEND": "marsha.websocket.layers.JsonRedisChannelLayer",
    #         "CONFIG": {
    #             "hosts": [{
    #                 "sentinels": [(SENTINEL_HOST, SENTINEL_PORT)],
    #                 "master_name": SENTINEL_MASTER,
    #             }],
    #         },
    #     },
    # }
    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "marsha.websocket.layers.JsonRedisChannelLayer",
            "CONFIG": {
                "hosts":
                values.ListValue([("redis", 6379)],
                                 environ_name="REDIS_HOST",
                                 environ_prefix=None),
            },
        },
    }

    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES":
        ("rest_framework_simplejwt.authentication.JWTTokenUserAuthentication",
         ),
        "EXCEPTION_HANDLER":
        "marsha.core.views.exception_handler",
        "DEFAULT_PAGINATION_CLASS":
        "rest_framework.pagination.LimitOffsetPagination",
        "PAGE_SIZE":
        50,
        "DEFAULT_THROTTLE_RATES": {
            "anon": "3/minute",
            "live_registration": "3/minute",
        },
    }

    # WAFFLE
    WAFFLE_CREATE_MISSING_SWITCHES = True

    # Password validation
    # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
    AUTH_PASSWORD_VALIDATORS = [
        {
            "NAME":
            "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
        },
        {
            "NAME":
            "django.contrib.auth.password_validation.MinimumLengthValidator"
        },
        {
            "NAME":
            "django.contrib.auth.password_validation.CommonPasswordValidator"
        },
        {
            "NAME":
            "django.contrib.auth.password_validation.NumericPasswordValidator"
        },
    ]

    JWT_SIGNING_KEY = values.Value(SECRET_KEY)

    # Internationalization
    # https://docs.djangoproject.com/en/2.0/topics/i18n/

    # Django sets `LANGUAGES` by default with all supported languages. Let's save it to a
    # different setting before overriding it with the languages active in Marsha. We can use it
    # for example for the choice of time text tracks languages which should not be limited to
    # the few languages active in Marsha.
    # pylint: disable=no-member
    ALL_LANGUAGES = Configuration.LANGUAGES

    LANGUAGE_CODE = "en-us"

    # Careful! Languages should be ordered by priority, as this tuple is used to get
    # fallback/default languages throughout the app.
    # Use "en" as default as it is the language that is most likely to be spoken by any visitor
    # when their preferred language, whatever it is, is unavailable
    LANGUAGES = [("en", _("english")), ("fr", _("french"))]
    LANGUAGES_DICT = dict(LANGUAGES)

    # Internationalization
    TIME_ZONE = "UTC"
    USE_I18N = True
    USE_L10N = True
    USE_TZ = True

    REACT_LOCALES = values.ListValue(["en_US", "es_ES", "fr_FR", "fr_CA"])
    DEFAULT_LTI_LAUNCH_PRESENTATION_LOCALE = values.Value("en")

    VIDEO_RESOLUTIONS = [144, 240, 480, 720, 1080]
    STORAGE_BACKEND = values.Value("marsha.core.storage.s3")

    # Logging
    LOGGING = values.DictValue({
        "version": 1,
        "disable_existing_loggers": False,
        "handlers": {
            "console": {
                "class": "logging.StreamHandler",
                "stream": "ext://sys.stdout",
            },
        },
        "loggers": {
            "marsha": {
                "handlers": ["console"],
                "level": "INFO",
                "propagate": True
            },
        },
    })

    # AWS
    AWS_ACCESS_KEY_ID = values.SecretValue()
    AWS_SECRET_ACCESS_KEY = values.SecretValue()
    AWS_S3_REGION_NAME = values.Value("eu-west-1")
    AWS_S3_URL_PROTOCOL = values.Value("https")
    AWS_BASE_NAME = values.Value()
    UPDATE_STATE_SHARED_SECRETS = values.ListValue()
    AWS_UPLOAD_EXPIRATION_DELAY = values.Value(24 * 60 * 60)  # 24h
    AWS_MEDIALIVE_ROLE_ARN = values.SecretValue()
    AWS_MEDIAPACKAGE_HARVEST_JOB_ARN = values.SecretValue()

    # BBB
    BBB_ENABLED = values.BooleanValue(False)
    BBB_API_ENDPOINT = values.Value()
    BBB_API_SECRET = values.Value(None)

    # LIVE_RAW
    LIVE_RAW_ENABLED = values.BooleanValue(False)

    # Cloud Front key pair for signed urls
    CLOUDFRONT_ACCESS_KEY_ID = values.Value(None)
    CLOUDFRONT_PRIVATE_KEY_PATH = values.Value(
        os.path.join(BASE_DIR, "..", ".ssh", "cloudfront_private_key"))
    CLOUDFRONT_SIGNED_URLS_ACTIVE = values.BooleanValue(True)
    CLOUDFRONT_SIGNED_URLS_VALIDITY = 2 * 60 * 60  # 2 hours

    CLOUDFRONT_DOMAIN = values.Value(None)

    BYPASS_LTI_VERIFICATION = values.BooleanValue(False)

    # Cache
    APP_DATA_CACHE_DURATION = values.Value(60)  # 60 secondes

    SENTRY_DSN = values.Value(None)

    # Resource max file size
    DOCUMENT_SOURCE_MAX_SIZE = values.Value(2**30)  # 1GB
    VIDEO_SOURCE_MAX_SIZE = values.Value(2**30)  # 1GB
    SUBTITLE_SOURCE_MAX_SIZE = values.Value(2**20)  # 1MB
    THUMBNAIL_SOURCE_MAX_SIZE = values.Value(10 * (2**20))  # 10MB
    SHARED_LIVE_MEDIA_SOURCE_MAX_SIZE = values.Value(300 * (2**20))  # 300MB

    EXTERNAL_JAVASCRIPT_SCRIPTS = values.ListValue([])

    VIDEO_PLAYER = values.Value("videojs")
    FRONT_UPLOAD_POLL_INTERVAL = values.Value("60")

    MAINTENANCE_MODE = values.BooleanValue(False)

    # XMPP Settings
    LIVE_CHAT_ENABLED = values.BooleanValue(False)
    XMPP_BOSH_URL = values.Value(None)
    XMPP_CONVERSE_PERSISTENT_STORE = values.Value("localStorage")
    XMPP_WEBSOCKET_URL = values.Value(None)
    XMPP_CONFERENCE_DOMAIN = values.Value(None)
    XMPP_PRIVATE_ADMIN_JID = values.Value(None)
    XMPP_PRIVATE_SERVER_PORT = values.Value(5222)
    XMPP_PRIVATE_SERVER_PASSWORD = values.Value(None)
    XMPP_JWT_SHARED_SECRET = values.Value(None)
    XMPP_JWT_ISSUER = values.Value("marsha")
    XMPP_JWT_AUDIENCE = values.Value("marsha")
    XMPP_DOMAIN = values.Value(None)

    # LIVE SETTINGS
    NB_DAYS_BEFORE_DELETING_LIVE_RECORDINGS = values.Value(14)
    NB_SECONDS_LIVING_DEV_STACK = values.PositiveIntegerValue(600)
    LIVE_PLAYLIST_WINDOW_SECONDS = values.PositiveIntegerValue(10)
    LIVE_SEGMENT_DURATION_SECONDS = values.PositiveIntegerValue(4)
    LIVE_FRAMERATE_NUMERATOR = values.PositiveIntegerValue(24000)
    LIVE_FRAMERATE_DENOMINATOR = values.PositiveIntegerValue(1000)
    LIVE_GOP_SIZE = values.FloatValue(4)

    # JITSI SETTINGS
    JITSI_EXTERNAL_API_URL = values.Value(
        "https://meet.jit.si/external_api.js")
    JITSI_DOMAIN = values.Value("meet.jit.si")
    JITSI_CONFIG_OVERWRITE = values.DictValue({})
    JITSI_INTERFACE_CONFIG_OVERWRITE = values.DictValue({})

    # LIVE PAIRING
    LIVE_PAIRING_EXPIRATION_SECONDS = 60

    # SHARED LIVE MEDIA SETTINGS
    ALLOWED_SHARED_LIVE_MEDIA_MIME_TYPES = values.ListValue(
        ["application/pdf"])

    # Cors
    CORS_ALLOW_ALL_ORIGINS = values.BooleanValue(False)
    CORS_ALLOWED_ORIGINS = values.ListValue([])
    CORS_ALLOWED_ORIGIN_REGEXES = values.ListValue([])
    CORS_URLS_REGEX = values.Value(r"^/api/pairing-challenge$")
    CORS_ALLOW_METHODS = values.ListValue(["POST", "OPTIONS"])
    CORS_ALLOW_HEADERS = values.ListValue(list(default_headers))

    # Mail
    EMAIL_BACKEND = values.Value("django.core.mail.backends.smtp.EmailBackend")
    EMAIL_HOST = values.Value(None)
    EMAIL_HOST_USER = values.Value(None)
    EMAIL_HOST_PASSWORD = values.Value(None)
    EMAIL_PORT = values.PositiveIntegerValue(None)
    EMAIL_USE_TLS = values.BooleanValue(False)
    EMAIL_FROM = values.Value("*****@*****.**")

    # REMINDERS SENT for scheduled webinars
    REMINDER_1, REMINDER_2, REMINDER_3, REMINDER_IS_STARTED, REMINDER_ERROR = (
        "REMINDER_1",
        "REMINDER_2",
        "REMINDER_3",
        "REMINDER_IS_STARTED",
        "REMINDER_ERROR",
    )
    # keys for REMINDERS_STEP
    (
        REMINDER_ELAPSED_LABEL,
        REGISTER_EXCLUDE_STEP,
        REMINDER_KEY_REGISTER_BEFORE_S,
        REMINDER_KEY_STARTS_IN_S,
        REMINDER_OBJECT_MAIL,
    ) = (
        "REMINDER_ELAPSED_LABEL",
        "REGISTER_EXCLUDE_STEP",
        "STARTS_IN_S",
        "REGISTER_BEFORE_S",
        "REMINDER_OBJECT_MAIL",
    )
    REMINDERS_STEP = values.DictValue({
        REMINDER_1: {
            REMINDER_ELAPSED_LABEL: _("5 minutes"),
            REGISTER_EXCLUDE_STEP: [],
            REMINDER_KEY_STARTS_IN_S:
            300,  # webinar starts in less than 5 minutes
            REMINDER_KEY_REGISTER_BEFORE_S: 10800,  # three hours before
            REMINDER_OBJECT_MAIL: _("Live starts in less than 5 minutes"),
        },
        REMINDER_2: {
            REMINDER_ELAPSED_LABEL: _("3 hours"),
            REGISTER_EXCLUDE_STEP:
            [REMINDER_1],  # if step REMINDER_1 is done it will cancel this one
            REMINDER_KEY_REGISTER_BEFORE_S: 86400,  # 1 day before in seconds
            REMINDER_KEY_STARTS_IN_S:
            10800,  # webinar starts in less than 3 hours
            REMINDER_OBJECT_MAIL: _("Live starts in less than 3 hours"),
        },
        REMINDER_3: {
            REMINDER_ELAPSED_LABEL: _("3 days"),
            REGISTER_EXCLUDE_STEP: [REMINDER_1, REMINDER_2],
            REMINDER_KEY_REGISTER_BEFORE_S: 30 * 86400,  # thirty days before
            REMINDER_KEY_STARTS_IN_S:
            3 * 86400,  # webinar starts in less than 3 days
            REMINDER_OBJECT_MAIL: _("Live starts in less than 3 days"),
        },
    })
    REMINDERS_SECRET = values.Value()

    # pylint: disable=invalid-name
    @property
    def AWS_SOURCE_BUCKET_NAME(self):
        """Source bucket name.

        If this setting is set in an environment variable we use it. Otherwise
        the value is computed with the AWS_BASE_NAME value.
        """
        return os.environ.get("DJANGO_AWS_SOURCE_BUCKET_NAME",
                              f"{self.AWS_BASE_NAME}-marsha-source")

    # pylint: disable=invalid-name
    @property
    def AWS_DESTINATION_BUCKET_NAME(self):
        """Destination bucket name.

        If this setting is set in an environment variable we use it. Otherwise
        the value is computed with the AWS_BASE_NAME value.
        """
        return os.environ.get(
            "DJANGO_AWS_DESTINATION_BUCKET_NAME",
            f"{self.AWS_BASE_NAME}-marsha-destination",
        )

    # pylint: disable=invalid-name
    @property
    def SIMPLE_JWT(self):
        """Define settings for `djangorestframework_simplejwt`.

        The JWT_SIGNING_KEY must be evaluated late as the jwt library check for string type.
        """
        return {
            "ACCESS_TOKEN_LIFETIME":
            timedelta(days=1),
            "ALGORITHM":
            "HS256",
            "SIGNING_KEY":
            str(self.JWT_SIGNING_KEY),
            "USER_ID_CLAIM":
            "resource_id",
            "AUTH_TOKEN_CLASSES":
            ("rest_framework_simplejwt.tokens.AccessToken", ),
        }

    # pylint: disable=invalid-name
    @property
    def RELEASE(self):
        """
        Return the release information.

        Delegate to the module function to enable easier testing.
        """
        return get_release()

    @classmethod
    def _get_environment(cls):
        """Environment in which the application is launched."""
        return cls.__name__.lower()

    # pylint: disable=invalid-name
    @property
    def ENVIRONMENT(self):
        """Environment in which the application is launched."""
        return self._get_environment()

    @classmethod
    def post_setup(cls):
        """Post setup configuration.

        This is the place where you can configure settings that require other
        settings to be loaded.
        """
        super().post_setup()

        # The DJANGO_SENTRY_DSN environment variable should be set to activate
        # sentry for an environment
        if cls.SENTRY_DSN is not None:
            sentry_sdk.init(
                dsn=cls.SENTRY_DSN,
                environment=cls._get_environment(),
                release=get_release(),
                integrations=[DjangoIntegration()],
            )
            with sentry_sdk.configure_scope() as scope:
                scope.set_extra("application", "backend")
Beispiel #6
0
class Base(Configuration):
    """Base configuration every configuration (aka environment) should inherit from.

    It depends on an environment variable that SHOULD be defined:
    - DJANGO_SECRET_KEY

    You may also want to override default configuration by setting the following
    environment variables:
    - DJANGO_DEBUG
    """

    DATA_DIR = values.Value(os.path.join("/", "data"))

    # Static files (CSS, JavaScript, Images)
    STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"), )
    STATIC_URL = "/static/"
    MEDIA_URL = "/media/"
    # Allow to configure location of static/media files for non-Docker installation
    MEDIA_ROOT = values.Value(os.path.join(str(DATA_DIR), "media"))
    STATIC_ROOT = values.Value(os.path.join(str(DATA_DIR), "static"))

    SECRET_KEY = values.SecretValue()

    DEBUG = values.BooleanValue(False)

    DATABASES = {
        "default": {
            "ENGINE":
            values.Value(
                "django.db.backends.postgresql",
                environ_name="DATABASE_ENGINE",
                environ_prefix=None,
            ),
            "NAME":
            values.Value("marsha",
                         environ_name="POSTGRES_DB",
                         environ_prefix=None),
            "USER":
            values.Value("marsha_user",
                         environ_name="POSTGRES_USER",
                         environ_prefix=None),
            "PASSWORD":
            values.Value("pass",
                         environ_name="POSTGRES_PASSWORD",
                         environ_prefix=None),
            "HOST":
            values.Value("localhost",
                         environ_name="POSTGRES_HOST",
                         environ_prefix=None),
            "PORT":
            values.Value(5432,
                         environ_name="POSTGRES_PORT",
                         environ_prefix=None),
        }
    }

    ALLOWED_HOSTS = []

    SITE_ID = 1

    SECURE_BROWSER_XSS_FILTER = True
    SECURE_CONTENT_TYPE_NOSNIFF = True
    X_FRAME_OPTIONS = "DENY"
    SECURE_REFERRER_POLICY = "same-origin"
    SILENCED_SYSTEM_CHECKS = values.ListValue([])
    CSRF_TRUSTED_ORIGINS = values.ListValue([])

    # Application definition

    INSTALLED_APPS = [
        "django.contrib.auth",
        "django.contrib.contenttypes",
        "django.contrib.sessions",
        "django.contrib.messages",
        "django.contrib.sites",
        "django.contrib.staticfiles",
        "django_extensions",
        "dockerflow.django",
        "waffle",
        "rest_framework",
        "corsheaders",
        "channels",
        "parler",  # django-parler, for translated models
        "social_django.apps.PythonSocialAuthConfig",  # python-social-auth for Django
        "social_edu_federation.django.apps.PythonSocialEduFedAuthConfig",
        # Marsha
        "marsha.account.apps.AccountConfig",
        "marsha.core.apps.MarshaAdminConfig",
        "marsha.core.apps.CoreConfig",
        "marsha.bbb.apps.BbbConfig",
        "marsha.markdown.apps.MarkdownConfig",
        "marsha.websocket.apps.WebsocketConfig",
    ]
    MIDDLEWARE = [
        "corsheaders.middleware.CorsMiddleware",
        "django.middleware.security.SecurityMiddleware",
        "whitenoise.middleware.WhiteNoiseMiddleware",
        "django.contrib.sessions.middleware.SessionMiddleware",
        "django.middleware.locale.LocaleMiddleware",
        "django.middleware.common.CommonMiddleware",
        "django.middleware.csrf.CsrfViewMiddleware",
        "django.contrib.auth.middleware.AuthenticationMiddleware",
        "django.contrib.messages.middleware.MessageMiddleware",
        "django.middleware.clickjacking.XFrameOptionsMiddleware",
        "dockerflow.django.middleware.DockerflowMiddleware",
        "waffle.middleware.WaffleMiddleware",
        "social_django.middleware.SocialAuthExceptionMiddleware",
    ]

    ROOT_URLCONF = "marsha.urls"

    TEMPLATES = [{
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
                "social_django.context_processors.backends",
                "social_django.context_processors.login_redirect",
            ]
        },
    }]

    AUTH_USER_MODEL = "core.User"

    AUTHENTICATION_BACKENDS = [
        "social_edu_federation.backends.saml_fer.FERSAMLAuth",
        "django.contrib.auth.backends.ModelBackend",
    ]

    ASGI_APPLICATION = "marsha.asgi.application"

    # For sentinels:
    # CHANNEL_LAYERS = {
    #     "default": {
    #         "BACKEND": "marsha.websocket.layers.JsonRedisChannelLayer",
    #         "CONFIG": {
    #             "hosts": [{
    #                 "sentinels": [(SENTINEL_HOST, SENTINEL_PORT)],
    #                 "master_name": SENTINEL_MASTER,
    #             }],
    #         },
    #     },
    # }
    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "marsha.websocket.layers.JsonRedisChannelLayer",
            "CONFIG": {
                "hosts":
                values.ListValue([("redis", 6379)],
                                 environ_name="REDIS_HOST",
                                 environ_prefix=None),
            },
        },
    }

    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES":
        ("rest_framework_simplejwt.authentication.JWTStatelessUserAuthentication",
         ),
        "EXCEPTION_HANDLER":
        "marsha.core.views.exception_handler",
        "DEFAULT_PAGINATION_CLASS":
        "rest_framework.pagination.LimitOffsetPagination",
        "PAGE_SIZE":
        50,
        "DEFAULT_THROTTLE_RATES": {
            "anon":
            values.Value("3/minute",
                         environ_name="REST_FRAMEWORK_ANON_THROTTLE_RATE"),
            "live_session":
            values.Value(
                "3/minute",
                environ_name="REST_FRAMEWORK_LIVE_SESSION_THROTTLE_RATE"),
        },
    }

    # WAFFLE
    WAFFLE_CREATE_MISSING_SWITCHES = True

    # User login base Django settings
    LOGIN_REDIRECT_URL = "site"  # redirect to home (React site)
    LOGIN_URL = "account:login"

    # Password validation
    # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
    AUTH_PASSWORD_VALIDATORS = [
        {
            "NAME":
            "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
        },
        {
            "NAME":
            "django.contrib.auth.password_validation.MinimumLengthValidator"
        },
        {
            "NAME":
            "django.contrib.auth.password_validation.CommonPasswordValidator"
        },
        {
            "NAME":
            "django.contrib.auth.password_validation.NumericPasswordValidator"
        },
    ]

    JWT_SIGNING_KEY = values.Value(SECRET_KEY)

    # Internationalization
    # https://docs.djangoproject.com/en/2.0/topics/i18n/

    # Django sets `LANGUAGES` by default with all supported languages. Let's save it to a
    # different setting before overriding it with the languages active in Marsha. We can use it
    # for example for the choice of time text tracks languages which should not be limited to
    # the few languages active in Marsha.
    # pylint: disable=no-member
    ALL_LANGUAGES = Configuration.LANGUAGES

    LANGUAGE_CODE = "en-us"

    # Careful! Languages should be ordered by priority, as this tuple is used to get
    # fallback/default languages throughout the app.
    # Use "en" as default as it is the language that is most likely to be spoken by any visitor
    # when their preferred language, whatever it is, is unavailable
    LANGUAGES = [("en", _("english")), ("fr", _("french"))]
    LANGUAGES_DICT = dict(LANGUAGES)
    LOCALE_PATHS = (os.path.join(BASE_DIR, "../locale"), )
    # Internationalization
    TIME_ZONE = "UTC"
    USE_I18N = True
    USE_L10N = True
    USE_TZ = True

    REACT_LOCALES = values.ListValue(["en_US", "es_ES", "fr_FR", "fr_CA"])
    DEFAULT_LTI_LAUNCH_PRESENTATION_LOCALE = values.Value("en")

    PARLER_DEFAULT_LANGUAGE_CODE = LANGUAGES[0][0]
    PARLER_LANGUAGES = {
        None: tuple({"code": code} for code in LANGUAGES_DICT.keys()),
        "default": {
            "fallbacks": [PARLER_DEFAULT_LANGUAGE_CODE
                          ],  # defaults to PARLER_DEFAULT_LANGUAGE_CODE
            "hide_untranslated": False,
        },
    }
    # Admin tabs don't show up when only Site `None` is defined
    PARLER_LANGUAGES[SITE_ID] = PARLER_LANGUAGES[None]

    VIDEO_RESOLUTIONS = [144, 240, 480, 720, 1080]
    STORAGE_BACKEND = values.Value("marsha.core.storage.s3")

    # Logging
    LOGGING = values.DictValue({
        "version": 1,
        "disable_existing_loggers": False,
        "handlers": {
            "console": {
                "class": "logging.StreamHandler",
                "stream": "ext://sys.stdout",
            },
        },
        "loggers": {
            "marsha": {
                "handlers": ["console"],
                "level": "INFO",
                "propagate": True
            },
        },
    })

    # AWS
    AWS_ACCESS_KEY_ID = values.SecretValue()
    AWS_SECRET_ACCESS_KEY = values.SecretValue()
    AWS_S3_REGION_NAME = values.Value("eu-west-1")
    AWS_S3_URL_PROTOCOL = values.Value("https")
    AWS_BASE_NAME = values.Value()
    UPDATE_STATE_SHARED_SECRETS = values.ListValue()
    AWS_UPLOAD_EXPIRATION_DELAY = values.Value(24 * 60 * 60)  # 24h
    AWS_MEDIALIVE_ROLE_ARN = values.SecretValue()
    AWS_MEDIAPACKAGE_HARVEST_JOB_ARN = values.SecretValue()
    AWS_MEDIALIVE_INPUT_WAITER_DELAY = values.PositiveIntegerValue(5)
    AWS_MEDIALIVE_INPUT_WAITER_MAX_ATTEMPTS = values.PositiveIntegerValue(84)

    # LTI Config
    LTI_CONFIG_TITLE = values.Value("Marsha")
    LTI_CONFIG_DESCRIPTION = values.Value(
        "An LTI first, opensource and extensible Learning Content Management System"
    )
    LTI_CONFIG_ICON = values.Value("marsha_32x32_blue.png")
    LTI_CONFIG_URL = values.Value()
    LTI_CONFIG_CONTACT_EMAIL = values.Value()

    # BBB
    BBB_ENABLED = values.BooleanValue(False)
    BBB_API_ENDPOINT = values.Value()
    BBB_API_SECRET = values.Value(None)

    # Markdown application
    MARKDOWN_ENABLED = values.BooleanValue(False)

    # LIVE_RAW
    LIVE_RAW_ENABLED = values.BooleanValue(False)

    # Cloud Front key pair for signed urls
    CLOUDFRONT_PRIVATE_KEY_PATH = values.Value(
        os.path.join(BASE_DIR, "..", ".ssh", "cloudfront_private_key"))
    CLOUDFRONT_SIGNED_URLS_ACTIVE = values.BooleanValue(True)
    CLOUDFRONT_SIGNED_URLS_VALIDITY = 2 * 60 * 60  # 2 hours
    CLOUDFRONT_SIGNED_URL_CACHE_DURATION = values.Value(900)  # 15 minutes
    CLOUDFRONT_SIGNED_PUBLIC_KEY_ID = values.Value(None)

    CLOUDFRONT_DOMAIN = values.Value(None)

    BYPASS_LTI_VERIFICATION = values.BooleanValue(False)

    # Cache
    APP_DATA_CACHE_DURATION = values.Value(60)  # 60 secondes
    VIDEO_ATTENDANCES_CACHE_DURATION = values.Value(300)  # 5 minutes

    SENTRY_DSN = values.Value(None)

    # Resource max file size
    DOCUMENT_SOURCE_MAX_SIZE = values.Value(2**30)  # 1GB
    VIDEO_SOURCE_MAX_SIZE = values.Value(2**30)  # 1GB
    SUBTITLE_SOURCE_MAX_SIZE = values.Value(2**20)  # 1MB
    THUMBNAIL_SOURCE_MAX_SIZE = values.Value(10 * (2**20))  # 10MB
    SHARED_LIVE_MEDIA_SOURCE_MAX_SIZE = values.Value(300 * (2**20))  # 300MB

    EXTERNAL_JAVASCRIPT_SCRIPTS = values.ListValue([])

    VIDEO_PLAYER = values.Value("videojs")
    FRONT_UPLOAD_POLL_INTERVAL = values.Value("60")

    MAINTENANCE_MODE = values.BooleanValue(False)

    # XMPP Settings
    LIVE_CHAT_ENABLED = values.BooleanValue(False)
    XMPP_BOSH_URL = values.Value(None)
    XMPP_CONVERSE_PERSISTENT_STORE = values.Value("localStorage")
    XMPP_WEBSOCKET_URL = values.Value(None)
    XMPP_CONFERENCE_DOMAIN = values.Value(None)
    XMPP_PRIVATE_ADMIN_JID = values.Value(None)
    XMPP_PRIVATE_SERVER_PORT = values.Value(5222)
    XMPP_PRIVATE_SERVER_PASSWORD = values.Value(None)
    XMPP_JWT_SHARED_SECRET = values.Value(None)
    XMPP_JWT_ISSUER = values.Value("marsha")
    XMPP_JWT_AUDIENCE = values.Value("marsha")
    XMPP_DOMAIN = values.Value(None)

    # LIVE SETTINGS
    NB_DAYS_BEFORE_DELETING_LIVE_RECORDINGS = values.Value(14)
    NB_SECONDS_LIVING_DEV_STACK = values.PositiveIntegerValue(600)
    LIVE_PLAYLIST_WINDOW_SECONDS = values.PositiveIntegerValue(10)
    LIVE_SEGMENT_DURATION_SECONDS = values.PositiveIntegerValue(4)
    LIVE_FRAMERATE_NUMERATOR = values.PositiveIntegerValue(24000)
    LIVE_FRAMERATE_DENOMINATOR = values.PositiveIntegerValue(1000)
    LIVE_GOP_SIZE = values.FloatValue(4)

    # JITSI SETTINGS
    JITSI_EXTERNAL_API_URL = values.Value(
        "https://meet.jit.si/external_api.js")
    JITSI_DOMAIN = values.Value("meet.jit.si")
    JITSI_CONFIG_OVERWRITE = values.DictValue({})
    JITSI_INTERFACE_CONFIG_OVERWRITE = values.DictValue({})
    JITSI_JWT_APP_ID = values.Value()
    JITSI_JWT_APP_SECRET = values.Value()
    JITSI_JWT_TOKEN_EXPIRATION_SECONDS = values.PositiveIntegerValue(600)

    # LIVE PAIRING
    LIVE_PAIRING_EXPIRATION_SECONDS = 60

    # SHARED LIVE MEDIA SETTINGS
    ALLOWED_SHARED_LIVE_MEDIA_MIME_TYPES = values.ListValue(
        ["application/pdf"])

    # Cors
    CORS_ALLOW_ALL_ORIGINS = values.BooleanValue(False)
    CORS_ALLOWED_ORIGINS = values.ListValue([])
    CORS_ALLOWED_ORIGIN_REGEXES = values.ListValue([])
    CORS_URLS_REGEX = values.Value(r"^/api/pairing-challenge$")
    CORS_ALLOW_METHODS = values.ListValue(["POST", "OPTIONS"])
    CORS_ALLOW_HEADERS = values.ListValue(list(default_headers))

    # Mail
    EMAIL_BACKEND = values.Value("django.core.mail.backends.smtp.EmailBackend")
    EMAIL_HOST = values.Value(None)
    EMAIL_HOST_USER = values.Value(None)
    EMAIL_HOST_PASSWORD = values.Value(None)
    EMAIL_PORT = values.PositiveIntegerValue(None)
    EMAIL_USE_TLS = values.BooleanValue(False)
    EMAIL_FROM = values.Value("*****@*****.**")

    # REMINDERS SENT for scheduled webinars
    REMINDER_1, REMINDER_2, REMINDER_3, REMINDER_DATE_UPDATED, REMINDER_ERROR = (
        "REMINDER_1",
        "REMINDER_2",
        "REMINDER_3",
        "REMINDER_DATE_UPDATED",
        "REMINDER_ERROR",
    )
    # keys for REMINDERS_STEP
    (
        REMINDER_ELAPSED_LABEL,
        REGISTER_EXCLUDE_STEP,
        REMINDER_KEY_REGISTER_BEFORE_S,
        REMINDER_KEY_STARTS_IN_S,
        REMINDER_OBJECT_MAIL,
    ) = (
        "REMINDER_ELAPSED_LABEL",
        "REGISTER_EXCLUDE_STEP",
        "STARTS_IN_S",
        "REGISTER_BEFORE_S",
        "REMINDER_OBJECT_MAIL",
    )
    REMINDERS_STEP = values.DictValue({
        REMINDER_1: {
            REMINDER_ELAPSED_LABEL: _("5 minutes"),
            REGISTER_EXCLUDE_STEP: [],
            REMINDER_KEY_STARTS_IN_S:
            300,  # webinar starts in less than 5 minutes
            REMINDER_KEY_REGISTER_BEFORE_S: 10800,  # three hours before
            REMINDER_OBJECT_MAIL: _("Live starts in less than 5 minutes"),
        },
        REMINDER_2: {
            REMINDER_ELAPSED_LABEL: _("3 hours"),
            REGISTER_EXCLUDE_STEP:
            [REMINDER_1],  # if step REMINDER_1 is done it will cancel this one
            REMINDER_KEY_REGISTER_BEFORE_S: 86400,  # 1 day before in seconds
            REMINDER_KEY_STARTS_IN_S:
            10800,  # webinar starts in less than 3 hours
            REMINDER_OBJECT_MAIL: _("Live starts in less than 3 hours"),
        },
        REMINDER_3: {
            REMINDER_ELAPSED_LABEL: _("3 days"),
            REGISTER_EXCLUDE_STEP: [REMINDER_1, REMINDER_2],
            REMINDER_KEY_REGISTER_BEFORE_S: 30 * 86400,  # thirty days before
            REMINDER_KEY_STARTS_IN_S:
            3 * 86400,  # webinar starts in less than 3 days
            REMINDER_OBJECT_MAIL: _("Live starts in less than 3 days"),
        },
    })
    REMINDERS_SECRET = values.Value()

    # Settings related to statistics in potsie project
    STAT_BACKEND = values.Value("marsha.core.stats.grafana_xapi_fun_backend")
    STAT_BACKEND_SETTINGS = values.DictValue({
        "api_key":
        values.Value(environ_name="GRAFANA_XAPI_FUN_API_KEY",
                     environ_prefix=None),
        "api_endpoint":
        values.Value(environ_name="GRAFANA_XAPI_FUN_API_ENDPOINT",
                     environ_prefix=None),
        "api_datasource_id":
        values.Value(environ_name="GRAFANA_XAPI_FUN_API_DATASOURCE_ID",
                     environ_prefix=None),
        "api_datastream":
        values.Value(environ_name="GRAFANA_XAPI_FUN_API_DATASTREAM",
                     environ_prefix=None),
    })
    ATTENDANCE_POINTS = values.Value(20)
    ATTENDANCE_PUSH_DELAY = values.Value(60)

    # Python social auth
    SOCIAL_AUTH_JSONFIELD_ENABLED = True
    SOCIAL_AUTH_URL_NAMESPACE = "account:social"
    SOCIAL_AUTH_SAML_FER_IDP_FAKER = False

    SOCIAL_AUTH_SAML_FER_SECURITY_CONFIG = {
        "authnRequestsSigned":
        values.BooleanValue(
            default=True,
            environ_name=
            "SOCIAL_AUTH_SAML_FER_SECURITY_CONFIG_AUTH_REQUEST_SIGNED",
        ),
        "signMetadata":
        values.BooleanValue(
            default=True,
            environ_name="SOCIAL_AUTH_SAML_FER_SECURITY_CONFIG_SIGN_METADATA",
        ),
        "wantMessagesSigned":
        values.BooleanValue(
            default=True,
            environ_name=
            "SOCIAL_AUTH_SAML_FER_SECURITY_CONFIG_WANT_MESSAGES_SIGNED",
        ),
        "wantAssertionsSigned":
        values.BooleanValue(
            default=True,
            environ_name=
            "SOCIAL_AUTH_SAML_FER_SECURITY_CONFIG_WANT_ASSERTIONS_SIGNED",
        ),
        "wantAssertionsEncrypted":
        values.BooleanValue(
            default=True,
            environ_name=
            "SOCIAL_AUTH_SAML_FER_SECURITY_CONFIG_WANT_ASSERTIONS_ENCRYPTED",
        ),
        "rejectDeprecatedAlgorithm":
        values.BooleanValue(
            default=True,
            environ_name=
            "SOCIAL_AUTH_SAML_FER_SECURITY_CONFIG_REJECT_DEPRECATED_ALGORITHM",
        ),
    }

    # SOCIAL_AUTH_SAML_FER_SP_ENTITY_ID should be a URL that includes a domain name you own
    SOCIAL_AUTH_SAML_FER_SP_ENTITY_ID = values.Value()
    # SOCIAL_AUTH_SAML_FER_SP_PUBLIC_CERT X.509 certificate for the key pair that
    # your app will use
    SOCIAL_AUTH_SAML_FER_SP_PUBLIC_CERT = values.Value()
    # SOCIAL_AUTH_SAML_FER_SP_PRIVATE_KEY The private key to be used by your app
    SOCIAL_AUTH_SAML_FER_SP_PRIVATE_KEY = values.Value()

    # Next certificate management, keep empty when next certificate is still not known
    SOCIAL_AUTH_SAML_FER_SP_NEXT_PUBLIC_CERT = values.Value()
    SOCIAL_AUTH_SAML_FER_SP_EXTRA = ({
        "x509certNew":
        SOCIAL_AUTH_SAML_FER_SP_NEXT_PUBLIC_CERT,
    } if SOCIAL_AUTH_SAML_FER_SP_NEXT_PUBLIC_CERT else {})

    SOCIAL_AUTH_SAML_FER_ORG_INFO = {  # specify values for English at a minimum
        "en-US": {
            "name":
            values.Value(
                "marsha",
                environ_name="SOCIAL_AUTH_SAML_FER_ORG_INFO_NAME",
            ),
            "displayname":
            values.Value(
                "Marsha",
                environ_name="SOCIAL_AUTH_SAML_FER_ORG_INFO_DISPLAY_NAME",
            ),
            "url":
            values.Value(environ_name="SOCIAL_AUTH_SAML_FER_ORG_INFO_URL", ),
        }
    }
    # SOCIAL_AUTH_SAML_FER_TECHNICAL_CONTACT technical contact responsible for your app
    SOCIAL_AUTH_SAML_FER_TECHNICAL_CONTACT = {
        "givenName":
        values.Value(
            "Marsha dev team",
            environ_name="SOCIAL_AUTH_SAML_FER_TECHNICAL_CONTACT_NAME",
        ),
        "emailAddress":
        values.Value(
            environ_name="SOCIAL_AUTH_SAML_FER_TECHNICAL_CONTACT_EMAIL", ),
    }
    # SOCIAL_AUTH_SAML_FER_SUPPORT_CONTACT support contact for your app
    SOCIAL_AUTH_SAML_FER_SUPPORT_CONTACT = {
        "givenName":
        values.Value(
            "Marsha dev team",
            environ_name="SOCIAL_AUTH_SAML_FER_SUPPORT_CONTACT_NAME",
        ),
        "emailAddress":
        values.Value(
            environ_name="SOCIAL_AUTH_SAML_FER_SUPPORT_CONTACT_EMAIL", ),
    }
    # SOCIAL_AUTH_SAML_FER_ENABLED_IDPS is not required since the
    # SAML FER backend is overridden to allow dynamic IdPs.
    # see social_edu_federation.backends.saml_fer.FERSAMLAuth.get_idp(idp_name)

    # Custom parameter to define the Renater Federation Metadata
    SOCIAL_AUTH_SAML_FER_FEDERATION_SAML_METADATA_URL = values.Value(
        "https://metadata.federation.renater.fr/renater/main/main-idps-renater-metadata.xml"
    )

    SOCIAL_AUTH_SAML_FER_PIPELINE = MARSHA_DEFAULT_AUTH_PIPELINE

    # pylint: disable=invalid-name
    @property
    def AWS_SOURCE_BUCKET_NAME(self):
        """Source bucket name.

        If this setting is set in an environment variable we use it. Otherwise
        the value is computed with the AWS_BASE_NAME value.
        """
        return os.environ.get("DJANGO_AWS_SOURCE_BUCKET_NAME",
                              f"{self.AWS_BASE_NAME}-marsha-source")

    # pylint: disable=invalid-name
    @property
    def AWS_DESTINATION_BUCKET_NAME(self):
        """Destination bucket name.

        If this setting is set in an environment variable we use it. Otherwise
        the value is computed with the AWS_BASE_NAME value.
        """
        return os.environ.get(
            "DJANGO_AWS_DESTINATION_BUCKET_NAME",
            f"{self.AWS_BASE_NAME}-marsha-destination",
        )

    # pylint: disable=invalid-name
    @property
    def SIMPLE_JWT(self):
        """Define settings for `djangorestframework_simplejwt`.

        The JWT_SIGNING_KEY must be evaluated late as the jwt library check for string type.
        """
        return {
            "ACCESS_TOKEN_LIFETIME":
            timedelta(days=1),
            "ALGORITHM":
            "HS256",
            "SIGNING_KEY":
            str(self.JWT_SIGNING_KEY),
            "USER_ID_CLAIM":
            "resource_id",
            "AUTH_TOKEN_CLASSES":
            ("rest_framework_simplejwt.tokens.AccessToken", ),
        }

    # pylint: disable=invalid-name
    @property
    def RELEASE(self):
        """
        Return the release information.

        Delegate to the module function to enable easier testing.
        """
        return get_release()

    @classmethod
    def _get_environment(cls):
        """Environment in which the application is launched."""
        return cls.__name__.lower()

    # pylint: disable=invalid-name
    @property
    def ENVIRONMENT(self):
        """Environment in which the application is launched."""
        return self._get_environment()

    @classmethod
    def post_setup(cls):
        """Post setup configuration.

        This is the place where you can configure settings that require other
        settings to be loaded.
        """
        super().post_setup()

        # The DJANGO_SENTRY_DSN environment variable should be set to activate
        # sentry for an environment
        if cls.SENTRY_DSN is not None:
            sentry_sdk.init(
                dsn=cls.SENTRY_DSN,
                environment=cls._get_environment(),
                release=get_release(),
                integrations=[DjangoIntegration()],
            )
            with sentry_sdk.configure_scope() as scope:
                scope.set_extra("application", "backend")
Beispiel #7
0
class Common(Configuration):
    BASE_DIR = os.path.dirname(os.path.dirname(__file__))

    ENVIRONMENT = values.Value(environ_prefix=None, default='DEVELOPMENT')

    SECRET_KEY = values.SecretValue(environ_prefix=None)

    DEBUG = False

    TEMPLATE_DEBUG = False

    ALLOWED_HOSTS = ['aecc-uprb.herokuapp.com', '*']

    # Application definition

    INSTALLED_APPS = (
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'django.contrib.sites',

        # Apps
        'apps.users',
        'apps.events',
        'apps.contact',
        'apps.blog',
        'apps.surveys',

        # Third party
        'allauth',
        'allauth.account',
        'allauth.socialaccount',
        'django_extensions',
        'debug_toolbar',
        'import_export',
        'taggit',
        'tinymce',
        'multiselectfield',
        'storages',
    )

    DISQUS_API_KEY = values.Value(environ_prefix=None)

    DISQUS_WEBSITE_SHORTNAME = values.Value(environ_prefix=None)

    MIDDLEWARE_CLASSES = (
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    )

    ROOT_URLCONF = 'aecc.urls'

    WSGI_APPLICATION = 'aecc.wsgi.application'

    # Database
    # https://docs.djangoproject.com/en/dev/ref/settings/#databases
    DATABASES = values.DatabaseURLValue('sqlite:///{}'.format(
        os.path.join(BASE_DIR, 'storage.sqlite3')))

    # Internationalization
    # https://docs.djangoproject.com/en/1.6/topics/i18n/
    LANGUAGE_CODE = 'en-us'

    TIME_ZONE = 'UTC'

    USE_I18N = True

    USE_L10N = True

    USE_TZ = True

    # Static files (CSS, JavaScript, Images)
    # https://docs.djangoproject.com/en/1.6/howto/static-files/

    STATIC_ROOT = 'staticfiles'
    STATIC_URL = '/static/'

    TEMPLATE_CONTEXT_PROCESSORS = (
        "django.core.context_processors.request",
        "django.contrib.auth.context_processors.auth",
        "allauth.account.context_processors.account",
        "allauth.socialaccount.context_processors.socialaccount",
        "django.contrib.messages.context_processors.messages",
        "aecc.context_processor.months_dropdown_content")

    AUTHENTICATION_BACKENDS = (
        "django.contrib.auth.backends.ModelBackend",
        "allauth.account.auth_backends.AuthenticationBackend",
    )

    SITE_ID = 1

    STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static'), )

    TEMPLATE_DIRS = (os.path.join(BASE_DIR, 'templates'), )

    LOGGING = {
        "version": 1,
        "disable_existing_loggers": False,
        "handlers": {
            "console": {
                "level": "INFO",
                "class": "logging.StreamHandler",
            },
        },
        "loggers": {
            "django": {
                "handlers": ["console"],
            }
        }
    }

    AUTH_USER_MODEL = "users.User"

    # auth and allauth settings
    LOGIN_REDIRECT_URL = '/'
    LOGIN_URL = '/accounts/login/'
    ACCOUNT_EMAIL_VERIFICATION = "none"
    ACCOUNT_EMAIL_SUBJECT_PREFIX = '[AECC]'
    ACCOUNT_LOGOUT_ON_GET = True
    ACCOUNT_EMAIL_REQUIRED = True
    ACCOUNT_CONFIRM_EMAIL_ON_GET = True
    ACCOUNT_LOGOUT_REDIRECT_URL = '/'
    ACCOUNT_USERNAME_BLACKLIST = ['admin']
    ACCOUNT_USER_MODEL_USERNAME_FIELD = "email"
    ACCOUNT_SIGNUP_FORM_CLASS = 'apps.users.forms.SignupForm'
    ACCOUNT_AUTHENTICATION_METHOD = "email"
    ACCOUNT_USERNAME_REQUIRED = False

    TINYMCE_JS_ROOT = os.path.join(STATIC_URL, "js/tiny_mce")
    TINYMCE_JS_URL = os.path.join(STATIC_URL, "js/tiny_mce/tiny_mce_src.js")
    TINYMCE_DEFAULT_CONFIG = {
        'plugins': "table,spellchecker,paste,searchreplace",
        'theme': "advanced",
        'cleanup_on_startup': True,
        'custom_undo_redo_levels': 10,
    }
    TINYMCE_SPELLCHECKER = True

    EMAIL_HOST = values.Value()
    EMAIL_HOST_USER = values.Value()
    EMAIL_HOST_PASSWORD = values.Value()
    EMAIL_PORT = values.IntegerValue()
    EMAIL_USE_TLS = values.BooleanValue(False)

    AECC_UPRB_MEMBER_FEE = values.FloatValue(environ_prefix=None)
    AECC_TSHIRT = values.FloatValue(environ_prefix=None)
    AECC_TSHIRT_CUSTOM = values.FloatValue(environ_prefix=None)