From 18a711ef7d2c4557e87bbce14a333537708afff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Mon, 13 May 2024 16:44:02 +0200 Subject: [PATCH] doc: features and use cases documentation --- CONTRIBUTING.rst | 4 +- TODO.md | 5 + canaille/app/configuration.py | 20 +- canaille/translations/README.rst | 2 +- doc/_static/group-edition.webp | Bin 0 -> 19192 bytes doc/_static/password-recovery.webp | Bin 0 -> 8582 bytes doc/_static/user-invite.webp | Bin 0 -> 14574 bytes doc/changelog.rst | 54 ---- doc/conf.py | 50 ++- doc/contributing.rst | 1 - doc/development/changelog.rst | 9 + doc/development/contributing.rst | 1 + doc/development/index.rst | 8 + doc/{ => development}/specifications.rst | 0 doc/features.rst | 314 +++++++++++++++++++ doc/index.rst | 94 +++--- doc/{ => references}/commands.rst | 16 + doc/{ => references}/configuration.rst | 29 +- doc/references/index.rst | 8 + doc/{reference.rst => references/models.rst} | 4 +- doc/{ => tutorial}/databases.rst | 21 +- doc/{ => tutorial}/deployment.rst | 27 +- doc/tutorial/index.rst | 10 + doc/{ => tutorial}/install.rst | 5 +- doc/{ => tutorial}/troubleshooting.rst | 0 poetry.lock | 40 ++- pyproject.toml | 2 + 27 files changed, 582 insertions(+), 142 deletions(-) create mode 100644 TODO.md create mode 100644 doc/_static/group-edition.webp create mode 100644 doc/_static/password-recovery.webp create mode 100644 doc/_static/user-invite.webp delete mode 100644 doc/changelog.rst delete mode 100644 doc/contributing.rst create mode 100644 doc/development/changelog.rst create mode 100644 doc/development/contributing.rst create mode 100644 doc/development/index.rst rename doc/{ => development}/specifications.rst (100%) create mode 100644 doc/features.rst rename doc/{ => references}/commands.rst (85%) rename doc/{ => references}/configuration.rst (60%) create mode 100644 doc/references/index.rst rename doc/{reference.rst => references/models.rst} (94%) rename doc/{ => tutorial}/databases.rst (90%) rename doc/{ => tutorial}/deployment.rst (90%) create mode 100644 doc/tutorial/index.rst rename doc/{ => tutorial}/install.rst (98%) rename doc/{ => tutorial}/troubleshooting.rst (100%) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 6da50793..a53ce1c9 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -148,7 +148,7 @@ The dynamical parts of the interface use `htmx `_. Translations ------------ -.. include:: ../canaille/translations/README.rst +.. include:: ../../canaille/translations/README.rst Documentation ------------- @@ -173,7 +173,7 @@ Publish a new release 1. Check that dependencies are up to date with ``poetry show --outdated --with dev,doc,demo`` and update dependencies accordingly in separated commits. 2. Check that tests are still green for every supported python version, and that coverage is still at 100%, by running ``tox`` 3. Check that the demo environments are still working -4. Check that the :ref:`changelog:Release notes` section is correctly filled up +4. Check that the :ref:`development/changelog:Release notes` section is correctly filled up 5. Increase the version number in ``pyproject.toml`` 6. Commit with ``git commit`` 7. Publish with ``poetry publish --build`` diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..484f6ade --- /dev/null +++ b/TODO.md @@ -0,0 +1,5 @@ +- screenshots partout +- s'assurer qu'on reste pas trop techniques dans features.html +- s'assurer que les concepts techniques pas mentionnés dans features.html sont bien mentionnés quelque part +- écrire les usecases +- s'assurer qu'il ne reste pas de TODO ou de TBD diff --git a/canaille/app/configuration.py b/canaille/app/configuration.py index bddee471..75e2dc4e 100644 --- a/canaille/app/configuration.py +++ b/canaille/app/configuration.py @@ -25,6 +25,18 @@ class RootSettings(BaseSettings): - :doc:`Flask-WTF ` - :doc:`Flask-Babel ` - :doc:`Authlib ` + + .. code-block:: toml + :caption: config.toml + + SECRET_KEY = "very-secret" + SERVER_NAME = "auth.mydomain.example" + PREFERRED_URL_SCHEME = false + DEBUG = false + + [CANAILLE] + NAME = "My organization" + ... """ model_config = SettingsConfigDict( @@ -55,8 +67,12 @@ class RootSettings(BaseSettings): DEBUG: bool = False """The Flask :external:py:data:`DEBUG` configuration setting. - This enables debug options. This is useful for development but - should be absolutely avoided in production environments. + This enables debug options. + + .. danger:: + + This is useful for development but should be absolutely + avoided in production environments. """ diff --git a/canaille/translations/README.rst b/canaille/translations/README.rst index a158ef38..9c8101e7 100644 --- a/canaille/translations/README.rst +++ b/canaille/translations/README.rst @@ -1,4 +1,4 @@ -Translations are done with [Weblate](https://hosted.weblate.org/projects/canaille/canaille/). +Translations are done with `Weblate `_. The following commands are there as documentation, only the message extraction is needed for contributors. All the other steps are automatically done with Weblate. diff --git a/doc/_static/group-edition.webp b/doc/_static/group-edition.webp new file mode 100644 index 0000000000000000000000000000000000000000..49837859957f5ab9247b83dd241b14775a4861b5 GIT binary patch literal 19192 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 M_z}^6qW^}Y1W&20LI3~& literal 0 HcmV?d00001 diff --git a/doc/_static/user-invite.webp b/doc/_static/user-invite.webp new file mode 100644 index 0000000000000000000000000000000000000000..9487ea589a48d18bea8696a85c77171a155df7d8 GIT binary patch literal 14574 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 Y9=%t=d`1v9!fEZL>yiJz8e(q&07d{ws{jB1 literal 0 HcmV?d00001 diff --git a/doc/changelog.rst b/doc/changelog.rst deleted file mode 100644 index 2569d171..00000000 --- a/doc/changelog.rst +++ /dev/null @@ -1,54 +0,0 @@ -Roadmap and changelog -##################### - -Roadmap -******* - -Bêta version -============ - -To go out of the current Alpha version we want to achieve the following tasks: - -- :issue:`Configuration validation using pydantic <138>` - -Stable version -============== - -Before we push Canaille in stable version we want to achieve the following tasks: - -Security --------- - -- :issue:`Password hashing configuration <175>` -- :issue:`Authentication logging policy <177>` -- :issue:`Intruder lockout <173>` -- :issue:`Password expiry policy <176>` -- :issue:`Password compromission check <179>` -- :issue:`Multi-factor authentication: Email <47>` -- :issue:`Multi-factor authentication: SMS <47>` -- :issue:`Multi-factor authentication: OTP <47>` - -Packaging ---------- - -- :issue:`Nix package <190>` -- :issue:`Docker / OCI package <59>` - -And beyond -========== - -- :issue:`OpenID Connect certification <182>` -- :issue:`SCIM support <116>` - -Release notes -************* - -All notable changes to this project will be documented in there. - -The format is based on `Keep a Changelog `_, -and this project adheres to `Semantic Versioning `_. - -Alpha versions -============== - -.. include:: ../CHANGES.rst diff --git a/doc/conf.py b/doc/conf.py index e6c2b73c..05f20da0 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -34,9 +34,11 @@ extensions = [ "sphinx.ext.todo", "sphinx.ext.viewcode", "sphinx_click", + "sphinx_design", "sphinx_issues", "sphinx_sitemap", "sphinxcontrib.autodoc_pydantic", + "sphinxcontrib.images", ] templates_path = ["_templates"] @@ -54,7 +56,8 @@ version = metadata.version("canaille") language = "en" exclude_patterns = [] pygments_style = "sphinx" -todo_include_todos = False +todo_include_todos = True +toctree_collapse = False intersphinx_mapping = { "python": ("https://docs.python.org/3", None), @@ -63,6 +66,7 @@ intersphinx_mapping = { "flask-babel": ("https://python-babel.github.io/flask-babel", None), "flask-wtf": ("https://flask-wtf.readthedocs.io", None), "pydantic": ("https://docs.pydantic.dev/latest", None), + "pytest-iam": ("https://pytest-iam.readthedocs.io/en/latest/", None), } issues_uri = "https://gitlab.com/yaal/canaille/-/issues/{issue}" @@ -75,20 +79,45 @@ html_theme = "shibuya" html_static_path = ["_static"] html_baseurl = "https://canaille.readthedocs.io/" html_theme_options = { + "globaltoc_expand_depth": 3, "accent_color": "yellow", "light_logo": "_static/canaille-label-black.webp", "dark_logo": "_static/canaille-label-white.webp", "gitlab_url": "https://gitlab.com/yaal/canaille", "mastodon_url": "https://toot.aquilenet.fr/@yaal", + "discussion_url": "https://matrix.to/#/#canaille-discuss:yaal.coop", "nav_links": [ { - "title": "Homepage", - "url": "https://canaille.yaal.coop", - "summary": "The homepage for the Canaille project", + "title": "Demo", + "children": [ + { + "title": "Canaille demo server", + "url": "https://demo.canaille.yaal.coop", + }, + { + "title": "OIDC Client 1", + "url": "https://demo.client1.yaal.coop", + }, + { + "title": "OIDC Client 2", + "url": "https://demo.client2.yaal.coop", + }, + ], + }, + {"title": "PyPI", "url": "https://pypi.org/project/Canaille"}, + { + "title": "Weblate", + "url": "https://hosted.weblate.org/projects/canaille/canaille", }, - {"title": "PyPI", "url": "https://pypi.org/project/Canaille/"}, ], } +html_context = { + "source_type": "gitlab", + "source_user": "yaal", + "source_repo": "canaille", + "source_version": "main", + "source_docs_path": "/doc/", +} # -- Options for HTMLHelp output ------------------------------------------ @@ -125,7 +154,7 @@ texinfo_documents = [ autosectionlabel_prefix_document = True autosectionlabel_maxdepth = 2 -# -- Options for autodo_pydantic_settings ------------------------------------------- +# -- Options for autodoc_pydantic_settings ------------------------------------------- autodoc_pydantic_settings_show_json = False autodoc_pydantic_settings_show_config_summary = False @@ -133,4 +162,13 @@ autodoc_pydantic_settings_show_config_summary = False autodoc_pydantic_settings_show_validator_summary = False autodoc_pydantic_settings_show_validator_members = False autodoc_pydantic_settings_show_field_summary = False +autodoc_pydantic_settings_signature_prefix = "" +autodoc_pydantic_field_signature_prefix = "" autodoc_pydantic_field_list_validators = False + +# -- Options for images + +images_config = { + "override_image_directive": True, + "download": False, +} diff --git a/doc/contributing.rst b/doc/contributing.rst deleted file mode 100644 index e582053e..00000000 --- a/doc/contributing.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../CONTRIBUTING.rst diff --git a/doc/development/changelog.rst b/doc/development/changelog.rst new file mode 100644 index 00000000..e1d739b1 --- /dev/null +++ b/doc/development/changelog.rst @@ -0,0 +1,9 @@ +Release notes +############# + +All notable changes to this project will be documented in there. + +The format is based on `Keep a Changelog `_, +and this project adheres to `Semantic Versioning `_. + +.. include:: ../../CHANGES.rst diff --git a/doc/development/contributing.rst b/doc/development/contributing.rst new file mode 100644 index 00000000..ac7b6bcf --- /dev/null +++ b/doc/development/contributing.rst @@ -0,0 +1 @@ +.. include:: ../../CONTRIBUTING.rst diff --git a/doc/development/index.rst b/doc/development/index.rst new file mode 100644 index 00000000..8a881b3d --- /dev/null +++ b/doc/development/index.rst @@ -0,0 +1,8 @@ +Development +=========== + +.. toctree:: + + specifications + contributing + changelog diff --git a/doc/specifications.rst b/doc/development/specifications.rst similarity index 100% rename from doc/specifications.rst rename to doc/development/specifications.rst diff --git a/doc/features.rst b/doc/features.rst new file mode 100644 index 00000000..16c43110 --- /dev/null +++ b/doc/features.rst @@ -0,0 +1,314 @@ +.. This page should list the functional perimiter of Canaille, + without mentioning too much technical details. We should avoid giving + explicit configuration parameters for instance. However, we should put as + much links to other sections of the documentation as possible. + + TODO: replace 'users with user management permission' by 'administrators'? + +Features +######## + +Here are the different features that Canaille provides. +You can enable any of those features with the :doc:`configuration ` to fit any :doc:`use cases ` you may meet. +Check our :ref:`roadmap ` to see what is coming next. + +Users can interact with Canaille through its :ref:`web interface ` and administrators can also use its :ref:`command line interface `. +Canaille can handle data stored in different :ref:`database backends `. + +Web interface +************* + +Canaille web interface can be used either in :doc:`production environments ` or locally for development purposes. + +.. _feature_profile_management: + +Profile management +================== + +.. image:: _static/profile.webp + :width: 200px + :alt: Profile + :align: right + +Canaille provides an interface to manage user profiles. + +The exact list of displayed fields, and wether they are :attr:`writable ` or :attr:`read-only ` depends on the user :class:`Access Control List settings (ACL) `. + +Depending on their ACL :class:`permissions `, users can either be allowed to edit their own profile, edit any user profile, or do nothing at all. + +.. _feature_email_confirmation: + +Email confirmation +================== + +If the :attr:`email confirmation feature ` is enabled, any modification or addition of a profile email will send a confirmation mail to the new address. The mail will contain a link that users will need to click on to confirm their email address. + +Users with :attr:`user management permission ` can set user emails without confirmation though. + +.. _feature_group_management: + +Group management +================ + +.. image:: _static/group-edition.webp + :width: 200px + :alt: Group edition + :align: right + +In a similar fashion than :ref:`profile management ` Canaille provides an interface to manage user groups. + +The group management is quite simple at the moment and consists in a group name and description, and the list of its members. +Group membership can be use as :attr:`ACL Filter ` to define user permissions. + +.. todo:: + At the moment adding an user to a group can only be achieved by the user settings page, but we are :issue:`working to improve this <192>`. + +Group management can be enable with a :attr:`dedicated user permission `. + +.. important:: + Due to limitations in the :ref:`LDAP backend `, groups must have at least one member. + Thus it is not possible to remove the last user of a group without removing the group. + +.. _feature_user_authentication: + +User authentication +=================== + +Unless their account is :ref:`locked `, users can authenticate with a login and a password. + +.. important:: + + For security reasons, it won't be told to users if they try to sign in with an unexisting logging, unless explicitly :attr:`set in the configuration `. + +.. todo:: :ref:`LDAP backend ` users can define which :class:`user field ` should be used as the login (such as :attr:`~canaille.core.models.User.user_name` or :attr:`~canaille.core.models.User.emails`) using a :attr:`configuration parameter `, but other backends can only login using :attr:`~canaille.core.models.User.user_name`. We are :issue:`working to improve this <196>`. + +.. _feature_user_registration: + +User registration +================= + +Users can create accounts on Canaille if the feature :attr:`registration feature ` is enabled. They will be able to fill a registration form with the fields detailed in the default :class:`ACL settings `. + +If :attr:`email confirmation ` is also enabled, users will be sent a confirmation link to their email address, on which they will need to click in order to finalize their registration. + +.. _feature_user_invitation: + +User invitation +=============== + +.. image:: _static/user-invite.webp + :width: 200px + :alt: User invitation + :align: right + +If a :class:`mail server ` is configured, users with :attr:`user management permission ` can create an invitation link for one user. + +The link goes to a registration form, even if regular :ref:`user registration ` is disabled. + +It can be automatically sent by email to the new user. + +.. _feature_account_locking: + +Account locking +=============== + +If Canaille is plugged to a :ref:`backend ` that supports it, user accounts can be locked by users with :attr:`user management permission `. +The lock date can be set instantly or at a given date in the future. + +At the moment a user account is locked: + +- their open sessions will be closed; +- they won't be able to sign in again; +- no new OIDC token will be issued; + +User accounts must be manually unlocked by an administrator for the users to regain access to those actions. + +.. _feature_account_deletion: + +Account deletion +================ + +Users with the :attr:`account deletion permission ` are allowed to delete their own account. + +Users that also have the :attr:`user management permission ` are also allowed to delete other users accounts. + +.. _feature_password_recovery: + +Password recovery +================= + +.. image:: _static/password-recovery.webp + :width: 200px + :alt: Group edition + :align: right + +If a :class:`mail server ` is configured and the :attr:`password recovery feature ` is enabled, then users can ask for a password reset email if they cannot remember their password. + +The email will be sent to the email addresses filled in their profile, and will contain a link that will allow them to choose a new password. . + +.. todo:: + + Check that password recovery is disabled on locked accounts. + +.. _feature_password_reset: + +Password reset +============== + +If a :class:`mail server ` is configured, :attr:`user management permission ` can send password reset mails to users. +The mails contains a link that allow users to choose a new password without having to retrieve the old one. + +.. _feature_password_initialization: + +Password initialization +======================= + +User :attr:`passwords ` are optional. +If a :class:`mail server ` is configured, when users with no password attempt to sign in, they are invited to click a button that will send them a password initialization mail. +The mail contains a link that leads to a form that allows users to choose a password. + +.. _feature_i18n: + +Internationalization +==================== + +.. image:: https://hosted.weblate.org/widgets/canaille/-/canaille/multi-blue.svg + :alt: Translation state + :align: right + :width: 600px + +Canaile will display in your :attr:`preferred language ` if available, or your browser language if available (and if it is not you can :ref:`help us with the translation `). +If you prefer, you can also :attr:`force a language ` for every users. + +.. _feature_ui: + +Lightweight +=========== + +The web interface is lightweight, so everything should load quickly. +There is a few Javascript here and there to smooth the experience, but no Javascript at all is needed to use Canaille. + +Customizable +============ + +The default theme should be good enough for most usages. +It has a dark theme, display well on mobile, and let you choose a :attr:`logo ` and a :attr:`favicon `. + +If you need more you can also use a :attr:`custom theme `. + +.. _feature_oidc: + +OpenID Connect +************** + +Canaille implements a :ref:`subset` of the OAuth2/OpenID Connect specifications . +This allows to provide :abbr:`SSO (Single Sign-On)` and :abbr:`SLO (Single Log-On)` to applications plugged to Canaille. + +Consent management +================== + +.. image:: _static/consent.webp + :width: 200px + :alt: Profile + :align: right + + +Users can give their consent to application requesting access to their personal information, +and then revoke those consent at their will. + +Application management +====================== + +Users with the right :attr:`permission ` can manager OIDC clients through the web interface. + +In some cases, it might be useful to avoid the consent page for some trusted applications, so clients can be pre-consented. + +Discovery +========= + +Canaille implements the :doc:`Discovery specifications ` so most of the applications plugged to Canaille can auto-configure themselves. + +Dynamic Client Registration +=========================== + +Canaille implements the :doc:`Dynamic Client Registration specifications `, so when the :attr:`feature is enabled `, clients can register themselves on Canaille without an administrator intervention. + +.. _feature_cli: + +Command Line Interface +********************** + +Canaille comes with a :abbr:`CLI (Command Line Interface)` to help administrators in hosting and management. + +There are tools to :ref:`check your configuration ` or to :ref:`install missing parts `. +You can use the CLI to :ref:`create `, :ref:`read `, :ref:`update ` and :ref:`delete ` models such as :class:`users `, :class:`groups ` or :class:`OIDC clients `. + +There are also tools to :ref:`fill your database ` with random objects, for tests purpose for instance. + +.. _feature_backends: + +Backends +******** + +Canaille can handle data from the most :ref:`common SQL databases ` such as PostgreSQL, MariaDB or SQLite, as well as :ref:`OpenLDAP `. +It also comes with a no-dependency :ref:`in-memory database ` that can be used in unit tests suites. + +Miscellaneous +************* + +.. _feature_logging: + +Logging +======= + +Canaille writes :attr:`logs ` for every important event happening, to help administrators understand what is going on and debug funky situations. + +.. _feature_development: + +A tool for your development and tests +===================================== + +Thanks to its lightweight :ref:`in-memory database ` and its curated :ref:`dependency list `, Canaille can be used in the unit test suite of your application, so you can check how it behaves against a real world OpenID Connect server. If you work with python you might want to check :doc:`pytest-iam:index`. + +It can also being launched in your development environment, if you find that launching a Keycloak in a Docker container is too heavy for your little web application. + +It also fits well in continuous integration scenarios. Thanks to its :ref:`CLI `, you can prepare data in Canaille, let your application interract with it, and then check the side effects. + +Roadmap +******* + +Bêta version +============ + +To go out of the current Alpha version we want to achieve the following tasks: + +- :issue:`Configuration validation using pydantic <138>` + +Stable version +============== + +Before we push Canaille in stable version we want to achieve the following tasks: + +Security +-------- + +- :issue:`Password hashing configuration <175>` +- :issue:`Authentication logging policy <177>` +- :issue:`Intruder lockout <173>` +- :issue:`Password expiry policy <176>` +- :issue:`Password compromission check <179>` +- :issue:`Multi-factor authentication: Email <47>` +- :issue:`Multi-factor authentication: SMS <47>` +- :issue:`Multi-factor authentication: OTP <47>` + +Packaging +--------- + +- :issue:`Nix package <190>` +- :issue:`Docker / OCI package <59>` + +And beyond +========== + +- :issue:`OpenID Connect certification <182>` +- :issue:`SCIM support <116>` diff --git a/doc/index.rst b/doc/index.rst index 301fa196..c2e5a0be 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,3 +1,5 @@ +:layout: landing + .. figure:: _static/canaille-full-black.webp :width: 400 :figclass: light-only @@ -8,53 +10,71 @@ :figclass: dark-only :align: center +.. rst-class:: lead + + Lightweight Identity and Autorization Management + +---- + **Canaille** is a French word meaning *rascal*. It is roughly pronounced **Can I?**, as in *Can I access your data?* Canaille is a lightweight identity and authorization management software. - It aims to be very light, simple to install and simple to maintain. Its main features are : -- User profile and groups management; -- Authentication, registration, email confirmation, "I forgot my password" emails; -- OpenID Connect identity provider; -- postgresql, mariadb and OpenLDAP first-class citizenship; -- Customizable, themable; -- The code is easy to read and easy to edit! +.. grid:: 3 + :gutter: 2 + :padding: 0 -Screenshots -=========== + .. grid-item-card:: Profile management + :link-type: ref + :link: feature_profile_management -.. image:: _static/login.webp - :width: 225 - :alt: Login + User profile and groups management, + Basic permissions -.. image:: _static/profile.webp - :width: 225 - :alt: Profile + .. grid-item-card:: User authentication + :link-type: ref + :link: feature_user_authentication -.. image:: _static/consent.webp - :width: 225 - :alt: Consent + Authentication, registration, email confirmation, "I forgot my password" emails -Table of contents -================= + .. grid-item-card:: :abbr:`SSO (Single Sign-On)` + :link-type: ref + :link: feature_oidc + + OpenID Connect identity provider + + .. grid-item-card:: Multi-database support + :link-type: ref + :link: feature_backends + + PostgreSQL, Mariadb and OpenLDAP first-class citizenship + + .. grid-item-card:: Customization + :link-type: ref + :link: feature_ui + + Put Canaille at yours colors by choosing a logo or use a custom theme! + + .. grid-item-card:: Developers friendliness + :link-type: ref + :link: feature_development + + Canaille can easily fit in your unit tests suite or in your Continuous Integration. + +.. container:: buttons + + :doc:`Full feature list ` + +.. rst-class:: lead + + Documentation + +---- .. toctree:: :maxdepth: 2 - install - deployment - databases - configuration - commands - troubleshooting - reference - specifications - contributing - changelog - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` + features + tutorial/index + references/index + development/index diff --git a/doc/commands.rst b/doc/references/commands.rst similarity index 85% rename from doc/commands.rst rename to doc/references/commands.rst index 4350fc1c..9b613952 100644 --- a/doc/commands.rst +++ b/doc/references/commands.rst @@ -3,34 +3,50 @@ Command Line Interface Canaille provide several commands to help administrator manage their data. +.. _cli_check: + .. click:: canaille.app.commands:check :prog: canaille check :nested: full +.. _cli_clean: + .. click:: canaille.oidc.commands:clean :prog: canaille clean :nested: full +.. _cli_install: + .. click:: canaille.app.commands:install :prog: canaille install :nested: full +.. _cli_populate: + .. click:: canaille.core.commands:populate :prog: canaille populate :nested: full +.. _cli_get: + .. click:: doc.commands:get :prog: canaille get :nested: full +.. _cli_set: + .. click:: doc.commands:set :prog: canaille set :nested: full +.. _cli_create: + .. click:: doc.commands:create :prog: canaille create :nested: full +.. _cli_delete: + .. click:: doc.commands:delete :prog: canaille delete :nested: full diff --git a/doc/configuration.rst b/doc/references/configuration.rst similarity index 60% rename from doc/configuration.rst rename to doc/references/configuration.rst index c6339c59..c78b0107 100644 --- a/doc/configuration.rst +++ b/doc/references/configuration.rst @@ -1,12 +1,15 @@ 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. +Canaille can be configured either by a environment variables, environment file, or by a configuration file. -Toml file -========= +Configuration file +================== -:: +The configuration can be written in `toml` configuration file which path is passed in the :envvar:`CONFIG` environment variable. + +.. code-block:: toml + :caption: config.toml SECRET_KEY = "very-secret" @@ -17,7 +20,7 @@ Toml file DATABASE_URI = "postgresql://user:password@localhost/database" ... -You can have a look at the :ref:`configuration:Example file` for inspiration. +You can have a look at the :ref:`example file ` for inspiration. Environment variables ===================== @@ -25,17 +28,20 @@ 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. +Environment file +================ + +Any environment variable can also be written in a ``.env``, and will be read if present. .. TODO: Uncomment this when pydantic-settings implements nested secrets directories https://github.com/pydantic/pydantic-settings/issues/154 -Secret parameters -================= + Secret parameters + ================= - A ``SECRETS_DIR`` environment variable can be passed as an environment variable, being a path to a directory in which are stored files named after the configuration settings. + A :envvar:`SECRETS_DIR` environment variable can be passed as an environment variable, being a path to a directory in which are stored files named after the configuration settings. - For instance, you can set ``SECRETS_DIR=/run/secrets`` and put your secret key in the file ``/run/secrets/SECRET_KEY``. + For instance, you can set ``SECRETS_DIR=/run/secrets`` and put your secret key in the file ``/run/secrets/SECRET_KEY``. Parameters ========== @@ -61,5 +67,6 @@ Example file Here is a configuration file example: -.. literalinclude :: ../canaille/config.sample.toml +.. literalinclude :: ../../canaille/config.sample.toml :language: toml + :caption: config.toml diff --git a/doc/references/index.rst b/doc/references/index.rst new file mode 100644 index 00000000..62d4d298 --- /dev/null +++ b/doc/references/index.rst @@ -0,0 +1,8 @@ +References +========== + +.. toctree:: + + configuration + commands + models diff --git a/doc/reference.rst b/doc/references/models.rst similarity index 94% rename from doc/reference.rst rename to doc/references/models.rst index 28be1303..a54d097d 100644 --- a/doc/reference.rst +++ b/doc/references/models.rst @@ -1,5 +1,5 @@ -Reference -######### +Data models +########### This reference details the data models used by Canaille. This is mostly useful for developers. diff --git a/doc/databases.rst b/doc/tutorial/databases.rst similarity index 90% rename from doc/databases.rst rename to doc/tutorial/databases.rst index 63ccbe64..33c1d0a3 100644 --- a/doc/databases.rst +++ b/doc/tutorial/databases.rst @@ -4,9 +4,6 @@ Databases Canaille can read and save data in different databases. This page presents the different database backends and their specificities: -.. contents:: - :local: - Memory ====== @@ -21,7 +18,10 @@ SQL Canaille can use any database supported by `SQLAlchemy `_, such as sqlite, postgresql or mariadb. -It is used when the ``CANAILLE_SQL`` configuration parameter is defined. For instance:: +It is used when the ``CANAILLE_SQL`` configuration parameter is defined. For instance: + +.. code-block:: toml + :caption: config.toml [CANAILLE_SQL] SQL_DATABASE_URI = "postgresql://user:password@localhost/database" @@ -32,7 +32,10 @@ LDAP ==== Canaille can use OpenLDAP as its main database. -It is used when the ``CANAILLE_LDAP`` configuration parameter is defined. For instance:: +It is used when the ``CANAILLE_LDAP`` configuration parameter is defined. For instance: + +.. code-block:: toml + :caption: config.toml [CANAILLE_LDAP] URI = "ldap://ldap" @@ -67,11 +70,11 @@ overlays are needed for the Canaille group membership to work correctly. Here is a configuration example compatible with canaille: -.. literalinclude :: ../demo/ldif/memberof-config.ldif +.. literalinclude :: ../..//demo/ldif/memberof-config.ldif :language: ldif :caption: memberof-config.ldif -.. literalinclude :: ../demo/ldif/refint-config.ldif +.. literalinclude :: ../..//demo/ldif/refint-config.ldif :language: ldif :caption: refint-config.ldif @@ -90,11 +93,11 @@ If the `ppolicy ServerName mydomain.tld @@ -156,10 +158,11 @@ Apache Create the first user ===================== -Once canaille is installed, you have several ways to populate the database. The obvious one is by adding -directly users and group into your LDAP directory. You might also want to temporarily enable then the -:attr:`~canaille.core.configuration.CoreSettings.ENABLE_REGISTRATION` configuration parameter to allow you to create your first users. Then, if you -have configured your ACLs properly then you will be able to manage users and groups through the Canaille -interface. +Once canaille is installed, soon enough you will need to add users. +To create your first user you can use the :ref:`canaille create ` CLI. + +.. code-block:: bash + + canaille create user --user-name admin --password admin --emails admin@mydomain.example --first-name George --last-name Abitbol .. _WebFinger: https://www.rfc-editor.org/rfc/rfc7033.html diff --git a/doc/tutorial/index.rst b/doc/tutorial/index.rst new file mode 100644 index 00000000..27abe826 --- /dev/null +++ b/doc/tutorial/index.rst @@ -0,0 +1,10 @@ +Tutorial +######## + +.. toctree:: + :maxdepth: 2 + + install + deployment + databases + troubleshooting diff --git a/doc/install.rst b/doc/tutorial/install.rst similarity index 98% rename from doc/install.rst rename to doc/tutorial/install.rst index d2972811..fd3af07f 100644 --- a/doc/install.rst +++ b/doc/tutorial/install.rst @@ -7,9 +7,6 @@ Installation The installation of canaille consist in several steps, some of which you can do manually or with command line tool: -.. contents:: - :local: - Get the code ============ @@ -45,7 +42,7 @@ Choose a path where to store your configuration file. You can pass any configura sudo mkdir --parents "$CANAILLE_CONF_DIR" sudo cp $CANAILLE_INSTALL_DIR/env/lib/python*/site-packages/canaille/config.sample.toml "$CANAILLE_CONF_DIR/config.toml" -You should then edit your configuration file to adapt the values to your needs. Look at the configuration details in the :doc:`configuration` page. +You should then edit your configuration file to adapt the values to your needs. Look at the configuration details in the :doc:`configuration <../references/configuration>` page. Install and check ================= diff --git a/doc/troubleshooting.rst b/doc/tutorial/troubleshooting.rst similarity index 100% rename from doc/troubleshooting.rst rename to doc/tutorial/troubleshooting.rst diff --git a/poetry.lock b/poetry.lock index b0e21174..cdc5f0a1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1913,6 +1913,29 @@ click = ">=8.0" docutils = "*" sphinx = ">=4.0" +[[package]] +name = "sphinx-design" +version = "0.5.0" +description = "A sphinx extension for designing beautiful, view size responsive web components." +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinx_design-0.5.0-py3-none-any.whl", hash = "sha256:1af1267b4cea2eedd6724614f19dcc88fe2e15aff65d06b2f6252cee9c4f4c1e"}, + {file = "sphinx_design-0.5.0.tar.gz", hash = "sha256:e8e513acea6f92d15c6de3b34e954458f245b8e761b45b63950f65373352ab00"}, +] + +[package.dependencies] +sphinx = ">=5,<8" + +[package.extras] +code-style = ["pre-commit (>=3,<4)"] +rtd = ["myst-parser (>=1,<3)"] +testing = ["myst-parser (>=1,<3)", "pytest (>=7.1,<8.0)", "pytest-cov", "pytest-regressions"] +theme-furo = ["furo (>=2023.7.0,<2023.8.0)"] +theme-pydata = ["pydata-sphinx-theme (>=0.13.0,<0.14.0)"] +theme-rtd = ["sphinx-rtd-theme (>=1.0,<2.0)"] +theme-sbt = ["sphinx-book-theme (>=1.0,<2.0)"] + [[package]] name = "sphinx-issues" version = "4.1.0" @@ -1996,6 +2019,21 @@ lint = ["docutils-stubs", "flake8", "mypy"] standalone = ["Sphinx (>=5)"] test = ["html5lib", "pytest"] +[[package]] +name = "sphinxcontrib-images" +version = "0.9.4" +description = "Sphinx extension for thumbnails" +optional = false +python-versions = "*" +files = [ + {file = "sphinxcontrib-images-0.9.4.tar.gz", hash = "sha256:f6c237d0430793e65d91dbddb13b1fb26a2cf838040a9deeb52112969fbc4a4b"}, + {file = "sphinxcontrib_images-0.9.4-py2.py3-none-any.whl", hash = "sha256:8863e8e8533a116f45cb92523938ab25879cc31dc594f5de4c3dbd9ab3d440b0"}, +] + +[package.dependencies] +requests = ">2.2,<3" +sphinx = {version = ">=2.0", markers = "python_version >= \"3.0\""} + [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" @@ -2463,4 +2501,4 @@ sql = ["passlib", "sqlalchemy", "sqlalchemy-json", "sqlalchemy-utils"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "c0cd43822b196a493e086b80389c22e1be6a488ae2ce8bcc21354895b2c10e67" +content-hash = "ba6d179f761bb617fb6f4f435b71a8a98d9267bb656aa0f24520a931b0f3effe" diff --git a/pyproject.toml b/pyproject.toml index 12fbb967..9081f651 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,9 +73,11 @@ optional = true autodoc-pydantic = "^2.0.1" shibuya = "^2024.3.1" sphinx = "^7.0.0" +sphinx-design = "^0.5.0" sphinx-sitemap = "^2.5.1" sphinx-issues = "^4.0.0" sphinx-click = "^6.0.0" +sphinxcontrib-images = "^0.9.4" [tool.poetry.group.dev.dependencies] coverage = {version = "*", extras=["toml"]}