canaille-globuzma/canaille/core/configuration.py
2024-03-28 17:44:26 +01:00

310 lines
9.1 KiB
Python

from enum import Enum
from typing import Dict
from typing import List
from typing import Optional
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.
"""
HOST: Optional[str] = "localhost"
"""The SMTP host."""
PORT: Optional[int] = 25
"""The SMTP port."""
TLS: Optional[bool] = False
"""Wether to use TLS to connect to the SMTP server."""
SSL: Optional[bool] = False
"""Wether to use SSL to connect to the SMTP server."""
LOGIN: Optional[str] = None
"""The SMTP login."""
PASSWORD: Optional[str] = None
"""The SMTP password."""
FROM_ADDR: Optional[str] = None
"""The sender for Canaille mails.
Some mail provider might require a valid sender address.
"""
class Permission(str, Enum):
"""The permissions that can be assigned to users."""
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.
If used with :enum:member:`~canaille.core.configuration.Permission.MANAGE_USERS`, users can delete any account.
"""
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.
"""
PERMISSIONS: List[Permission] = [Permission.EDIT_SELF, Permission.USE_OIDC]
"""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",
]"""
READ: List[str] = [
"user_name",
"groups",
"lock_date",
]
"""A list of :class:`~canaille.core.models.User` attributes that users in
the ACL will be able to read."""
WRITE: List[str] = [
"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,
read: List[str],
info: ValidationInfo,
) -> List[str]:
from canaille.core.models import User
assert all(field in User.__annotations__ for field in read)
return read
@field_validator("WRITE")
def validate_write_values(
cls,
write: List[str],
info: ValidationInfo,
) -> List[str]:
from canaille.core.models import User
assert all(field in User.__annotations__ for field in write)
return write
FILTER: Optional[Dict[str, str] | List[Dict[str, str]]] = None
""":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.
"""
LOGO: Optional[str] = None
"""The logo of your organization, this is useful to make your organization
recognizable on login screens."""
FAVICON: Optional[str] = None
"""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>`_.
"""
LANGUAGE: Optional[str] = None
"""If a language code is set, it will be used for every user.
If unset, the language is guessed according to the users browser.
"""
TIMEZONE: Optional[str] = None
"""The timezone in which datetimes will be displayed to the users (e.g.
``CEST``).
If unset, the server timezone will be used.
"""
SENTRY_DSN: Optional[str] = None
"""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
"""Accelerates webpages loading with asynchroneous requests."""
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
if ``SMTP`` is configured, else this is false. If explicitely set to true
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
clue wether the login exists or not.
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.
"""
LOGGING: Optional[str | Dict] = None
"""Configures the logging output using the python logging configuration format:
- if :py:data:`None`, everything is logged in the standard output
the log level is :py:data:`~logging.DEBUG` if :attr:`RootSettings.DEBUG` is :py:data:`True`, else this is :py:data:`~logging.INFO`
- if this is a dictionnary, it is passed to :func:`logging.config.dictConfig`:
- if this is a string, it is expected to be a file path that will be passed
to :func:`logging.config.fileConfig`
For example::
[CANAILLE]
LOGGING = {
version = 1,
formatters = {
default = {
format = "[%(asctime)s] %(levelname)s in %(module)s: %(message)s",
},
},
handlers = {
canaille = {
class = "logging.handlers.WatchedFileHandler",
filename = /foo/bar/canaille.log,
formatter = "default",
}
},
root = {
level = "INFO",
handlers = ["canaille"],
},
}
"""
SMTP: Optional[SMTPSettings] = None
"""The settings related to SMTP and mail configuration.
If unset, mail-related features like password recovery won't be
enabled.
"""
ACL: Optional[Dict[str, ACLSettings]] = {"DEFAULT": ACLSettings()}
"""Mapping of permission groups. See :class:`ACLSettings` for more details.
The ACL name can be freely choosed. For example::
[CANAILLE.ACL.DEFAULT]
PERMISSIONS = ["edit_self", "use_oidc"]
READ = ["user_name", "groups"]
WRITE = ["given_name", "family_name"]
[CANAILLE.ACL.ADMIN]
WRITE = ["user_name", "groups"]
"""