Merge branch 'main' of gitlab.com:yaal/canaille into 179-check-passwords-on-compromised-password-databases

This commit is contained in:
sebastien 2024-11-07 09:15:29 +01:00
commit e6a9f2dcc6
22 changed files with 2341 additions and 2977 deletions

View file

@ -11,7 +11,7 @@ on:
- '*.*.*' - '*.*.*'
jobs: jobs:
tests: tests:
name: ${{ matrix.python }} name: py${{ matrix.python }} unit tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
@ -23,12 +23,12 @@ jobs:
- '3.10' - '3.10'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install Poetry - name: Install uv
uses: snok/install-poetry@v1 uses: astral-sh/setup-uv@v3
- uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python }} enable-cache: true
cache: 'poetry' - name: Install Python ${{ matrix.python }}
run: uv python install ${{ matrix.python }}
- name: Install apt dependencies - name: Install apt dependencies
run: | run: |
sudo apt update sudo apt update
@ -36,25 +36,25 @@ jobs:
- name: App armor configuration for slapd - name: App armor configuration for slapd
if: ${{ !env.ACT }} if: ${{ !env.ACT }}
run: sudo aa-complain /usr/sbin/slapd run: sudo aa-complain /usr/sbin/slapd
- name: Install dependencies and run tests - name: Run tests
run: | run: |
# python tzinfo fails on 'act' without this # python tzinfo fails on 'act' without this
ulimit -n 1024 ulimit -n 1024
export TZ=UTC export TZ=UTC
poetry --version uv sync --all-extras
poetry install --extras all uv run pytest --showlocals
poetry run pytest --showlocals
minversions: minversions:
name: minimum dependency versions name: minimum dependency versions
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install Poetry - name: Install uv
uses: snok/install-poetry@v1 uses: astral-sh/setup-uv@v3
- uses: actions/setup-python@v5
with: with:
python-version: '3.10' enable-cache: true
cache: 'poetry' - name: Install Python 3.10
run: uv python install 3.10
- name: Install apt dependencies - name: Install apt dependencies
run: | run: |
sudo apt update sudo apt update
@ -62,37 +62,33 @@ jobs:
- name: App armor configuration for slapd - name: App armor configuration for slapd
if: ${{ !env.ACT }} if: ${{ !env.ACT }}
run: sudo aa-complain /usr/sbin/slapd run: sudo aa-complain /usr/sbin/slapd
- run: sed -i -E 's/"(\^|>=)([0-9\.]+)([^,]*)"/"==\2"/' pyproject.toml - name: Run tests
- run: sed -i -E 's/python = "==/python = "^/' pyproject.toml
- name: Install dependencies and run tests
run: | run: |
# python tzinfo fails on 'act' without this # python tzinfo fails on 'act' without this
ulimit -n 1024 ulimit -n 1024
export TZ=UTC export TZ=UTC
poetry --version uv sync --all-extras --resolution=lowest-direct
poetry lock uv run pytest --showlocals
poetry install --extras all
poetry run pytest --showlocals
style: style:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pre-commit/action@v3.0.1 - uses: pre-commit/action@v3.0.1
doc: doc:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install Poetry - name: Install uv
uses: snok/install-poetry@v1 uses: astral-sh/setup-uv@v3
- uses: actions/setup-python@v5
with: with:
python-version: '3.13' enable-cache: true
cache: 'poetry'
- name: Install apt dependencies - name: Install apt dependencies
run: | run: |
sudo apt update sudo apt update
sudo DEBIAN_FRONTEND=noninteractive apt --yes --quiet install libsasl2-dev python3-dev libldap2-dev libssl-dev slapd ldap-utils sudo DEBIAN_FRONTEND=noninteractive apt --yes --quiet install libsasl2-dev python3-dev libldap2-dev libssl-dev slapd ldap-utils
- run: | - run: |
export TZ=UTC export TZ=UTC
poetry install --with doc uv sync --group doc
poetry run sphinx-build doc build/sphinx/html uv run sphinx-build doc build/sphinx/html

View file

@ -1,80 +1,82 @@
--- ---
image: python
stages: stages:
- test - test
- build - build
- release - release
before_script: variables:
- apt update UV_VERSION: 0.4
- env DEBIAN_FRONTEND=noninteractive apt install --yes --quiet python3-dev libldap2-dev libsasl2-dev libssl-dev slapd ldap-utils python3-poetry UV_CACHE_DIR: .uv-cache
- poetry config virtualenvs.in-project true BASE_LAYER: bookworm-slim
cache: cache:
- key:
files:
- uv.lock
paths: paths:
- .venv - $UV_CACHE_DIR
before_script:
- apt update
- env DEBIAN_FRONTEND=noninteractive apt install --yes --quiet gcc python3-dev libldap2-dev libsasl2-dev libssl-dev slapd ldap-utils git curl
# Rust is needed to install the zxcvbn dependency
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
- export PATH="$HOME/.cargo/bin:$PATH"
# Needed until zxcvbn supports Python 3.13
# https://github.com/fief-dev/zxcvbn-rs-py/issues/2
- export PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1
style: style:
image: python:3.13 variables:
PYTHON_VERSION: "3.13"
image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
stage: test stage: test
script: script:
# pre-commit version is fixed until docformatted hook is compatible - uv sync --all-extras
# https://github.com/PyCQA/docformatter/issues/293 - uv run pre-commit run --all-files --show-diff-on-failure
- pip install "pre-commit<4.0.0" - uv cache prune --ci
- pre-commit run --all-files --show-diff-on-failure
python310:
image: python:3.10
stage: test
script:
- poetry install --extras all
- poetry run pytest
python311:
image: python:3.11
stage: test
script:
- poetry install --extras all
- poetry run pytest
python312:
image: python:3.12
stage: test
script:
- poetry install --extras all
- poetry run pytest
python313:
image: python:3.13
stage: test
script:
- poetry install --extras all
- poetry run pytest
minversions:
image: python:3.13
stage: test
script:
- sed -i -E 's/"(\^|>=)([0-9\.]+)([^,]*)"/"==\2"/' pyproject.toml
- sed -i -E 's/python = "==/python = "^/' pyproject.toml
- poetry lock
- poetry install --extras all
- poetry run pytest
doc:
image: python:3.13
stage: test
script:
- poetry install --only doc
- poetry run sphinx-build doc build/sphinx/html
coverage: coverage:
image: python:3.13 variables:
PYTHON_VERSION: "3.12"
image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
stage: test stage: test
allow_failure: true
script: script:
- pip install coveralls pyyaml tomli - uv sync --all-extras
- poetry install --extras all - uv pip install coveralls pyyaml tomli
- poetry run pytest --cov --cov-fail-under=100 --cov-report term:skip-covered -n auto - uv run pytest --cov --cov-fail-under=100 --cov-report term:skip-covered -n auto
- coveralls - uv run coveralls
- uv cache prune --ci
tests:
needs: ["coverage", "style"]
parallel:
matrix:
- PYTHON_VERSION: ['3.10', '3.11', '3.12']
image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
stage: test
script:
- uv sync --all-extras
- uv run pytest
- uv cache prune --ci
minversions:
needs: ["tests"]
variables:
PYTHON_VERSION: "3.10"
image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
stage: test
script:
- uv sync --all-extras --resolution=lowest-direct
- uv run pytest
- uv cache prune --ci
doc:
variables:
PYTHON_VERSION: "3.13"
image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
stage: test
script:
- uv sync --group doc
- uv run sphinx-build doc build/sphinx/html
- uv cache prune --ci

View file

@ -9,11 +9,11 @@ build:
- libldap2-dev - libldap2-dev
- libssl-dev - libssl-dev
tools: tools:
python: "3.11" python: "3.12"
jobs: jobs:
post_create_environment: post_create_environment:
- pip install poetry - pip install uv
- poetry export --with doc --output requirements.txt - uv export --group doc --no-hashes --output-file requirements.txt
post_install: post_install:
- pip install . - pip install .
- pip install --requirement requirements.txt - pip install --requirement requirements.txt

View file

@ -1,22 +1,22 @@
[0.0.56] - Unreleased [0.0.56] - Unreleased
--------------------- ---------------------
Fixed
^^^^^
- With LDAP backend, updating another user groups could result in a permission lost for the editor. :issue:`202`
Added Added
^^^^^ ^^^^^
- 1 new parameter : MAX_PASSWORD_LENGTH :issue:`174` - :attr:`~canaille.core.configuration.CoreSettings.MAX_PASSWORD_LENGHT` and
- 1 new validator : maximum password length (default 1000) :issue:`174` :attr:`~canaille.core.configuration.CoreSettings.MIN_PASSWORD_LENGHT` configuration options :issue:`174`
- password strength progress bar :issue:`174` - Password strength visual indicator :issue:`174`
- implementation of zxcvbn-rs-py which score the password strength :issue:`174` - Security events logs :issue:`177`
- New security events logs :issue:`177`
- Support for Python 3.13 :pr:`186` - Support for Python 3.13 :pr:`186`
Changed Changed
^^^^^^^ ^^^^^^^
- Maximum Python requirement is < 3.13 (because of the password_strength_calculator : zxcvbn-rs-py)
- MIN_PASSWORD_LENGTH become a parameter :issue:`174`
- all password tests and validator are supported by password1 field :issue:`174`
- password2 (or Password confirmation) field only support "EQUAL TO PASSWORD" test :issue:`174`
- Update to HTMX 2.0.3 :pr:`184` - Update to HTMX 2.0.3 :pr:`184`
- Migrate from poetry to uv :pr:`187`
Removed Removed
^^^^^^^ ^^^^^^^

View file

@ -106,23 +106,23 @@ users and groups with the ``populate`` command:
.. code-block:: console .. code-block:: console
# If using docker: # If using docker:
docker compose exec canaille env CONFIG=conf-docker/canaille-ldap.toml poetry run canaille populate --nb 100 users # or docker-compose docker compose exec canaille env CONFIG=conf-docker/canaille-ldap.toml uv run canaille populate --nb 100 users # or docker-compose
# If running in local environment # If running in local environment
env CONFIG=conf/canaille-ldap.toml poetry run canaille populate --nb 100 users env CONFIG=conf/canaille-ldap.toml uv run canaille populate --nb 100 users
Adapt to use either the `ldap` or the `sql` configuration file. Note that this will not work with the memory backend. Adapt to use either the `ldap` or the `sql` configuration file. Note that this will not work with the memory backend.
Unit tests Unit tests
---------- ----------
To run the tests, you just can run `poetry run pytest` and/or `tox` to test all the supported python environments. To run the tests, you just can run `uv run pytest` and/or `uv run tox` to test all the supported python environments.
Everything must be green before patches get merged. Everything must be green before patches get merged.
To test a specific backend you can pass ``--backend memory``, ``--backend sql`` or ``--backend ldap`` to pytest and tox. To test a specific backend you can pass ``--backend memory``, ``--backend sql`` or ``--backend ldap`` to pytest and tox.
The test coverage is 100%, patches won't be accepted if not entirely covered. You can check the The test coverage is 100%, patches won't be accepted if not entirely covered. You can check the
test coverage with ``poetry run pytest --cov --cov-report=html`` or ``tox -e coverage -- --cov-report=html``. test coverage with ``uv run pytest --cov --cov-report=html`` or ``tox -e coverage -- --cov-report=html``.
You can check the HTML coverage report in the newly created `htmlcov` directory. You can check the HTML coverage report in the newly created `htmlcov` directory.
Code style Code style
@ -170,12 +170,13 @@ The generated documentation is located at ``build/sphinx/html``.
Publish a new release 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. 1. Check that dependencies are up to date with ``uv sync --upgrade`` 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`` 2. Check that tests are still green for every supported python version, and that coverage is still at 100%, by running ``uv run tox``
3. Check that the demo environments are still working 3. Check that the demo environments are still working
4. Check that the :ref:`development/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`` 5. Increase the version number in ``pyproject.toml``
6. Commit with ``git commit`` 6. Commit with ``git commit``
7. Publish with ``poetry publish --build`` 7. Build with ``uv build``
7. Publish with ``uv publish``
8. Tag you commit with ``git tag XX.YY.ZZ`` 8. Tag you commit with ``git tag XX.YY.ZZ``
9. Push the release commit and the new tag on the repository with ``git push --tags`` 9. Push the release commit and the new tag on the repository with ``git push --tags``

View file

@ -1,6 +0,0 @@
include canaille/*.sample.*
graft canaille/backends/ldap/schemas
graft canaille/templates
graft canaille/themes
graft canaille/translations
graft canaille/static

View file

@ -57,7 +57,7 @@ def setup_flask(app):
@app.context_processor @app.context_processor
def global_processor(): def global_processor():
from canaille.app.flask import current_user from canaille.app.session import current_user
return { return {
"debug": app.debug or app.config.get("TESTING", False), "debug": app.debug or app.config.get("TESTING", False),

View file

@ -5,61 +5,14 @@ from urllib.parse import urlunsplit
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask import g
from flask import request from flask import request
from flask import session
from werkzeug.routing import BaseConverter from werkzeug.routing import BaseConverter
from canaille.app import models
from canaille.app.i18n import gettext as _ from canaille.app.i18n import gettext as _
from canaille.app.session import current_user
from canaille.app.themes import render_template from canaille.app.themes import render_template
def current_user():
if "user" in g:
return g.user
for user_id in session.get("user_id", [])[::-1]:
user = current_app.backend.instance.get(models.User, user_id)
if user and (
not current_app.backend.has_account_lockability() or not user.locked
):
g.user = user
return g.user
session["user_id"].remove(user_id)
if "user_id" in session and not session["user_id"]:
del session["user_id"]
return None
def login_user(user):
"""Open a session for the user."""
g.user = user
try:
previous = (
session["user_id"]
if isinstance(session["user_id"], list)
else [session["user_id"]]
)
session["user_id"] = previous + [user.id]
except KeyError:
session["user_id"] = [user.id]
def logout_user():
"""Close the user session."""
try:
session["user_id"].pop()
del g.user
if not session["user_id"]:
del session["user_id"]
except (IndexError, KeyError):
pass
def user_needed(): def user_needed():
def wrapper(view_function): def wrapper(view_function):
@wraps(view_function) @wraps(view_function)

View file

@ -46,7 +46,7 @@ def setup_i18n(app):
def locale_selector(): def locale_selector():
from .flask import current_user from .session import current_user
user = current_user() user = current_user()
available_language_codes = getattr(g, "available_language_codes", []) available_language_codes = getattr(g, "available_language_codes", [])

50
canaille/app/session.py Normal file
View file

@ -0,0 +1,50 @@
from flask import current_app
from flask import g
from flask import session
from canaille.app import models
def current_user():
if "user" in g:
return g.user
for user_id in session.get("user_id", [])[::-1]:
user = current_app.backend.instance.get(models.User, user_id)
if user and (
not current_app.backend.has_account_lockability() or not user.locked
):
g.user = user
return g.user
session["user_id"].remove(user_id)
if "user_id" in session and not session["user_id"]:
del session["user_id"]
return None
def login_user(user):
"""Open a session for the user."""
g.user = user
try:
previous = (
session["user_id"]
if isinstance(session["user_id"], list)
else [session["user_id"]]
)
session["user_id"] = previous + [user.id]
except KeyError:
session["user_id"] = [user.id]
def logout_user():
"""Close the user session."""
try:
session["user_id"].pop()
del g.user
if not session["user_id"]:
del session["user_id"]
except (IndexError, KeyError):
pass

View file

@ -123,6 +123,7 @@ class BackendModel:
return any(self.match_filter(subfilter) for subfilter in filter) return any(self.match_filter(subfilter) for subfilter in filter)
# If attribute are models, resolve the instance # If attribute are models, resolve the instance
filter = filter.copy()
for attribute, value in filter.items(): for attribute, value in filter.items():
model, _ = self.get_model_annotations(attribute) model, _ = self.get_model_annotations(attribute)

View file

@ -302,16 +302,12 @@ class CoreSettings(BaseModel):
MIN_PASSWORD_LENGTH: int = 8 MIN_PASSWORD_LENGTH: int = 8
"""Minimum length for user password. """Minimum length for user password.
Defaults to 8.
It is possible not to set a minimum, by entering None or 0. It is possible not to set a minimum, by entering None or 0.
""" """
MAX_PASSWORD_LENGTH: int = 1000 MAX_PASSWORD_LENGTH: int = 1000
"""Maximum length for user password. """Maximum length for user password.
Defaults to 1000.
There is a technical limit with passlib used by sql database of 4096 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, characters. If the value entered is 0 or None, or greater than 4096,
then 4096 will be retained. then 4096 will be retained.

View file

@ -24,9 +24,6 @@ from canaille.app import build_hash
from canaille.app import default_fields from canaille.app import default_fields
from canaille.app import models from canaille.app import models
from canaille.app import obj_to_b64 from canaille.app import obj_to_b64
from canaille.app.flask import current_user
from canaille.app.flask import login_user
from canaille.app.flask import logout_user
from canaille.app.flask import permissions_needed from canaille.app.flask import permissions_needed
from canaille.app.flask import render_htmx_template from canaille.app.flask import render_htmx_template
from canaille.app.flask import request_is_htmx from canaille.app.flask import request_is_htmx
@ -42,6 +39,9 @@ from canaille.app.forms import set_readonly
from canaille.app.forms import set_writable from canaille.app.forms import set_writable
from canaille.app.i18n import gettext as _ from canaille.app.i18n import gettext as _
from canaille.app.i18n import reload_translations from canaille.app.i18n import reload_translations
from canaille.app.session import current_user
from canaille.app.session import login_user
from canaille.app.session import logout_user
from canaille.app.themes import render_template from canaille.app.themes import render_template
from canaille.backends import Backend from canaille.backends import Backend

View file

@ -8,11 +8,11 @@ from flask import session
from flask import url_for from flask import url_for
from canaille.app import build_hash from canaille.app import build_hash
from canaille.app.flask import current_user
from canaille.app.flask import login_user
from canaille.app.flask import logout_user
from canaille.app.flask import smtp_needed from canaille.app.flask import smtp_needed
from canaille.app.i18n import gettext as _ from canaille.app.i18n import gettext as _
from canaille.app.session import current_user
from canaille.app.session import login_user
from canaille.app.session import logout_user
from canaille.app.themes import render_template from canaille.app.themes import render_template
from canaille.backends import Backend from canaille.backends import Backend

View file

@ -18,10 +18,10 @@ from werkzeug.datastructures import CombinedMultiDict
from canaille import csrf from canaille import csrf
from canaille.app import models from canaille.app import models
from canaille.app.flask import current_user
from canaille.app.flask import logout_user
from canaille.app.flask import set_parameter_in_url_query from canaille.app.flask import set_parameter_in_url_query
from canaille.app.i18n import gettext as _ from canaille.app.i18n import gettext as _
from canaille.app.session import current_user
from canaille.app.session import logout_user
from canaille.app.themes import render_template from canaille.app.themes import render_template
from canaille.backends import Backend from canaille.backends import Backend

View file

@ -8,10 +8,10 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: contact@yaal.coop\n"
"POT-Creation-Date: 2024-10-28 11:57+0100\n" "POT-Creation-Date: 2024-10-28 11:57+0100\n"
"PO-Revision-Date: 2024-09-13 08:47+0000\n" "PO-Revision-Date: 2024-11-01 09:00+0000\n"
"Last-Translator: Éloi Rivard <eloi.rivard@nubla.fr>\n" "Last-Translator: sebastien yaal <sebastien@yaal.coop>\n"
"Language-Team: French <https://hosted.weblate.org/projects/canaille/canaille/" "Language-Team: French <https://hosted.weblate.org/projects/canaille/canaille/"
"fr/>\n" "fr/>\n"
"Language: fr\n" "Language: fr\n"
@ -19,7 +19,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n" "Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.8-dev\n" "X-Generator: Weblate 5.8.2-dev\n"
"Generated-By: Babel 2.12.1\n" "Generated-By: Babel 2.12.1\n"
#: canaille/app/flask.py:100 #: canaille/app/flask.py:100
@ -40,11 +40,11 @@ msgstr "Nest pas un numéro de téléphone valide"
#: canaille/app/forms.py:55 #: canaille/app/forms.py:55
msgid "Field must be at least {minimum_password_length} characters long." msgid "Field must be at least {minimum_password_length} characters long."
msgstr "" msgstr "Ce champ doit contenir au minimum {minimum_password_length} caractères."
#: canaille/app/forms.py:67 #: canaille/app/forms.py:67
msgid "Field cannot be longer than {maximum_password_length} characters." msgid "Field cannot be longer than {maximum_password_length} characters."
msgstr "" msgstr "Ce champ doit contenir au maximum {maximum_password_length} caractères."
#: canaille/app/forms.py:240 #: canaille/app/forms.py:240
msgid "The page number is not valid" msgid "The page number is not valid"
@ -1967,10 +1967,8 @@ msgid "Add another field"
msgstr "Ajouter un autre champ" msgstr "Ajouter un autre champ"
#: canaille/templates/macro/form.html:119 #: canaille/templates/macro/form.html:119
#, fuzzy
#| msgid "Password reset"
msgid "Password strength" msgid "Password strength"
msgstr "Réinitialisation du mot de passe" msgstr "Robustesse du mot de passe"
#: canaille/templates/macro/table.html:8 #: canaille/templates/macro/table.html:8
msgid "Search…" msgid "Search…"

View file

@ -7,11 +7,21 @@ RUN \
gcc \ gcc \
libsasl2-dev \ libsasl2-dev \
libldap2-dev \ libldap2-dev \
libssl-dev libssl-dev \
curl
COPY poetry.lock pyproject.toml demo/demoapp.py /opt/canaille/ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
RUN pip install poetry
ENV PATH="/root/.cargo/bin:${PATH}"
# Needed until zxcvbn supports Python 3.13
# https://github.com/fief-dev/zxcvbn-rs-py/issues/2
ENV PYO3_USE_ABI3_FORWARD_COMPATIBILITY="1"
COPY uv.lock pyproject.toml hatch_build.py LICENSE.rst README.md demo/demoapp.py /opt/canaille/
COPY canaille /opt/canaille/canaille
RUN pip install uv
WORKDIR /opt/canaille WORKDIR /opt/canaille
RUN poetry install --with demo --without dev --extras all RUN uv sync --group demo --all-extras
ENTRYPOINT ["poetry", "run", "flask", "run", "--host=0.0.0.0", "--extra-files", "/opt/canaille/conf/canaille-memory.toml", "--extra-files", "/opt/canaille/conf/canaille-ldap.toml", "--extra-files", "/opt/canaille/conf/canaille-sql.toml"] ENTRYPOINT ["uv", "run", "flask", "run", "--host=0.0.0.0", "--extra-files", "/opt/canaille/conf/canaille-memory.toml", "--extra-files", "/opt/canaille/conf/canaille-ldap.toml", "--extra-files", "/opt/canaille/conf/canaille-sql.toml"]

View file

@ -13,24 +13,28 @@ if ! type python > /dev/null 2>&1 && ! type python3 > /dev/null 2>&1; then
exit 1 exit 1
fi fi
if ! type poetry > /dev/null 2>&1; then if ! type uv > /dev/null 2>&1; then
echo "Cannot start the canaille demo server. Please install poetry on your system" echo "Cannot start the canaille demo server. Please install uv on your system"
echo "or run the demo with docker-compose." echo "or run the demo with docker-compose."
echo "https://python-poetry.org/docs/#installation" echo "https://docs.astral.sh/uv/getting-started/installation/"
exit 1 exit 1
fi fi
pushd "$DIR" > /dev/null 2>&1 || exit pushd "$DIR" > /dev/null 2>&1 || exit
poetry install --with demo --all-extras # Needed until zxcvbn supports Python 3.13
# https://github.com/fief-dev/zxcvbn-rs-py/issues/2
export PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1
uv sync --group demo --all-extras
if [ "$BACKEND" = "memory" ]; then if [ "$BACKEND" = "memory" ]; then
poetry run honcho --env ../.env --procfile Procfile-memory start uv run honcho --env ../.env --procfile Procfile-memory start
elif [ "$BACKEND" = "sql" ]; then elif [ "$BACKEND" = "sql" ]; then
poetry run honcho --env ../.env --procfile Procfile-sql start uv run honcho --env ../.env --procfile Procfile-sql start
elif [ "$BACKEND" = "ldap" ]; then elif [ "$BACKEND" = "ldap" ]; then
@ -40,7 +44,7 @@ elif [ "$BACKEND" = "ldap" ]; then
exit 1 exit 1
fi fi
poetry run honcho --env ../.env --procfile Procfile-ldap start uv run honcho --env ../.env --procfile Procfile-ldap start
else else

View file

@ -1,7 +1,9 @@
import os import os
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
def create_mo_files(setup_kwargs):
def create_mo_files():
from babel.messages.frontend import compile_catalog from babel.messages.frontend import compile_catalog
cmd = compile_catalog() cmd = compile_catalog()
@ -10,7 +12,11 @@ def create_mo_files(setup_kwargs):
cmd.statistics = True cmd.statistics = True
cmd.finalize_options() cmd.finalize_options()
cmd.run() cmd.run()
return setup_kwargs
class CustomBuildHook(BuildHookInterface):
def initialize(self, version, build_data):
create_mo_files()
if __name__ == "__main__": if __name__ == "__main__":

2615
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,13 @@
[build-system] [build-system]
requires = ["poetry-core>=1.0.0", "babel", "setuptools; python_version>='3.12'"] requires = ["hatchling", "babel", "setuptools >= 50.0.0; python_version>='3.12'"]
build-backend = "poetry.core.masonry.api" build-backend = "hatchling.build"
[tool] [project]
[tool.poetry]
name = "Canaille" name = "Canaille"
version = "0.0.55" version = "0.0.55"
description = "Lightweight identity and authorization management software" description = "Lightweight identity and authorization management software"
license = "MIT" license = {file = "LICENSE.rst"}
readme = "README.md"
keywords = ["oidc", "oauth", "oauth2", "openid", "identity"] keywords = ["oidc", "oauth", "oauth2", "openid", "identity"]
classifiers = [ classifiers = [
"Intended Audience :: Developers", "Intended Audience :: Developers",
@ -25,137 +25,102 @@ classifiers = [
"Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP", "Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP",
] ]
authors = ["Yaal Coop <contact@yaal.coop>"] authors = [{name="Yaal Coop", email="contact@yaal.coop"}]
maintainers = [ maintainers = [{name="Éloi Rivard", email="eloi@yaal.coop"}]
"Éloi Rivard <eloi@yaal.coop>",
requires-python = ">=3.10"
dependencies = [
"flask >= 3.0.0",
"flask-wtf >= 1.2.1",
"pydantic-settings >= 2.0.3",
"wtforms >= 3.1.1",
] ]
[project.optional-dependencies]
front = [
"email_validator >= 2.0.0",
"flask-babel >= 4.0.0",
"flask-themer >= 2.0.0",
"pycountry >= 23.12.7",
"pytz >= 2022.7",
"toml >= 0.10.0",
# zxcvbn does not support Python 3.13 yet
# This leads to Canaille installation to fail in some situations.
# https://github.com/fief-dev/zxcvbn-rs-py/issues/2
"zxcvbn-rs-py >= 0.1.1; python_version<'3.13'",
]
oidc = [
"authlib >= 1.3.0",
]
ldap = [
"python-ldap >= 3.4.0",
]
sentry = [
"sentry-sdk >= 2.0.0",
]
sql = [
"passlib >= 1.7.4",
"sqlalchemy >= 2.0.23",
"sqlalchemy-json >= 0.7.0",
"sqlalchemy-utils >= 0.41.1",
]
[project.urls]
homepage = "https://canaille.yaal.coop" homepage = "https://canaille.yaal.coop"
documentation = "https://canaille.readthedocs.io/en/latest/" documentation = "https://canaille.readthedocs.io/en/latest/"
repository = "https://gitlab.com/yaal/canaille" repository = "https://gitlab.com/yaal/canaille"
readme = "README.md"
include = ["canaille/translations/*/LC_MESSAGES/*.mo"]
[tool.poetry.dependencies] [dependency-groups]
python = "<3.13,>=3.10" dev = [
flask = "^3.0.0" "babel >= 2.14.0",
flask-wtf = "^1.2.1" "coverage[toml] >= 6.0.0",
pydantic-settings = "^2.0.3" "faker >= 30.0.0",
wtforms = "^3.1.1" "flask-webtest >= 0.1.6",
# pre-commit version is fixed until docformatted hook is compatible
# extra : front # https://github.com/PyCQA/docformatter/issues/293
email_validator = {version = "^2.0.0", optional=true} "pre-commit < 4.0.0",
flask-babel = {version = "^4.0.0", optional=true} "pre-commit-uv>=4.1.4",
flask-themer = {version = "^2.0.0", optional=true} "pyquery >= 2.0.0",
pycountry = {version = ">=22.1.10", optional=true} "pytest >= 8.0.0",
pytz = {version = ">=2022.7", optional=true} "pytest-cov >= 6.0.0",
toml = {version = "^0.10.0", optional=true, python = "<3.11"} "pytest-httpserver >= 1.1.0",
zxcvbn-rs-py = {version = "^0.1.1", optional=true} "pytest-lazy-fixtures >= 1.0.7",
"pytest-smtpd >= 0.1.0",
# extra : oidc "pytest-xdist >= 3.3.1",
authlib = {version = "^1.2.1", optional=true} "slapd >= 0.1.5",
"time-machine >= 2.14.1",
# extra : ldap "toml >= 0.10.0",
python-ldap = {version = "^3.4.0", optional=true} "tox-uv >= 1.16.0",
# extra : sentry
sentry-sdk = {version = "^2.0.0", optional=true, extras=["flask"]}
# extra : sql
passlib = {version = "^1.7.4", optional=true}
sqlalchemy = {version = "^2.0.23", optional=true}
sqlalchemy-json = {version = "^0.7.0", optional=true}
sqlalchemy-utils = {version = "^0.41.1", optional=true}
[tool.poetry.group.doc]
optional = true
[tool.poetry.group.doc.dependencies]
autodoc-pydantic = "^2.0.1"
shibuya = "^2024.3.1"
sphinx = ">=7.0.0"
sphinx-design = "^0.6.0"
sphinx-issues = "^5.0.0"
sphinx-click = "^6.0.0"
[tool.poetry.group.dev.dependencies]
coverage = {version = "*", extras=["toml"]}
faker = "*"
flask-webtest = "*"
# pre-commit version is fixed until docformatted hook is compatible
# https://github.com/PyCQA/docformatter/issues/293
pre-commit = "^3.0.0"
pyquery = "*"
pytest = "^8.0.0"
pytest-coverage = "*"
pytest-httpserver = "*"
pytest-lazy-fixtures = "^1.0.7"
pytest-smtpd = "^0.1.0"
pytest-xdist = "^3.3.1"
slapd = "*"
time-machine = "^2.14.1"
toml = "^0.10.0"
# Babel 2.14 does not directly depend on setuptools # Babel 2.14 does not directly depend on setuptools
# https://github.com/python-babel/babel/blob/40e60a1f6cf178d9f57fcc14f157ea1b2ab77361/CHANGES.rst?plain=1#L22-L24 # https://github.com/python-babel/babel/blob/40e60a1f6cf178d9f57fcc14f157ea1b2ab77361/CHANGES.rst?plain=1#L22-L24
# and neither python 3.12 due to PEP 632 # and neither python 3.12 due to PEP 632
# https://peps.python.org/pep-0632/ # https://peps.python.org/pep-0632/
setuptools = {version = "*", python = ">=3.12"} "setuptools >= 50.0.0; python_version>='3.12'"
[tool.poetry.group.demo]
optional = true
[tool.poetry.group.demo.dependencies]
faker = "*"
honcho = "*"
slapd = "*"
requests = "*"
watchdog = "^4.0.0"
[tool.poetry.extras]
front = [
"click",
"email_validator",
"flask-babel",
"flask-themer",
"pycountry",
"pytz",
"toml",
"zxcvbn-rs-py",
]
ldap = [
"python-ldap",
]
oidc = [
"authlib",
]
sentry = [
"sentry-sdk",
]
sql = [
"passlib",
"sqlalchemy",
"sqlalchemy-json",
"sqlalchemy-utils",
]
all = [
"click",
"email_validator",
"flask-babel",
"flask-themer",
"passlib",
"pycountry",
"pytz",
"toml",
"python-ldap",
"authlib",
"sentry-sdk",
"sqlalchemy",
"sqlalchemy-json",
"sqlalchemy-utils",
"zxcvbn-rs-py",
] ]
[tool.poetry.scripts] doc = [
"autodoc-pydantic >= 2.0.1",
"shibuya >= 2024.3.1",
"sphinx >= 7.0.0",
"sphinx-design >= 0.6.0",
"sphinx-issues >= 5.0.0",
"sphinx-click >= 6.0.0",
]
demo = [
"faker",
"honcho",
"slapd",
"requests",
"watchdog >= 4.0.0",
]
[project.scripts]
canaille = "canaille.commands:cli" canaille = "canaille.commands:cli"
[options.packages.find] [options.packages.find]
@ -166,9 +131,25 @@ exclude = [
"doc.*", "doc.*",
] ]
[tool.poetry.build] [tool.hatch.build]
generate-setup-file = false include = [
script = "build.py" "canaille/",
"doc/",
"tests/",
"CHANGES.rst",
"CONTRIBUTING.rst",
"README.md",
]
exclude = [
"docs/_build/",
]
artifacts = ["canaille/translations/*/LC_MESSAGES/*.mo"]
[tool.hatch.build.hooks.custom]
dependencies = [
"Babel>=2.6.0",
"setuptools >= 50.0.0; python_version>='3.12'",
]
[tool.coverage.run] [tool.coverage.run]
source = [ source = [
@ -206,41 +187,52 @@ force-single-line = true
docstring-code-format = true docstring-code-format = true
[tool.tox] [tool.tox]
legacy_tox_ini = """ requires = ["tox>=4.19"]
[tox] env_list = [
isolated_build = true "style",
skipsdist = true "py310",
envlist = "py311",
style "py312",
py310 "py313",
py311 "minversions",
py312 "doc",
py313 "coverage",
doc ]
coverage
[testenv] [tool.tox.env_run_base]
allowlist_externals = poetry # Needed until zxcvbn supports Python 3.13
commands = # https://github.com/fief-dev/zxcvbn-rs-py/issues/2
poetry install --extras all set_env = {PYO3_USE_ABI3_FORWARD_COMPATIBILITY = "1"}
poetry run pytest --showlocals --full-trace {posargs} runner = "uv-venv-lock-runner"
dependency_groups = ["dev"]
uv_sync_flags = ["--all-extras"]
commands = [
["pytest", "--showlocals", "--full-trace", "{posargs}"],
]
[testenv:style] [tool.tox.env.style]
commands = skip_install = true
pip install pre-commit<4 runner = "uv-venv-runner"
pre-commit run --all-files commands = [
["pre-commit", "run", "--all-files", "--show-diff-on-failure"],
]
[testenv:doc] [tool.tox.env.minversions]
commands = uv_resolution = "lowest-direct"
poetry install --with doc --without dev --extras oidc basepython = ["python3.10"]
poetry run sphinx-build doc build/sphinx/html
[testenv:coverage] [tool.tox.env.doc]
commands = dependency_groups = ["doc"]
poetry install --extras all commands = [
poetry run pytest --cov --cov-fail-under=100 --cov-report term:skip-covered {posargs:-n auto} ["sphinx-build", "--builder", "html", "doc", "build/sphinx/html"],
poetry run coverage html ["sphinx-build", "--builder", "man", "doc", "build/sphinx/html"],
""" ]
[tool.tox.env.coverage]
commands = [
["pytest", "--cov", "--cov-fail-under=100", "--cov-report", "term:skip-covered", "{posargs}"],
["coverage", "html"],
]
[[tool.babel.mappings]] [[tool.babel.mappings]]
method = "python" method = "python"

1976
uv.lock Normal file

File diff suppressed because it is too large Load diff