2023-12-18 17:06:03 +00:00
|
|
|
from enum import Enum
|
|
|
|
|
|
|
|
from pydantic import BaseModel
|
|
|
|
from pydantic import ValidationInfo
|
|
|
|
from pydantic import field_validator
|
|
|
|
|
|
|
|
|
|
|
|
class SMTPSettings(BaseModel):
|
|
|
|
"""The SMTP configuration. Belong in the ``CANAILLE.SMTP`` namespace. If
|
|
|
|
unset, mail related features will be disabled, such as mail verification or
|
|
|
|
password recovery emails.
|
|
|
|
|
|
|
|
By default, Canaille will try to send mails from localhost without
|
|
|
|
authentication.
|
|
|
|
"""
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
HOST: str | None = "localhost"
|
2023-12-18 17:06:03 +00:00
|
|
|
"""The SMTP host."""
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
PORT: int | None = 25
|
2023-12-18 17:06:03 +00:00
|
|
|
"""The SMTP port."""
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
TLS: bool | None = False
|
2024-09-11 07:33:42 +00:00
|
|
|
"""Whether to use TLS to connect to the SMTP server."""
|
2023-12-18 17:06:03 +00:00
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
SSL: bool | None = False
|
2024-09-11 07:33:42 +00:00
|
|
|
"""Whether to use SSL to connect to the SMTP server."""
|
2023-12-18 17:06:03 +00:00
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
LOGIN: str | None = None
|
2023-12-18 17:06:03 +00:00
|
|
|
"""The SMTP login."""
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
PASSWORD: str | None = None
|
2023-12-18 17:06:03 +00:00
|
|
|
"""The SMTP password."""
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
FROM_ADDR: str | None = None
|
2023-12-18 17:06:03 +00:00
|
|
|
"""The sender for Canaille mails.
|
|
|
|
|
|
|
|
Some mail provider might require a valid sender address.
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
class Permission(str, Enum):
|
2024-05-14 07:05:46 +00:00
|
|
|
"""The permissions that can be assigned to users.
|
|
|
|
|
|
|
|
The permissions are intended to be used in :attr:`ACLSettings <canaille.core.configuration.ACLSettings.PERMISSIONS>`.
|
|
|
|
"""
|
2023-12-18 17:06:03 +00:00
|
|
|
|
|
|
|
EDIT_SELF = "edit_self"
|
|
|
|
"""Allows users to edit their own profile."""
|
|
|
|
|
|
|
|
USE_OIDC = "use_oidc"
|
|
|
|
"""Allows OpenID Connect authentication."""
|
|
|
|
|
|
|
|
MANAGE_OIDC = "manage_oidc"
|
|
|
|
"""Allows OpenID Connect client managements."""
|
|
|
|
|
|
|
|
MANAGE_USERS = "manage_users"
|
|
|
|
"""Allows other users management."""
|
|
|
|
|
|
|
|
MANAGE_GROUPS = "manage_groups"
|
|
|
|
"""Allows group edition and creation."""
|
|
|
|
|
|
|
|
DELETE_ACCOUNT = "delete_account"
|
|
|
|
"""Allows users to delete their account.
|
|
|
|
|
2024-05-14 07:32:32 +00:00
|
|
|
If used with :attr:`~canaille.core.configuration.Permission.MANAGE_USERS`, users can delete any account.
|
2023-12-18 17:06:03 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
IMPERSONATE_USERS = "impersonate_users"
|
|
|
|
"""Allows users to take the identity of another user."""
|
|
|
|
|
|
|
|
|
|
|
|
class ACLSettings(BaseModel):
|
|
|
|
"""Access Control List settings. Belong in the ``CANAILLE.ACL`` namespace.
|
|
|
|
|
|
|
|
You can define access controls that define what users can do on canaille
|
|
|
|
An access control consists in a :attr:`FILTER` to match users, a list of :attr:`PERMISSIONS`
|
|
|
|
matched users will be able to perform, and fields users will be able
|
|
|
|
to :attr:`READ` and :attr:`WRITE`. Users matching several filters will cumulate permissions.
|
|
|
|
"""
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
PERMISSIONS: list[Permission] = [Permission.EDIT_SELF, Permission.USE_OIDC]
|
2023-12-18 17:06:03 +00:00
|
|
|
"""A list of :class:`Permission` users in the access control will be able
|
|
|
|
to manage. For example::
|
|
|
|
|
|
|
|
PERMISSIONS = [
|
|
|
|
"manage_users",
|
|
|
|
"manage_groups",
|
|
|
|
"manage_oidc",
|
|
|
|
"delete_account",
|
|
|
|
"impersonate_users",
|
|
|
|
]"""
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
READ: list[str] = [
|
2023-12-18 17:06:03 +00:00
|
|
|
"user_name",
|
|
|
|
"groups",
|
|
|
|
"lock_date",
|
|
|
|
]
|
|
|
|
"""A list of :class:`~canaille.core.models.User` attributes that users in
|
|
|
|
the ACL will be able to read."""
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
WRITE: list[str] = [
|
2023-12-18 17:06:03 +00:00
|
|
|
"photo",
|
|
|
|
"given_name",
|
|
|
|
"family_name",
|
|
|
|
"display_name",
|
|
|
|
"password",
|
|
|
|
"phone_numbers",
|
|
|
|
"emails",
|
|
|
|
"profile_url",
|
|
|
|
"formatted_address",
|
|
|
|
"street",
|
|
|
|
"postal_code",
|
|
|
|
"locality",
|
|
|
|
"region",
|
|
|
|
"preferred_language",
|
|
|
|
"employee_number",
|
|
|
|
"department",
|
|
|
|
"title",
|
|
|
|
"organization",
|
|
|
|
]
|
|
|
|
"""A list of :class:`~canaille.core.models.User` attributes that users in
|
|
|
|
the ACL will be able to edit."""
|
|
|
|
|
|
|
|
@field_validator("READ")
|
|
|
|
def validate_read_values(
|
|
|
|
cls,
|
2024-10-28 08:13:00 +00:00
|
|
|
read: list[str],
|
2023-12-18 17:06:03 +00:00
|
|
|
info: ValidationInfo,
|
2024-10-28 08:13:00 +00:00
|
|
|
) -> list[str]:
|
2023-12-18 17:06:03 +00:00
|
|
|
from canaille.core.models import User
|
|
|
|
|
2024-04-06 21:22:38 +00:00
|
|
|
assert all(field in User.attributes for field in read)
|
2023-12-18 17:06:03 +00:00
|
|
|
return read
|
|
|
|
|
|
|
|
@field_validator("WRITE")
|
|
|
|
def validate_write_values(
|
|
|
|
cls,
|
2024-10-28 08:13:00 +00:00
|
|
|
write: list[str],
|
2023-12-18 17:06:03 +00:00
|
|
|
info: ValidationInfo,
|
2024-10-28 08:13:00 +00:00
|
|
|
) -> list[str]:
|
2023-12-18 17:06:03 +00:00
|
|
|
from canaille.core.models import User
|
|
|
|
|
2024-04-06 21:22:38 +00:00
|
|
|
assert all(field in User.attributes for field in write)
|
2023-12-18 17:06:03 +00:00
|
|
|
return write
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
FILTER: dict[str, str] | list[dict[str, str]] | None = None
|
2023-12-18 17:06:03 +00:00
|
|
|
""":attr:`FILTER` can be:
|
|
|
|
|
|
|
|
- :py:data:`None`, in which case all the users will match this access control
|
|
|
|
- a mapping where keys are user attributes name and the values those user
|
|
|
|
attribute values. All the values must be matched for the user to be part
|
|
|
|
of the access control.
|
|
|
|
- a list of those mappings. If a user values match at least one mapping,
|
|
|
|
then the user will be part of the access control
|
|
|
|
|
|
|
|
Here are some examples::
|
|
|
|
|
|
|
|
FILTER = {user_name = 'admin'}
|
|
|
|
FILTER = [
|
|
|
|
{groups = 'admins},
|
|
|
|
{groups = 'moderators'},
|
|
|
|
]
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
class CoreSettings(BaseModel):
|
|
|
|
"""The settings from the ``CANAILLE`` namespace.
|
|
|
|
|
|
|
|
Those are all the configuration parameters that controls the
|
|
|
|
behavior of Canaille.
|
|
|
|
"""
|
|
|
|
|
|
|
|
NAME: str = "Canaille"
|
|
|
|
"""Your organization name.
|
|
|
|
|
|
|
|
Used for display purpose.
|
|
|
|
"""
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
LOGO: str | None = None
|
2023-12-18 17:06:03 +00:00
|
|
|
"""The logo of your organization, this is useful to make your organization
|
|
|
|
recognizable on login screens."""
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
FAVICON: str | None = None
|
2023-12-18 17:06:03 +00:00
|
|
|
"""You favicon.
|
|
|
|
|
|
|
|
If unset and :attr:`LOGO` is set, then the logo will be used.
|
|
|
|
"""
|
|
|
|
|
|
|
|
THEME: str = "default"
|
|
|
|
"""The name of a theme in the 'theme' directory, or a path to a theme.
|
|
|
|
|
|
|
|
Defaults to ``default``. Theming is done with `flask-themer <https://github.com/tktech/flask-themer>`_.
|
|
|
|
"""
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
LANGUAGE: str | None = None
|
2023-12-18 17:06:03 +00:00
|
|
|
"""If a language code is set, it will be used for every user.
|
|
|
|
|
|
|
|
If unset, the language is guessed according to the users browser.
|
|
|
|
"""
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
TIMEZONE: str | None = None
|
2023-12-18 17:06:03 +00:00
|
|
|
"""The timezone in which datetimes will be displayed to the users (e.g.
|
|
|
|
``CEST``).
|
|
|
|
|
|
|
|
If unset, the server timezone will be used.
|
|
|
|
"""
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
SENTRY_DSN: str | None = None
|
2023-12-18 17:06:03 +00:00
|
|
|
"""A `Sentry <https://sentry.io>`_ DSN to collect the exceptions.
|
|
|
|
|
|
|
|
This is useful for tracking errors in test and production environments.
|
|
|
|
"""
|
|
|
|
|
|
|
|
JAVASCRIPT: bool = True
|
|
|
|
"""Enables Javascript to smooth the user experience."""
|
|
|
|
|
|
|
|
HTMX: bool = True
|
2024-09-11 07:33:42 +00:00
|
|
|
"""Accelerates webpages loading with asynchronous requests."""
|
2023-12-18 17:06:03 +00:00
|
|
|
|
|
|
|
EMAIL_CONFIRMATION: bool = True
|
|
|
|
"""If :py:data:`True`, users will need to click on a confirmation link sent
|
|
|
|
by email when they want to add a new email.
|
|
|
|
|
|
|
|
By default, this is true
|
2024-09-11 07:33:42 +00:00
|
|
|
if ``SMTP`` is configured, else this is false. If explicitly set to true
|
2023-12-18 17:06:03 +00:00
|
|
|
and ``SMTP`` is disabled, the email field will be read-only.
|
|
|
|
"""
|
|
|
|
|
|
|
|
ENABLE_REGISTRATION: bool = False
|
|
|
|
"""If :py:data:`True`, then users can freely create an account at this
|
|
|
|
instance.
|
|
|
|
|
|
|
|
If email verification is available, users must confirm their email
|
|
|
|
before the account is created.
|
|
|
|
"""
|
|
|
|
|
|
|
|
HIDE_INVALID_LOGINS: bool = True
|
|
|
|
"""If :py:data:`True`, when users try to sign in with an invalid login, a
|
|
|
|
message is shown indicating that the password is wrong, but does not give a
|
2024-09-11 07:33:42 +00:00
|
|
|
clue whether the login exists or not.
|
2023-12-18 17:06:03 +00:00
|
|
|
|
|
|
|
If :py:data:`False`,
|
|
|
|
when a user tries to sign in with an invalid login, a message is shown
|
|
|
|
indicating that the login does not exist.
|
|
|
|
"""
|
|
|
|
|
|
|
|
ENABLE_PASSWORD_RECOVERY: bool = True
|
|
|
|
"""If :py:data:`False`, then users cannot ask for a password recovery link
|
|
|
|
by email."""
|
|
|
|
|
|
|
|
INVITATION_EXPIRATION: int = 172800
|
|
|
|
"""The validity duration of registration invitations, in seconds.
|
|
|
|
|
|
|
|
Defaults to 2 days.
|
|
|
|
"""
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
LOGGING: str | dict | None = None
|
2023-12-18 17:06:03 +00:00
|
|
|
"""Configures the logging output using the python logging configuration format:
|
|
|
|
|
2024-04-09 08:04:26 +00:00
|
|
|
- if :py:data:`None`, everything is logged in the standard error output
|
|
|
|
the log level is :py:data:`~logging.DEBUG` if the :attr:`~canaille.app.configuration.RootSettings.DEBUG`
|
|
|
|
setting is :py:data:`True`, else this is :py:data:`~logging.INFO`
|
2024-04-02 07:27:47 +00:00
|
|
|
- if this is a :class:`dict`, it is passed to :func:`logging.config.dictConfig`:
|
|
|
|
- if this is a :class:`str`, it is expected to be a file path that will be passed
|
2023-12-18 17:06:03 +00:00
|
|
|
to :func:`logging.config.fileConfig`
|
|
|
|
|
|
|
|
For example::
|
|
|
|
|
2024-03-30 10:40:05 +00:00
|
|
|
[CANAILLE.LOGGING]
|
|
|
|
version = 1
|
|
|
|
formatters.default.format = "[%(asctime)s] %(levelname)s in %(module)s: %(message)s"
|
|
|
|
root = {level = "INFO", handlers = ["canaille"]}
|
|
|
|
|
|
|
|
[CANAILLE.LOGGING.handlers.canaille]
|
|
|
|
class = "logging.handlers.WatchedFileHandler"
|
|
|
|
filename = "/var/log/canaille.log"
|
|
|
|
formatter = "default"
|
2023-12-18 17:06:03 +00:00
|
|
|
"""
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
SMTP: SMTPSettings | None = None
|
2023-12-18 17:06:03 +00:00
|
|
|
"""The settings related to SMTP and mail configuration.
|
|
|
|
|
|
|
|
If unset, mail-related features like password recovery won't be
|
|
|
|
enabled.
|
|
|
|
"""
|
|
|
|
|
2024-10-28 08:13:00 +00:00
|
|
|
ACL: dict[str, ACLSettings] | None = {"DEFAULT": ACLSettings()}
|
2023-12-18 17:06:03 +00:00
|
|
|
"""Mapping of permission groups. See :class:`ACLSettings` for more details.
|
|
|
|
|
2024-09-11 07:33:42 +00:00
|
|
|
The ACL name can be freely chosen. For example::
|
2023-12-18 17:06:03 +00:00
|
|
|
|
|
|
|
[CANAILLE.ACL.DEFAULT]
|
|
|
|
PERMISSIONS = ["edit_self", "use_oidc"]
|
|
|
|
READ = ["user_name", "groups"]
|
|
|
|
WRITE = ["given_name", "family_name"]
|
|
|
|
|
|
|
|
[CANAILLE.ACL.ADMIN]
|
|
|
|
WRITE = ["user_name", "groups"]
|
|
|
|
"""
|
2024-10-28 21:17:47 +00:00
|
|
|
|
|
|
|
MIN_PASSWORD_LENGTH: int = 8
|
|
|
|
"""Minimum length for user password.
|
|
|
|
|
|
|
|
It is possible not to set a minimum, by entering None or 0.
|
|
|
|
"""
|
|
|
|
|
|
|
|
MAX_PASSWORD_LENGTH: int = 1000
|
|
|
|
"""Maximum length for user password.
|
|
|
|
|
|
|
|
There is a technical limit with passlib used by sql database of 4096
|
|
|
|
characters. If the value entered is 0 or None, or greater than 4096,
|
|
|
|
then 4096 will be retained.
|
|
|
|
"""
|
2024-11-12 15:50:00 +00:00
|
|
|
|
2024-11-15 15:28:21 +00:00
|
|
|
ADMIN_EMAIL: str | None = None
|
2024-11-12 15:50:00 +00:00
|
|
|
"""Administration email contact.
|
|
|
|
|
|
|
|
In certain special cases (example : questioning about password
|
|
|
|
corruption), it is necessary to provide an administration contact
|
|
|
|
email.
|
|
|
|
"""
|
2024-11-13 15:12:50 +00:00
|
|
|
|
2024-11-13 15:22:50 +00:00
|
|
|
ENABLE_PASSWORD_COMPROMISSION_CHECK: bool = False
|
2024-11-15 15:28:21 +00:00
|
|
|
"""If :py:data:`True`, Canaille will check for password compromise on HIBP
|
|
|
|
every time a new password is register.
|
|
|
|
|
|
|
|
(https://haveibeenpwned.com/)
|
2024-11-13 15:22:50 +00:00
|
|
|
"""
|