"""The module that defines the ``BackendSiteSettings`` model.
SPDX-License-Identifier: AGPL-3.0-only OR BSD-3-Clause-Clear
"""
from __future__ import annotations
import datetime
import typing as t
from dataclasses import dataclass, field
import cg_request_args as rqa
from cg_maybe import Maybe, Nothing
from cg_maybe.utils import maybe_from_nullable
from ..utils import to_dict
[docs]
@dataclass
class BackendSiteSettings:
"""The JSON representation of the settings used only in the backend."""
#: The maximum size of a single file uploaded by normal users. This limit
#: is really here to prevent users from uploading extremely large files
#: which can't really be downloaded/shown anyway.
max_file_size: Maybe[int] = Nothing
#: The maximum size of uploaded files that are mostly uploaded by "trusted"
#: users. Examples of these kind of files include AutoTest fixtures and
#: plagiarism base code.
max_large_upload_size: Maybe[int] = Nothing
#: The maximum total size of uploaded files that are uploaded by normal
#: users. This is also the maximum total size of submissions. Increasing
#: this size might cause a hosting costs to increase.
max_normal_upload_size: Maybe[int] = Nothing
#: The TA role to be used for TAs in new courses. Existing courses will not
#: be affected.
default_course_ta_role: Maybe[t.Literal["Full TA", "Non-Editing TA"]] = (
Nothing
)
#: The default OS that should be used for new ATv2 configurations.
new_auto_test_default_os: Maybe[
t.Literal["Ubuntu 20.04", "Ubuntu 24.04"]
] = Nothing
#: Whether to send a registration email to new users upon registration.
#: Note: this includes all registrations, also through LTI or SSO.
send_registration_email: Maybe[bool] = Nothing
#: The time a login session is valid. After this amount of time a user will
#: always need to re-authenticate.
jwt_access_token_expires: Maybe[datetime.timedelta] = Nothing
#: The minimum strength passwords by users should have. The higher this
#: value the stronger the password should be. When increasing the strength
#: all users with too weak passwords will be shown a warning on the next
#: login.
min_password_score: Maybe[int] = Nothing
#: The payment provider to use for student payments. This should usually be
#: configured at the tenant level, and will make new prices created in that
#: tenant use this provider. Any existing prices and transactions will not
#: be affected.
payments_provider: Maybe[t.Literal["paddle", "stripe"]] = Nothing
#: Controls whether users are also allowed to pay for courses individually.
#: Tenant-wide access passes can always be used if available. If this
#: setting is disabled, purchasing an access pass is the only way to pay
#: for course access. If there are no tenant passes this setting has no
#: influence.
per_course_payment_allowed: Maybe[bool] = Nothing
#: Should we sync teachers to Hubspot.
hubspot_syncing_enabled: Maybe[bool] = Nothing
#: Whether LTI 1.3 launches using cookies (and sessions) should check for
#: correct nonce and state. When disabling this feature flag, please be
#: mindful. These validations protect us from certain attacks. If unsure,
#: consult with the Security Officer before disabling.
lti_1p3_nonce_and_state_validation_enabled: Maybe[bool] = Nothing
#: Also look at context roles when determining the system role for a new
#: user in LMSes that have an `extra_roles_mapping` defined in their
#: `lms_capabilities`.
lti_1p3_system_role_from_context_role: Maybe[bool] = Nothing
#: Enable logging of LTI launch data. NEVER ENABLE THIS SITE-WIDE, only for
#: a single tenant, and disable this feature after you've gotten the data
#: you need.
lti_launch_data_logging: Maybe[bool] = Nothing
#: This enables whether we change the role of a user in a course when they
#: launch with a different role than their current one.
lti_role_switching: Maybe[bool] = Nothing
#: Do not store names and emails of users, but always retrieve them through
#: NRPS.
name_and_email_from_nrps_only: Maybe[bool] = Nothing
#: Whether a new user should be created on an SSO login of a user whose
#: username is already taken by another user. The new user will get a (X)
#: suffixed to their username to prevent the username collision, where X is
#: a number. When this is disabled the SSO user logging in will be linked
#: to the user with the same username, if one already exists.
sso_username_decollision_enabled: Maybe[bool] = Nothing
raw_data: t.Optional[t.Dict[str, t.Any]] = field(init=False, repr=False)
data_parser: t.ClassVar[t.Any] = rqa.Lazy(
lambda: rqa.FixedMapping(
rqa.OptionalArgument(
"MAX_FILE_SIZE",
rqa.SimpleValue.int,
doc="The maximum size of a single file uploaded by normal users. This limit is really here to prevent users from uploading extremely large files which can't really be downloaded/shown anyway.",
),
rqa.OptionalArgument(
"MAX_LARGE_UPLOAD_SIZE",
rqa.SimpleValue.int,
doc='The maximum size of uploaded files that are mostly uploaded by "trusted" users. Examples of these kind of files include AutoTest fixtures and plagiarism base code.',
),
rqa.OptionalArgument(
"MAX_NORMAL_UPLOAD_SIZE",
rqa.SimpleValue.int,
doc="The maximum total size of uploaded files that are uploaded by normal users. This is also the maximum total size of submissions. Increasing this size might cause a hosting costs to increase.",
),
rqa.OptionalArgument(
"DEFAULT_COURSE_TA_ROLE",
rqa.StringEnum("Full TA", "Non-Editing TA"),
doc="The TA role to be used for TAs in new courses. Existing courses will not be affected.",
),
rqa.OptionalArgument(
"NEW_AUTO_TEST_DEFAULT_OS",
rqa.StringEnum("Ubuntu 20.04", "Ubuntu 24.04"),
doc="The default OS that should be used for new ATv2 configurations.",
),
rqa.OptionalArgument(
"SEND_REGISTRATION_EMAIL",
rqa.SimpleValue.bool,
doc="Whether to send a registration email to new users upon registration. Note: this includes all registrations, also through LTI or SSO.",
),
rqa.OptionalArgument(
"JWT_ACCESS_TOKEN_EXPIRES",
rqa.RichValue.TimeDelta,
doc="The time a login session is valid. After this amount of time a user will always need to re-authenticate.",
),
rqa.OptionalArgument(
"MIN_PASSWORD_SCORE",
rqa.SimpleValue.int,
doc="The minimum strength passwords by users should have. The higher this value the stronger the password should be. When increasing the strength all users with too weak passwords will be shown a warning on the next login.",
),
rqa.OptionalArgument(
"PAYMENTS_PROVIDER",
rqa.StringEnum("paddle", "stripe"),
doc="The payment provider to use for student payments. This should usually be configured at the tenant level, and will make new prices created in that tenant use this provider. Any existing prices and transactions will not be affected.",
),
rqa.OptionalArgument(
"PER_COURSE_PAYMENT_ALLOWED",
rqa.SimpleValue.bool,
doc="Controls whether users are also allowed to pay for courses individually. Tenant-wide access passes can always be used if available. If this setting is disabled, purchasing an access pass is the only way to pay for course access. If there are no tenant passes this setting has no influence.",
),
rqa.OptionalArgument(
"HUBSPOT_SYNCING_ENABLED",
rqa.SimpleValue.bool,
doc="Should we sync teachers to Hubspot.",
),
rqa.OptionalArgument(
"LTI_1P3_NONCE_AND_STATE_VALIDATION_ENABLED",
rqa.SimpleValue.bool,
doc="Whether LTI 1.3 launches using cookies (and sessions) should check for correct nonce and state. When disabling this feature flag, please be mindful. These validations protect us from certain attacks. If unsure, consult with the Security Officer before disabling.",
),
rqa.OptionalArgument(
"LTI_1P3_SYSTEM_ROLE_FROM_CONTEXT_ROLE",
rqa.SimpleValue.bool,
doc="Also look at context roles when determining the system role for a new user in LMSes that have an `extra_roles_mapping` defined in their `lms_capabilities`.",
),
rqa.OptionalArgument(
"LTI_LAUNCH_DATA_LOGGING",
rqa.SimpleValue.bool,
doc="Enable logging of LTI launch data. NEVER ENABLE THIS SITE-WIDE, only for a single tenant, and disable this feature after you've gotten the data you need.",
),
rqa.OptionalArgument(
"LTI_ROLE_SWITCHING",
rqa.SimpleValue.bool,
doc="This enables whether we change the role of a user in a course when they launch with a different role than their current one.",
),
rqa.OptionalArgument(
"NAME_AND_EMAIL_FROM_NRPS_ONLY",
rqa.SimpleValue.bool,
doc="Do not store names and emails of users, but always retrieve them through NRPS.",
),
rqa.OptionalArgument(
"SSO_USERNAME_DECOLLISION_ENABLED",
rqa.SimpleValue.bool,
doc="Whether a new user should be created on an SSO login of a user whose username is already taken by another user. The new user will get a (X) suffixed to their username to prevent the username collision, where X is a number. When this is disabled the SSO user logging in will be linked to the user with the same username, if one already exists.",
),
).use_readable_describe(True)
)
def __post_init__(self) -> None:
getattr(super(), "__post_init__", lambda: None)()
self.max_file_size = maybe_from_nullable(self.max_file_size)
self.max_large_upload_size = maybe_from_nullable(
self.max_large_upload_size
)
self.max_normal_upload_size = maybe_from_nullable(
self.max_normal_upload_size
)
self.default_course_ta_role = maybe_from_nullable(
self.default_course_ta_role
)
self.new_auto_test_default_os = maybe_from_nullable(
self.new_auto_test_default_os
)
self.send_registration_email = maybe_from_nullable(
self.send_registration_email
)
self.jwt_access_token_expires = maybe_from_nullable(
self.jwt_access_token_expires
)
self.min_password_score = maybe_from_nullable(self.min_password_score)
self.payments_provider = maybe_from_nullable(self.payments_provider)
self.per_course_payment_allowed = maybe_from_nullable(
self.per_course_payment_allowed
)
self.hubspot_syncing_enabled = maybe_from_nullable(
self.hubspot_syncing_enabled
)
self.lti_1p3_nonce_and_state_validation_enabled = maybe_from_nullable(
self.lti_1p3_nonce_and_state_validation_enabled
)
self.lti_1p3_system_role_from_context_role = maybe_from_nullable(
self.lti_1p3_system_role_from_context_role
)
self.lti_launch_data_logging = maybe_from_nullable(
self.lti_launch_data_logging
)
self.lti_role_switching = maybe_from_nullable(self.lti_role_switching)
self.name_and_email_from_nrps_only = maybe_from_nullable(
self.name_and_email_from_nrps_only
)
self.sso_username_decollision_enabled = maybe_from_nullable(
self.sso_username_decollision_enabled
)
def to_dict(self) -> t.Dict[str, t.Any]:
res: t.Dict[str, t.Any] = {}
if self.max_file_size.is_just:
res["MAX_FILE_SIZE"] = to_dict(self.max_file_size.value)
if self.max_large_upload_size.is_just:
res["MAX_LARGE_UPLOAD_SIZE"] = to_dict(
self.max_large_upload_size.value
)
if self.max_normal_upload_size.is_just:
res["MAX_NORMAL_UPLOAD_SIZE"] = to_dict(
self.max_normal_upload_size.value
)
if self.default_course_ta_role.is_just:
res["DEFAULT_COURSE_TA_ROLE"] = to_dict(
self.default_course_ta_role.value
)
if self.new_auto_test_default_os.is_just:
res["NEW_AUTO_TEST_DEFAULT_OS"] = to_dict(
self.new_auto_test_default_os.value
)
if self.send_registration_email.is_just:
res["SEND_REGISTRATION_EMAIL"] = to_dict(
self.send_registration_email.value
)
if self.jwt_access_token_expires.is_just:
res["JWT_ACCESS_TOKEN_EXPIRES"] = to_dict(
self.jwt_access_token_expires.value
)
if self.min_password_score.is_just:
res["MIN_PASSWORD_SCORE"] = to_dict(self.min_password_score.value)
if self.payments_provider.is_just:
res["PAYMENTS_PROVIDER"] = to_dict(self.payments_provider.value)
if self.per_course_payment_allowed.is_just:
res["PER_COURSE_PAYMENT_ALLOWED"] = to_dict(
self.per_course_payment_allowed.value
)
if self.hubspot_syncing_enabled.is_just:
res["HUBSPOT_SYNCING_ENABLED"] = to_dict(
self.hubspot_syncing_enabled.value
)
if self.lti_1p3_nonce_and_state_validation_enabled.is_just:
res["LTI_1P3_NONCE_AND_STATE_VALIDATION_ENABLED"] = to_dict(
self.lti_1p3_nonce_and_state_validation_enabled.value
)
if self.lti_1p3_system_role_from_context_role.is_just:
res["LTI_1P3_SYSTEM_ROLE_FROM_CONTEXT_ROLE"] = to_dict(
self.lti_1p3_system_role_from_context_role.value
)
if self.lti_launch_data_logging.is_just:
res["LTI_LAUNCH_DATA_LOGGING"] = to_dict(
self.lti_launch_data_logging.value
)
if self.lti_role_switching.is_just:
res["LTI_ROLE_SWITCHING"] = to_dict(self.lti_role_switching.value)
if self.name_and_email_from_nrps_only.is_just:
res["NAME_AND_EMAIL_FROM_NRPS_ONLY"] = to_dict(
self.name_and_email_from_nrps_only.value
)
if self.sso_username_decollision_enabled.is_just:
res["SSO_USERNAME_DECOLLISION_ENABLED"] = to_dict(
self.sso_username_decollision_enabled.value
)
return res
@classmethod
def from_dict(
cls: t.Type[BackendSiteSettings], d: t.Dict[str, t.Any]
) -> BackendSiteSettings:
parsed = cls.data_parser.try_parse(d)
res = cls(
max_file_size=parsed.MAX_FILE_SIZE,
max_large_upload_size=parsed.MAX_LARGE_UPLOAD_SIZE,
max_normal_upload_size=parsed.MAX_NORMAL_UPLOAD_SIZE,
default_course_ta_role=parsed.DEFAULT_COURSE_TA_ROLE,
new_auto_test_default_os=parsed.NEW_AUTO_TEST_DEFAULT_OS,
send_registration_email=parsed.SEND_REGISTRATION_EMAIL,
jwt_access_token_expires=parsed.JWT_ACCESS_TOKEN_EXPIRES,
min_password_score=parsed.MIN_PASSWORD_SCORE,
payments_provider=parsed.PAYMENTS_PROVIDER,
per_course_payment_allowed=parsed.PER_COURSE_PAYMENT_ALLOWED,
hubspot_syncing_enabled=parsed.HUBSPOT_SYNCING_ENABLED,
lti_1p3_nonce_and_state_validation_enabled=parsed.LTI_1P3_NONCE_AND_STATE_VALIDATION_ENABLED,
lti_1p3_system_role_from_context_role=parsed.LTI_1P3_SYSTEM_ROLE_FROM_CONTEXT_ROLE,
lti_launch_data_logging=parsed.LTI_LAUNCH_DATA_LOGGING,
lti_role_switching=parsed.LTI_ROLE_SWITCHING,
name_and_email_from_nrps_only=parsed.NAME_AND_EMAIL_FROM_NRPS_ONLY,
sso_username_decollision_enabled=parsed.SSO_USERNAME_DECOLLISION_ENABLED,
)
res.raw_data = d
return res