Configuration

Canaille can be configured either by a environment variables, or by a toml configuration file which path is passed in the CONFIG environment variable.

Toml file

SECRET_KEY = "very-secret"

[CANAILLE]
NAME = "My organization"

[CANAILLE_SQL]
DATABASE_URI = "postgresql://user:password@localhost/database"
...

You can have a look at the Example file for inspiration.

Environment variables

In addition, parameters that have not been set in the configuration file can be read from environment variables. The way environment variables are parsed can be read from the pydantic-settings documentation.

Settings will also be read from a local .env file if present.

Parameters

pydantic settings canaille.app.configuration.RootSettings[source]

The top-level namespace contains holds the configuration settings unrelated to Canaille. The configuration paramateres from the following libraries can be used:

field DEBUG: bool = False

The Flask DEBUG configuration setting.

This enables debug options. This is useful for development but should be absolutely avoided in production environments.

field PREFERRED_URL_SCHEME: str = 'https'

The Flask PREFERRED_URL_SCHEME configuration setting.

This sets the url scheme by which canaille will be served.

field SECRET_KEY: str [Required]

The Flask SECRET_KEY configuration setting.

You MUST change this.

field SERVER_NAME: str | None = None

The Flask SERVER_NAME configuration setting.

This sets domain name on which canaille will be served.

pydantic settings canaille.core.configuration.CoreSettings[source]

The settings from the CANAILLE namespace.

Those are all the configuration parameters that controls the behavior of Canaille.

field ACL: Dict[str, ACLSettings] | None = {'DEFAULT': ACLSettings(PERMISSIONS=[<Permission.EDIT_SELF: 'edit_self'>, <Permission.USE_OIDC: 'use_oidc'>], READ=['user_name', 'groups', 'lock_date'], WRITE=['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'], FILTER=None)}

Mapping of permission groups. See 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"]
field EMAIL_CONFIRMATION: bool = True

If 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.

field ENABLE_PASSWORD_RECOVERY: bool = True

If False, then users cannot ask for a password recovery link by email.

field ENABLE_REGISTRATION: bool = False

If 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.

field FAVICON: str | None = None

You favicon.

If unset and LOGO is set, then the logo will be used.

field HIDE_INVALID_LOGINS: bool = True

If 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 False, when a user tries to sign in with an invalid login, a message is shown indicating that the login does not exist.

field HTMX: bool = True

Accelerates webpages loading with asynchroneous requests.

field INVITATION_EXPIRATION: int = 172800

The validity duration of registration invitations, in seconds.

Defaults to 2 days.

field JAVASCRIPT: bool = True

Enables Javascript to smooth the user experience.

field LANGUAGE: str | None = 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.

field LOGGING: str | Dict | None = None

Configures the logging output using the python logging configuration format:

For example:

[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"

The logo of your organization, this is useful to make your organization recognizable on login screens.

field NAME: str = 'Canaille'

Your organization name.

Used for display purpose.

field SENTRY_DSN: str | None = None

A Sentry DSN to collect the exceptions.

This is useful for tracking errors in test and production environments.

field SMTP: SMTPSettings | None = None

The settings related to SMTP and mail configuration.

If unset, mail-related features like password recovery won’t be enabled.

field 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.

field TIMEZONE: str | None = None

The timezone in which datetimes will be displayed to the users (e.g. CEST).

If unset, the server timezone will be used.

pydantic settings canaille.core.configuration.SMTPSettings[source]

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.

field FROM_ADDR: str | None = None

The sender for Canaille mails.

Some mail provider might require a valid sender address.

field HOST: str | None = 'localhost'

The SMTP host.

field LOGIN: str | None = None

The SMTP login.

field PASSWORD: str | None = None

The SMTP password.

field PORT: int | None = 25

The SMTP port.

field SSL: bool | None = False

Wether to use SSL to connect to the SMTP server.

field TLS: bool | None = False

Wether to use TLS to connect to the SMTP server.

pydantic settings canaille.core.configuration.ACLSettings[source]

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 FILTER to match users, a list of PERMISSIONS matched users will be able to perform, and fields users will be able to READ and WRITE. Users matching several filters will cumulate permissions.

field FILTER: Dict[str, str] | List[Dict[str, str]] | None = None

FILTER can be:

  • 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'},
]
field PERMISSIONS: List[Permission] = [<Permission.EDIT_SELF: 'edit_self'>, <Permission.USE_OIDC: 'use_oidc'>]

A list of Permission users in the access control will be able to manage. For example:

PERMISSIONS = [
    "manage_users",
    "manage_groups",
    "manage_oidc",
    "delete_account",
    "impersonate_users",
]
field READ: List[str] = ['user_name', 'groups', 'lock_date']

A list of User attributes that users in the ACL will be able to read.

field 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 User attributes that users in the ACL will be able to edit.

final class canaille.core.configuration.Permission(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)[source]

The permissions that can be assigned to users.

EDIT_SELF= edit_self

The permissions that can be assigned to users.

USE_OIDC= use_oidc

The permissions that can be assigned to users.

MANAGE_OIDC= manage_oidc

The permissions that can be assigned to users.

MANAGE_USERS= manage_users

The permissions that can be assigned to users.

MANAGE_GROUPS= manage_groups

The permissions that can be assigned to users.

DELETE_ACCOUNT= delete_account

The permissions that can be assigned to users.

IMPERSONATE_USERS= impersonate_users

The permissions that can be assigned to users.

pydantic settings canaille.oidc.configuration.OIDCSettings[source]

OpenID Connect settings.

Belong in the CANAILLE_OIDC namespace.

field DYNAMIC_CLIENT_REGISTRATION_OPEN: bool = False

Wether a token is needed for the RFC7591 dynamical client registration.

If True, no token is needed to register a client. If False, dynamical client registration needs a token defined in DYNAMIC_CLIENT_REGISTRATION_TOKENS.

field DYNAMIC_CLIENT_REGISTRATION_TOKENS: List[str] | None = None

A list of tokens that can be used for dynamic client registration.

field JWT: JWTSettings = JWTSettings(PRIVATE_KEY=None, PUBLIC_KEY=None, ISS=None, KTY='RSA', ALG='RS256', EXP=3600, MAPPING=JWTMappingSettings(SUB='{{ user.user_name }}', NAME='{% if user.formatted_name %}{{ user.formatted_name }}{% endif %}', PHONE_NUMBER='{% if user.phone_numbers %}{{ user.phone_numbers[0] }}{% endif %}', EMAIL='{% if user.preferred_email %}{{ user.preferred_email }}{% endif %}', GIVEN_NAME='{% if user.given_name %}{{ user.given_name }}{% endif %}', FAMILY_NAME='{% if user.family_name %}{{ user.family_name }}{% endif %}', PREFERRED_USERNAME='{% if user.display_name %}{{ user.display_name }}{% endif %}', LOCALE='{% if user.preferred_language %}{{ user.preferred_language }}{% endif %}', ADDRESS='{% if user.formatted_address %}{{ user.formatted_address }}{% endif %}', PICTURE="{% if user.photo %}{{ url_for('core.account.photo', user=user, field='photo', _external=True) }}{% endif %}", WEBSITE='{% if user.profile_url %}{{ user.profile_url }}{% endif %}'))

JSON Web Token settings.

field REQUIRE_NONCE: bool = True

Force the nonce exchange during the authentication flows.

This adds security but may not be supported by all clients.

pydantic settings canaille.oidc.configuration.JWTSettings[source]

JSON Web Token settings. Belong in the CANAILLE_OIDC.JWT namespace.

You can generate a RSA keypair with:

openssl genrsa -out private.pem 4096
openssl rsa -in private.pem -pubout -outform PEM -out public.pem
field ALG: str = 'RS256'

The key algorithm.

field EXP: int = 3600

The time the JWT will be valid, in seconds.

field ISS: str | None = None

The URI of the identity provider.

field KTY: str = 'RSA'

The key type.

field MAPPING: JWTMappingSettings | None = JWTMappingSettings(SUB='{{ user.user_name }}', NAME='{% if user.formatted_name %}{{ user.formatted_name }}{% endif %}', PHONE_NUMBER='{% if user.phone_numbers %}{{ user.phone_numbers[0] }}{% endif %}', EMAIL='{% if user.preferred_email %}{{ user.preferred_email }}{% endif %}', GIVEN_NAME='{% if user.given_name %}{{ user.given_name }}{% endif %}', FAMILY_NAME='{% if user.family_name %}{{ user.family_name }}{% endif %}', PREFERRED_USERNAME='{% if user.display_name %}{{ user.display_name }}{% endif %}', LOCALE='{% if user.preferred_language %}{{ user.preferred_language }}{% endif %}', ADDRESS='{% if user.formatted_address %}{{ user.formatted_address }}{% endif %}', PICTURE="{% if user.photo %}{{ url_for('core.account.photo', user=user, field='photo', _external=True) }}{% endif %}", WEBSITE='{% if user.profile_url %}{{ user.profile_url }}{% endif %}')
field PRIVATE_KEY: str | None = None

The private key.

If None and debug mode is enabled, then an in-memory key will be used.

field PUBLIC_KEY: str | None = None

The public key.

If None and debug mode is enabled, then an in-memory key will be used.

pydantic settings canaille.oidc.configuration.JWTMappingSettings[source]

Mapping between the user model and the JWT fields.

Fiels are evaluated with jinja. A user var is available.

field ADDRESS: str | None = '{% if user.formatted_address %}{{ user.formatted_address }}{% endif %}'
field EMAIL: str | None = '{% if user.preferred_email %}{{ user.preferred_email }}{% endif %}'
field FAMILY_NAME: str | None = '{% if user.family_name %}{{ user.family_name }}{% endif %}'
field GIVEN_NAME: str | None = '{% if user.given_name %}{{ user.given_name }}{% endif %}'
field LOCALE: str | None = '{% if user.preferred_language %}{{ user.preferred_language }}{% endif %}'
field NAME: str | None = '{% if user.formatted_name %}{{ user.formatted_name }}{% endif %}'
field PHONE_NUMBER: str | None = '{% if user.phone_numbers %}{{ user.phone_numbers[0] }}{% endif %}'
field PICTURE: str | None = "{% if user.photo %}{{ url_for('core.account.photo', user=user, field='photo', _external=True) }}{% endif %}"
field PREFERRED_USERNAME: str | None = '{% if user.display_name %}{{ user.display_name }}{% endif %}'
field SUB: str | None = '{{ user.user_name }}'
field WEBSITE: str | None = '{% if user.profile_url %}{{ user.profile_url }}{% endif %}'
pydantic settings canaille.backends.sql.configuration.SQLSettings[source]

Settings related to the SQL backend.

Belong in the CANAILLE_SQL namespace.

field DATABASE_URI: str [Required]

The SQL server URI. For example:

DATABASE_URI = "postgresql://user:password@localhost/database_name"
pydantic settings canaille.backends.ldap.configuration.LDAPSettings[source]

Settings related to the LDAP backend.

Belong in the CANAILLE_LDAP namespace.

field BIND_DN: str = 'cn=admin,dc=mydomain,dc=tld'

The LDAP bind DN.

field BIND_PW: str = 'admin'

The LDAP bind password.

field GROUP_BASE: str [Required]

The LDAP node under which groups will be looked for and saved.

For instance “ou=groups,dc=mydomain,dc=tld”.

field GROUP_CLASS: str = 'groupOfNames'

The object class to use for creating new groups.

field GROUP_NAME_ATTRIBUTE: str = 'cn'

The attribute to use to identify a group.

field GROUP_RDN: str = 'cn'

The attribute to identify an object in the Group DN.

field ROOT_DN: str = 'dc=mydomain,dc=tld'

The LDAP root DN.

field TIMEOUT: float = 0.0

The LDAP connection timeout.

field URI: str = 'ldap://localhost'

The LDAP server URI.

field USER_BASE: str [Required]

The LDAP node under which users will be looked for and saved.

For instance ou=users,dc=mydomain,dc=tld.

field USER_CLASS: str = 'inetOrgPerson'

The object class to use for creating new users.

field USER_FILTER: str = '(|(uid={{ login }})(mail={{ login }}))'

Filter to match users on sign in.

For instance (|(uid={{ login }})(mail={{ login }})). Jinja syntax is supported and a login variable is available, containing the value passed in the login field.

field USER_RDN: str = 'uid'

The attribute to identify an object in the User DN.

Example file

Here is a configuration file example:

# The Flask secret key for cookies. You MUST change this.
SECRET_KEY = "change me before you go in production"

# The interface on which canaille will be served
# SERVER_NAME = "auth.mydomain.tld"
# PREFERRED_URL_SCHEME = "https"

[CANAILLE]

# Your organization name.
# NAME = "Canaille"

# You can display a logo to be recognized on login screens
# LOGO = "/static/img/canaille-head.webp"

# Your favicon. If unset the LOGO will be used.
# FAVICON = "/static/img/canaille-c.webp"

# The name of a theme in the 'theme' directory, or a path path
# to a theme. Defaults to 'default'. Theming is done with
# https://github.com/tktech/flask-themer
# THEME = "default"

# If unset, language is detected
# LANGUAGE = "en"

# The timezone in which datetimes will be displayed to the users.
# If unset, the server timezone will be used.
# TIMEZONE = UTC

# If you have a sentry instance, you can set its dsn here:
# SENTRY_DSN = "https://examplePublicKey@o0.ingest.sentry.io/0"

# Enables javascript to smooth the user experience
# JAVASCRIPT = true

# Accelerates webpages with async requests
# HTMX = true

# If EMAIL_CONFIRMATION is set to 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.
# EMAIL_CONFIRMATION =

# If ENABLE_REGISTRATION is 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.
# ENABLE_REGISTRATION = false

# If HIDE_INVALID_LOGINS is set to true (the default), when a user
# tries 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 HIDE_INVALID_LOGINS is set to false, when a user tries to sign in with
# an invalid login, a message is shown indicating that the login does not
# exist.
# HIDE_INVALID_LOGINS = true

# If ENABLE_PASSWORD_RECOVERY is false, then users cannot ask for a password
# recovery link by email. This option is true by default.
# ENABLE_PASSWORD_RECOVERY = true

# The validity duration of registration invitations, in seconds.
# Defaults to 2 days
# INVITATION_EXPIRATION = 172800

# LOGGING configures the logging output:
# - if unset, everything is logged in the standard output
#   the log level is debug if DEBUG is True, else this is INFO
# - if this is a dictionnary, it is passed to the python dictConfig method:
#   https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig
# - if this is a string, it is passed to the python fileConfig method
#   https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig

# [CANAILLE_SQL]
# The SQL database connection string
# Details on https://docs.sqlalchemy.org/en/20/core/engines.html
# DATABASE_URI = "postgresql://user:password@localhost/database"

# [CANAILLE_LDAP]
# URI = "ldap://ldap"
# ROOT_DN = "dc=mydomain,dc=tld"
# BIND_DN = "cn=admin,dc=mydomain,dc=tld"
# BIND_PW = "admin"
# TIMEOUT =

# Where to search for users?
# USER_BASE = "ou=users,dc=mydomain,dc=tld"

# The object class to use for creating new users
# USER_CLASS = "inetOrgPerson"

# The attribute to identify an object in the User dn.
# USER_RDN = "uid"

# Filter to match users on sign in. Jinja syntax is supported
# and a `login` variable is available containing the value
# passed in the login field.
# USER_FILTER = "(|(uid={{ login }})(mail={{ login }}))"

# Where to search for groups?
# GROUP_BASE = "ou=groups,dc=mydomain,dc=tld"

# The object class to use for creating new groups
# GROUP_CLASS = "groupOfNames"

# The attribute to identify an object in the User dn.
# GROUP_RDN = "cn"

# The attribute to use to identify a group
# GROUP_NAME_ATTRIBUTE = "cn"

[CANAILLE.ACL]
# You can define access controls that define what users can do on canaille
# An access control consists in a FILTER to match users, a list of PERMISSIONS
# matched users will be able to perform, and fields users will be able
# to READ and WRITE. Users matching several filters will cumulate permissions.
#
# 'FILTER' parameter can be:
# - absent, 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'}
#
# The 'PERMISSIONS' parameter that is an list of items the users in the access
# control will be able to manage. 'PERMISSIONS' is optionnal. Values can be:
# - "edit_self" to allow users to edit their own profile
# - "use_oidc" to allow OpenID Connect authentication
# - "manage_oidc" to allow OpenID Connect client managements
# - "manage_users" to allow other users management
# - "manage_groups" to allow group edition and creation
# - "delete_account" allows a user to delete his own account. If used with
#                    manage_users, the user can delete any account
# - "impersonate_users" to allow a user to take the identity of another user
#
# The 'READ' and 'WRITE' attributes are the LDAP attributes of the user
# object that users will be able to read and/or write.
[CANAILLE.ACL.DEFAULT]
PERMISSIONS = ["edit_self", "use_oidc"]
READ = [
    "user_name",
    "groups",
    "lock_date",
]
WRITE = [
    "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",
]

[CANAILLE.ACL.ADMIN]
FILTER = {groups = "admins"}
PERMISSIONS = [
    "manage_users",
    "manage_groups",
    "manage_oidc",
    "delete_account",
    "impersonate_users",
]
WRITE = [
    "groups",
    "lock_date",
]

[CANAILLE_OIDC]
# Wether a token is needed for the RFC7591 dynamical client registration.
# If true, no token is needed to register a client.
# If false, dynamical client registration needs a token defined
# in DYNAMIC_CLIENT_REGISTRATION_TOKENS
# DYNAMIC_CLIENT_REGISTRATION_OPEN = false

# A list of tokens that can be used for dynamic client registration
# DYNAMIC_CLIENT_REGISTRATION_TOKENS = [
#     "xxxxxxx-yyyyyyy-zzzzzz",
# ]

# REQUIRE_NONCE force the nonce exchange during the authentication flows.
# This adds security but may not be supported by all clients.
# REQUIRE_NONCE = true

[CANAILLE_OIDC.JWT]
# PRIVATE_KEY and PUBLIC_KEY are the private and
# the public key. You can generate a RSA keypair with:
# openssl genrsa -out private.pem 4096
# openssl rsa -in private.pem -pubout -outform PEM -out public.pem
# If the variables are unset, and debug mode is enabled,
# a in-memory keypair will be used.
# PRIVATE_KEY = "..."
# PUBLIC_KEY = "..."
# The URI of the identity provider
# ISS = "https://auth.mydomain.tld"
# The key type parameter
# KTY = "RSA"
# The key algorithm
# ALG = "RS256"
# The time the JWT will be valid, in seconds
# EXP = 3600

[CANAILLE_OIDC.JWT.MAPPING]
# Mapping between JWT fields and LDAP attributes from your
# User objectClass.
# {attribute} will be replaced by the user ldap attribute value.
# Default values fits inetOrgPerson.
# SUB = "{{ user.user_name }}"
# NAME = "{{ user.formatted_name }}"
# PHONE_NUMBER = "{{ user.phone_numbers[0] }}"
# EMAIL = "{{ user.preferred_email }}"
# GIVEN_NAME = "{{ user.given_name }}"
# FAMILY_NAME = "{{ user.family_name }}"
# PREFERRED_USERNAME = "{{ user.display_name }}"
# LOCALE = "{{ user.preferred_language }}"
# ADDRESS = "{{ user.formatted_address }}"
# PICTURE = "{% if user.photo %}{{ url_for('core.account.photo', user=user, field='photo', _external=True) }}{% endif %}"
# WEBSITE = "{{ user.profile_url }}"

# The SMTP server options. If not set, mail related features such as
# user invitations, and password reset emails, will be disabled.
[CANAILLE.SMTP]
# HOST = "localhost"
# PORT = 25
# TLS = false
# SSL = false
# LOGIN = ""
# PASSWORD = ""
# FROM_ADDR = "admin@mydomain.tld"