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 andSMTP
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 INVITATION_EXPIRATION: int = 172800¶
The validity duration of registration invitations, in seconds.
Defaults to 2 days.
- 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:
if
None
, everything is logged in the standard error output the log level isDEBUG
if theDEBUG
setting isTrue
, else this isINFO
if this is a
dict
, it is passed tologging.config.dictConfig()
:if this is a
str
, it is expected to be a file path that will be passed tologging.config.fileConfig()
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"
- field LOGO: str | None = None¶
The logo of your organization, this is useful to make your organization recognizable on login screens.
- 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.
- 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.
- 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 ofPERMISSIONS
matched users will be able to perform, and fields users will be able toREAD
andWRITE
. 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 controla 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. IfFalse
, dynamical client registration needs a token defined inDYNAMIC_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.
- 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 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 %}')¶
- 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 LOCALE: str | None = '{% if user.preferred_language %}{{ user.preferred_language }}{% 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 %}"¶
- pydantic settings canaille.backends.sql.configuration.SQLSettings[source]¶
Settings related to the SQL backend.
Belong in the
CANAILLE_SQL
namespace.
- pydantic settings canaille.backends.ldap.configuration.LDAPSettings[source]¶
Settings related to the LDAP backend.
Belong in the
CANAILLE_LDAP
namespace.- 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 USER_BASE: str [Required]¶
The LDAP node under which users will be looked for and saved.
For instance ou=users,dc=mydomain,dc=tld.
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"