Merge branch canaille:main into main

This commit is contained in:
Denise 2023-06-05 07:34:49 +00:00
commit db72597cc0
41 changed files with 863 additions and 637 deletions

View file

@ -21,7 +21,6 @@ jobs:
- '3.10'
- '3.9'
- '3.8'
- '3.7'
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v2

View file

@ -20,11 +20,6 @@ style:
stage: test
script: tox -e style
python37:
image: python:3.7
stage: test
script: tox -e py37
python38:
image: python:3.8
stage: test

View file

@ -11,6 +11,7 @@ repos:
rev: 'v0.0.269'
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/asottile/reorder_python_imports
rev: v3.9.0
hooks:
@ -20,7 +21,7 @@ repos:
rev: v3.4.0
hooks:
- id: pyupgrade
args: ["--py37-plus"]
args: ["--py38-plus"]
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:

View file

@ -3,11 +3,20 @@ All notable changes to this project will be documented in this file.
The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.0.0/>`_,
and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0.html>`_.
Removed
*******
- Stop support for python 3.7 :pr:`131`
[0.0.26] - 2023-06-03
=====================
Added
*****
- Implemented account expiration based on OpenLDAP ppolicy overlay. Needs OpenLDAP 2.5+
:issue:`13` :pr:`118`
- Timezone configuration entry. :issue:`137` :pr:`130`
Fixed
*****

View file

@ -17,6 +17,7 @@ csrf = CSRFProtect()
def setup_config(app, config=None, validate=True):
import canaille.app.configuration
from canaille.oidc.installation import install
app.config.from_mapping(
{
@ -35,6 +36,9 @@ def setup_config(app, config=None, validate=True):
"Either create conf/config.toml or set the 'CONFIG' variable environment."
)
if app.debug: # pragma: no cover
install(app.config)
if validate:
canaille.app.configuration.validate(app.config)
@ -50,6 +54,9 @@ def setup_backend(app, backend):
g.backend = backend
app.backend = backend
if app.debug: # pragma: no cover
backend.install(app.config)
def setup_sentry(app): # pragma: no cover
if not app.config.get("SENTRY_DSN"):
@ -182,7 +189,6 @@ def create_app(config=None, validate=True, backend=None):
try:
from .oidc.oauth import setup_oauth
from .app.i18n import setup_i18n
from .app.installation import install
setup_logging(app)
setup_backend(app, backend)
@ -193,9 +199,6 @@ def create_app(config=None, validate=True, backend=None):
setup_themer(app)
setup_flask(app)
if app.debug: # pragma: no cover
install(app.config)
except Exception as exc: # pragma: no cover
if sentry_sdk:
sentry_sdk.capture_exception(exc)

View file

@ -1,8 +1,11 @@
import datetime
import math
import pytz
import wtforms
from canaille.app.i18n import DEFAULT_LANGUAGE_CODE
from canaille.app.i18n import locale_selector
from canaille.app.i18n import timezone_selector
from flask import abort
from flask import current_app
from flask import make_response
@ -81,3 +84,31 @@ class TableForm(I18NFormMixin, FlaskForm):
def validate_page(self, field):
if field.data < 1 or field.data > self.page_max:
raise wtforms.validators.ValidationError(_("The page number is not valid"))
class DateTimeUTCField(wtforms.DateTimeLocalField):
def _value(self):
if not self.data:
return ""
user_timezone = timezone_selector()
locale_dt = self.data.astimezone(user_timezone)
return locale_dt.strftime(self.format[0])
def process_formdata(self, valuelist):
if not valuelist:
return
date_str = " ".join(valuelist)
user_timezone = timezone_selector()
for format in self.strptime_format:
try:
unaware_dt = datetime.datetime.strptime(date_str, format)
locale_dt = user_timezone.localize(unaware_dt)
utc_dt = locale_dt.astimezone(pytz.utc)
self.data = utc_dt
return
except ValueError:
self.data = None
raise ValueError(self.gettext("Not a valid datetime value."))

View file

@ -1,6 +1,8 @@
import gettext
import pycountry
import pytz
from babel.dates import LOCALTZ
from flask import current_app
from flask import g
from flask import request
@ -13,7 +15,9 @@ babel = Babel()
def setup_i18n(app):
babel.init_app(app, locale_selector=locale_selector)
babel.init_app(
app, locale_selector=locale_selector, timezone_selector=timezone_selector
)
@app.before_request
def before_request():
@ -40,6 +44,13 @@ def locale_selector():
return request.accept_languages.best_match(available_language_codes)
def timezone_selector():
try:
return pytz.timezone(current_app.config.get("TIMEZONE"))
except pytz.exceptions.UnknownTimeZoneError:
return LOCALTZ
def native_language_name_from_code(code):
language = pycountry.languages.get(alpha_2=code[:2])
if code == DEFAULT_LANGUAGE_CODE:

View file

@ -127,6 +127,12 @@ class LDAPBackend(Backend):
)
def teardown(self):
try: # pragma: no cover
if request.endpoint == "static":
return
except RuntimeError: # pragma: no cover
pass
if self.connection: # pragma: no branch
self.connection.unbind_s()
self.connection = None

View file

@ -25,6 +25,10 @@ SECRET_KEY = "change me before you go in production"
# 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"

View file

@ -425,7 +425,7 @@ def registration(data, hash):
else:
user = profile_create(current_app, form)
user.login()
flash(_("Your account has been created successfuly."), "success")
flash(_("Your account has been created successfully."), "success")
return redirect(
url_for("account.profile_edition", username=user.user_name[0])
)
@ -574,7 +574,7 @@ def profile_edition(user, username):
user.preferred_language = None
user.save()
flash(_("Profile updated successfuly."), "success")
flash(_("Profile updated successfully."), "success")
return redirect(url_for("account.profile_edition", username=username))
return render_template(
@ -696,7 +696,7 @@ def profile_settings_edit(editor, edited_user):
edited_user.set_password(form["password1"].data)
edited_user.save()
flash(_("Profile updated successfuly."), "success")
flash(_("Profile updated successfully."), "success")
return redirect(
url_for("account.profile_settings", username=edited_user.user_name[0])
)
@ -816,7 +816,7 @@ def reset(user_name, hash):
user.set_password(form.password.data)
user.login()
flash(_("Your password has been updated successfuly"), "success")
flash(_("Your password has been updated successfully"), "success")
return redirect(url_for("account.profile_edition", username=user_name))
return render_template(

View file

@ -1,5 +1,6 @@
import wtforms.form
from canaille.app import models
from canaille.app.forms import DateTimeUTCField
from canaille.app.forms import HTMXBaseForm
from canaille.app.forms import HTMXForm
from canaille.app.forms import is_uri
@ -286,7 +287,7 @@ def profile_form(write_field_names, readonly_field_names, user=None):
del fields["groups"]
if current_app.backend.get().has_account_lockability(): # pragma: no branch
fields["lock_date"] = wtforms.DateTimeLocalField(
fields["lock_date"] = DateTimeUTCField(
_("Account expiration"),
validators=[wtforms.validators.Optional()],
format=[

View file

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2023-05-26 15:31+0200\n"
"PO-Revision-Date: 2023-05-14 23:49+0000\n"
"POT-Creation-Date: 2023-05-30 09:43+0200\n"
"PO-Revision-Date: 2023-05-29 15:50+0000\n"
"Last-Translator: Ettore Atalan <atalanttore@googlemail.com>\n"
"Language-Team: German <https://hosted.weblate.org/projects/canaille/canaille/"
"de/>\n"
@ -22,15 +22,15 @@ msgstr ""
#: canaille/app/flask.py:65
msgid "No SMTP server has been configured"
msgstr ""
msgstr "Es wurde kein SMTP-Server konfiguriert"
#: canaille/app/forms.py:20
msgid "This is not a valid URL"
msgstr ""
msgstr "Dies ist keine gültige URL"
#: canaille/app/forms.py:83
msgid "The page number is not valid"
msgstr ""
msgstr "Die Seitenzahl ist nicht gültig"
#: canaille/backends/ldap/backend.py:98
msgid "Could not connect to the LDAP server '{uri}'"
@ -42,30 +42,28 @@ msgstr ""
#: canaille/backends/ldap/backend.py:214
msgid "John Doe"
msgstr ""
msgstr "Max Mustermann"
#: canaille/backends/ldap/backend.py:217 canaille/core/forms.py:124
#: canaille/core/forms.py:340
#: canaille/core/forms.py:341
msgid "jdoe"
msgstr ""
msgstr "mmustermann"
#: canaille/backends/ldap/backend.py:220
msgid "john@doe.com"
msgstr ""
msgstr "max@mustermann.de"
#: canaille/backends/ldap/backend.py:222
msgid " or "
msgstr ""
msgstr " oder "
#: canaille/backends/ldap/models.py:121
#, fuzzy
#| msgid "Your account has already been created."
msgid "Your account has been locked."
msgstr "Ihr Konto wurde bereits erstellt."
msgstr "Ihr Konto wurde gesperrt."
#: canaille/backends/ldap/models.py:126
msgid "You should change your password."
msgstr ""
msgstr "Sie sollten Ihr Passwort ändern."
#: canaille/core/account.py:97 canaille/core/account.py:120
#: canaille/core/account.py:128 canaille/oidc/endpoints.py:81
@ -73,7 +71,7 @@ msgstr ""
msgid "Login failed, please check your information"
msgstr "Anmeldung fehlgeschlagen, bitte überprüfen Sie Ihre Angaben"
#: canaille/core/account.py:136 canaille/core/account.py:667
#: canaille/core/account.py:136 canaille/core/account.py:660
#, python-format
msgid "Connection successful. Welcome %(user)s"
msgstr "Verbindung erfolgreich. Willkommen %(user)s"
@ -112,19 +110,21 @@ msgid "You are already logged in, you cannot create an account."
msgstr ""
#: canaille/core/account.py:319 canaille/core/forms.py:263
#: canaille/core/forms.py:358 canaille/templates/groups.html:5
#: canaille/core/forms.py:359 canaille/templates/groups.html:5
#: canaille/templates/groups.html:25 canaille/templates/partial/users.html:18
#: canaille/themes/default/base.html:61
msgid "Groups"
msgstr ""
msgstr "Gruppen"
#: canaille/core/account.py:341 canaille/core/account.py:372
msgid "User account creation failed."
msgstr ""
msgstr "Erstellung des Benutzerkontos fehlgeschlagen."
#: canaille/core/account.py:346
msgid "Your account has been created successfuly."
msgstr ""
#, fuzzy
#| msgid "Your account has been created successfuly."
msgid "Your account has been created successfully."
msgstr "Ihr Konto wurde erfolgreich erstellt."
#: canaille/core/account.py:412
msgid "User account creation succeed."
@ -134,9 +134,11 @@ msgstr ""
msgid "Profile edition failed."
msgstr ""
#: canaille/core/account.py:495 canaille/core/account.py:624
msgid "Profile updated successfuly."
msgstr ""
#: canaille/core/account.py:495 canaille/core/account.py:617
#, fuzzy
#| msgid "Your password has been updated successfuly"
msgid "Profile updated successfully."
msgstr "Ihr Passwort wurde erfolgreich aktualisiert"
#: canaille/core/account.py:531
msgid ""
@ -159,25 +161,23 @@ msgid "Could not send the password reset email"
msgstr "Die E-Mail zum Zurücksetzen des Passworts konnte nicht gesendet werden"
#: canaille/core/account.py:559
#, fuzzy
msgid "The account has been locked"
msgstr "Der Kunde wurde bearbeitet."
msgstr "Das Konto wurde gesperrt"
#: canaille/core/account.py:570
#, fuzzy
msgid "The account has been unlocked"
msgstr "Der Kunde wurde bearbeitet."
msgstr "Das Konto wurde entsperrt"
#: canaille/core/account.py:644
#: canaille/core/account.py:637
#, python-format
msgid "The user %(user)s has been sucessfuly deleted"
msgstr ""
msgstr "Der Benutzer %(user)s wurde erfolgreich gelöscht"
#: canaille/core/account.py:684
#: canaille/core/account.py:677
msgid "Could not send the password reset link."
msgstr ""
#: canaille/core/account.py:688
#: canaille/core/account.py:681
msgid ""
"A password reset link has been sent at your email address. You should "
"receive it within a few minutes."
@ -185,7 +185,7 @@ msgstr ""
"Ein Link zum Zurücksetzen des Passworts wurde an Ihre E-Mail-Adresse "
"gesendet. Sie sollten ihn innerhalb weniger Minuten erhalten."
#: canaille/core/account.py:699
#: canaille/core/account.py:692
#, python-format
msgid ""
"The user '%(user)s' does not have permissions to update their password. We "
@ -194,19 +194,21 @@ msgstr ""
"Der Benutzer „%(user)s“ hat nicht die Berechtigung, sein Passwort zu "
"aktualisieren. Wir können keine E-Mail zum Zurücksetzen des Passworts senden."
#: canaille/core/account.py:714
#: canaille/core/account.py:707
msgid "We encountered an issue while we sent the password recovery email."
msgstr ""
"Beim Versand der E-Mail zur Wiederherstellung des Passworts ist ein Problem "
"aufgetreten."
#: canaille/core/account.py:735
#: canaille/core/account.py:728
msgid "The password reset link that brought you here was invalid."
msgstr ""
#: canaille/core/account.py:744
msgid "Your password has been updated successfuly"
msgstr ""
#: canaille/core/account.py:737
#, fuzzy
#| msgid "Your password has been updated successfuly"
msgid "Your password has been updated successfully"
msgstr "Ihr Passwort wurde erfolgreich aktualisiert"
#: canaille/core/admin.py:23 canaille/templates/partial/users.html:15
msgid "Email"
@ -214,7 +216,7 @@ msgstr "E-Mail"
#: canaille/core/admin.py:29 canaille/core/forms.py:56
#: canaille/core/forms.py:80 canaille/core/forms.py:168
#: canaille/core/forms.py:352
#: canaille/core/forms.py:353
msgid "jane@doe.com"
msgstr ""
@ -228,7 +230,7 @@ msgstr "Die Testeinladungs-E-Mail wurde nicht korrekt gesendet"
#: canaille/core/admin.py:89 canaille/core/mails.py:92
msgid "Password initialization on {website_name}"
msgstr ""
msgstr "Passwortinitialisierung auf {website_name}"
#: canaille/core/admin.py:131 canaille/core/mails.py:52
msgid "Password reset on {website_name}"
@ -259,16 +261,16 @@ msgstr ""
#: canaille/core/forms.py:53 canaille/core/forms.py:77
#: canaille/templates/partial/users.html:9
msgid "Login"
msgstr ""
msgstr "Anmeldung"
#: canaille/core/forms.py:66 canaille/core/forms.py:89
#: canaille/core/forms.py:213
msgid "Password"
msgstr ""
msgstr "Passwort"
#: canaille/core/forms.py:96 canaille/core/forms.py:223
msgid "Password confirmation"
msgstr ""
msgstr "Passwortbestätigung"
#: canaille/core/forms.py:99 canaille/core/forms.py:226
msgid "Password and confirmation do not match."
@ -278,12 +280,12 @@ msgstr "Passwort und Bestätigung stimmen nicht überein."
msgid "Automatic"
msgstr "Automatisch"
#: canaille/core/forms.py:123 canaille/core/forms.py:339
#: canaille/core/forms.py:123 canaille/core/forms.py:340
msgid "Username"
msgstr "Anmeldename"
#: canaille/core/forms.py:127 canaille/core/forms.py:311
#: canaille/core/forms.py:325 canaille/oidc/forms.py:22
#: canaille/core/forms.py:127 canaille/core/forms.py:312
#: canaille/core/forms.py:326 canaille/oidc/forms.py:22
#: canaille/templates/partial/groups.html:6
#: canaille/templates/partial/oidc/admin/client_list.html:6
#: canaille/templates/partial/users.html:12
@ -292,11 +294,11 @@ msgstr "Name"
#: canaille/core/forms.py:129
msgid "Title"
msgstr ""
msgstr "Titel"
#: canaille/core/forms.py:129
msgid "Vice president"
msgstr ""
msgstr "Vizepräsident"
#: canaille/core/forms.py:132
msgid "Given name"
@ -322,7 +324,7 @@ msgstr "Anzeigename"
msgid "Johnny"
msgstr "Maria"
#: canaille/core/forms.py:158 canaille/core/forms.py:345
#: canaille/core/forms.py:158 canaille/core/forms.py:346
msgid "Email address"
msgstr "E-Mail-Adresse"
@ -352,16 +354,15 @@ msgstr "Beispielstraße 132, 11111 Musterstadt"
#: canaille/core/forms.py:183
msgid "Street"
msgstr ""
msgstr "Straße"
#: canaille/core/forms.py:185
#, fuzzy
msgid "132, Foobar Street"
msgstr "Beispielstraße 132, 11111 Musterstadt"
msgstr "Beispielstraße 132"
#: canaille/core/forms.py:189
msgid "Postal Code"
msgstr ""
msgstr "Postleitzahl"
#: canaille/core/forms.py:195
msgid "Locality"
@ -373,11 +374,11 @@ msgstr ""
#: canaille/core/forms.py:201
msgid "Region"
msgstr ""
msgstr "Region"
#: canaille/core/forms.py:203
msgid "North Pole"
msgstr ""
msgstr "Nordpol"
#: canaille/core/forms.py:207
msgid "Photo"
@ -389,23 +390,20 @@ msgid "Delete the photo"
msgstr "Foto löschen"
#: canaille/core/forms.py:234
#, fuzzy
#| msgid "Username"
msgid "User number"
msgstr "Anmeldename"
msgstr "Benutzernummer"
#: canaille/core/forms.py:236 canaille/core/forms.py:242
msgid "1234"
msgstr "1234"
#: canaille/core/forms.py:240
#, fuzzy
msgid "Department"
msgstr "Telefonnummer"
msgstr "Abteilung"
#: canaille/core/forms.py:246
msgid "Organization"
msgstr ""
msgstr "Organisation"
#: canaille/core/forms.py:248
msgid "Cogip LTD."
@ -428,20 +426,20 @@ msgstr "Bevorzugte Sprache"
msgid "users, admins …"
msgstr "Benutzer/innen, Administrator/innen …"
#: canaille/core/forms.py:289
#: canaille/core/forms.py:290
msgid "Account expiration"
msgstr ""
#: canaille/core/forms.py:314
#: canaille/core/forms.py:315
msgid "group"
msgstr "Gruppe"
#: canaille/core/forms.py:318 canaille/core/forms.py:332
#: canaille/core/forms.py:319 canaille/core/forms.py:333
#: canaille/templates/partial/groups.html:7
msgid "Description"
msgstr "Beschreibung"
#: canaille/core/forms.py:343
#: canaille/core/forms.py:344
msgid "Username editable by the invitee"
msgstr "Vom Eingeladenen bearbeitbarer Benutzername"
@ -452,7 +450,7 @@ msgstr "Gruppenerstellung fehlgeschlagen."
#: canaille/core/groups.py:45
#, python-format
msgid "The group %(group)s has been sucessfully created"
msgstr ""
msgstr "Die Gruppe %(group)s wurde erfolgreich erstellt"
#: canaille/core/groups.py:97
#, python-format

View file

@ -7,9 +7,9 @@
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2023-05-26 15:31+0200\n"
"PO-Revision-Date: 2023-03-31 13:40+0000\n"
"Report-Msgid-Bugs-To: contact@yaal.coop\n"
"POT-Creation-Date: 2023-05-30 09:43+0200\n"
"PO-Revision-Date: 2023-05-31 19:28+0000\n"
"Last-Translator: gallegonovato <fran-carro@hotmail.es>\n"
"Language-Team: Spanish <https://hosted.weblate.org/projects/canaille/"
"canaille/es/>\n"
@ -18,7 +18,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.17-dev\n"
"X-Generator: Weblate 4.18-dev\n"
"Generated-By: Babel 2.12.1\n"
#: canaille/app/flask.py:65
@ -46,7 +46,7 @@ msgid "John Doe"
msgstr "John Doe"
#: canaille/backends/ldap/backend.py:217 canaille/core/forms.py:124
#: canaille/core/forms.py:340
#: canaille/core/forms.py:341
msgid "jdoe"
msgstr "jdoe"
@ -59,14 +59,12 @@ msgid " or "
msgstr " o "
#: canaille/backends/ldap/models.py:121
#, fuzzy
#| msgid "Your account has already been created."
msgid "Your account has been locked."
msgstr "Tu cuenta ya existe."
msgstr "Tu cuenta ha sido bloqueada."
#: canaille/backends/ldap/models.py:126
msgid "You should change your password."
msgstr ""
msgstr "Deberías cambiar tu contraseña."
#: canaille/core/account.py:97 canaille/core/account.py:120
#: canaille/core/account.py:128 canaille/oidc/endpoints.py:81
@ -74,7 +72,7 @@ msgstr ""
msgid "Login failed, please check your information"
msgstr "Falló el inicio de sesión, comprueba tus credenciales"
#: canaille/core/account.py:136 canaille/core/account.py:667
#: canaille/core/account.py:136 canaille/core/account.py:660
#, python-format
msgid "Connection successful. Welcome %(user)s"
msgstr "Acceso correcto. Bienvenido/a %(user)s"
@ -113,7 +111,7 @@ msgid "You are already logged in, you cannot create an account."
msgstr "Ya has iniciado sesión, no puedes crear una cuenta."
#: canaille/core/account.py:319 canaille/core/forms.py:263
#: canaille/core/forms.py:358 canaille/templates/groups.html:5
#: canaille/core/forms.py:359 canaille/templates/groups.html:5
#: canaille/templates/groups.html:25 canaille/templates/partial/users.html:18
#: canaille/themes/default/base.html:61
msgid "Groups"
@ -124,7 +122,7 @@ msgid "User account creation failed."
msgstr "Falló la creación de la cuenta de usuario."
#: canaille/core/account.py:346
msgid "Your account has been created successfuly."
msgid "Your account has been created successfully."
msgstr "Tu cuenta se ha creado correctamente."
#: canaille/core/account.py:412
@ -135,8 +133,8 @@ msgstr "Cuenta creada correctamente."
msgid "Profile edition failed."
msgstr "Falló la actualización del perfil."
#: canaille/core/account.py:495 canaille/core/account.py:624
msgid "Profile updated successfuly."
#: canaille/core/account.py:495 canaille/core/account.py:617
msgid "Profile updated successfully."
msgstr "Perfil actualizado correctamente."
#: canaille/core/account.py:531
@ -160,27 +158,23 @@ msgid "Could not send the password reset email"
msgstr "No fue posible enviar el email para restablecer de contraseña"
#: canaille/core/account.py:559
#, fuzzy
#| msgid "The access has been revoked"
msgid "The account has been locked"
msgstr "El acceso ha sido revocado"
msgstr "La cuenta ha sido bloqueada"
#: canaille/core/account.py:570
#, fuzzy
#| msgid "The access has been revoked"
msgid "The account has been unlocked"
msgstr "El acceso ha sido revocado"
msgstr "La cuenta ha sido desbloqueada"
#: canaille/core/account.py:644
#: canaille/core/account.py:637
#, python-format
msgid "The user %(user)s has been sucessfuly deleted"
msgstr "El usuario %(user)s ha sido eliminado correctamente"
#: canaille/core/account.py:684
#: canaille/core/account.py:677
msgid "Could not send the password reset link."
msgstr "No fue posible enviar el email para restablecer de contraseña."
#: canaille/core/account.py:688
#: canaille/core/account.py:681
msgid ""
"A password reset link has been sent at your email address. You should "
"receive it within a few minutes."
@ -188,7 +182,7 @@ msgstr ""
"Se ha enviado un enlace para restablecer la contraseña a tu correo "
"electrónico. Deberías recibirlo en algunos minutos."
#: canaille/core/account.py:699
#: canaille/core/account.py:692
#, python-format
msgid ""
"The user '%(user)s' does not have permissions to update their password. We "
@ -197,18 +191,18 @@ msgstr ""
"El usuario '%(user)s' no tiene permiso para actualizar su contraseña. No es "
"posible enviarle un email para que restablezca su contraseña."
#: canaille/core/account.py:714
#: canaille/core/account.py:707
msgid "We encountered an issue while we sent the password recovery email."
msgstr "Se produjo un problema enviando el email para recuperar la contraseña."
#: canaille/core/account.py:735
#: canaille/core/account.py:728
msgid "The password reset link that brought you here was invalid."
msgstr ""
"El enlace para restablecer la contraseña con el que has accedido no es "
"válido."
#: canaille/core/account.py:744
msgid "Your password has been updated successfuly"
#: canaille/core/account.py:737
msgid "Your password has been updated successfully"
msgstr "Tu contraseña se ha actualizado correctamente"
#: canaille/core/admin.py:23 canaille/templates/partial/users.html:15
@ -217,7 +211,7 @@ msgstr "Correo electrónico"
#: canaille/core/admin.py:29 canaille/core/forms.py:56
#: canaille/core/forms.py:80 canaille/core/forms.py:168
#: canaille/core/forms.py:352
#: canaille/core/forms.py:353
msgid "jane@doe.com"
msgstr "jane@doe.com"
@ -279,12 +273,12 @@ msgstr "Las dos contraseñas no coinciden."
msgid "Automatic"
msgstr "Automático"
#: canaille/core/forms.py:123 canaille/core/forms.py:339
#: canaille/core/forms.py:123 canaille/core/forms.py:340
msgid "Username"
msgstr "Nombre de usuario"
#: canaille/core/forms.py:127 canaille/core/forms.py:311
#: canaille/core/forms.py:325 canaille/oidc/forms.py:22
#: canaille/core/forms.py:127 canaille/core/forms.py:312
#: canaille/core/forms.py:326 canaille/oidc/forms.py:22
#: canaille/templates/partial/groups.html:6
#: canaille/templates/partial/oidc/admin/client_list.html:6
#: canaille/templates/partial/users.html:12
@ -323,7 +317,7 @@ msgstr "Nombre a mostrar"
msgid "Johnny"
msgstr "Johnny"
#: canaille/core/forms.py:158 canaille/core/forms.py:345
#: canaille/core/forms.py:158 canaille/core/forms.py:346
msgid "Email address"
msgstr "Correo electrónico"
@ -396,10 +390,8 @@ msgid "1234"
msgstr "1234"
#: canaille/core/forms.py:240
#, fuzzy
#| msgid "Department number"
msgid "Department"
msgstr "Número del departamento"
msgstr "Departamento"
#: canaille/core/forms.py:246
msgid "Organization"
@ -425,22 +417,20 @@ msgstr "Idioma preferido"
msgid "users, admins …"
msgstr "usuarios, administradores…"
#: canaille/core/forms.py:289
#, fuzzy
#| msgid "Account creation"
#: canaille/core/forms.py:290
msgid "Account expiration"
msgstr "Crear cuenta"
msgstr "Caducidad de la cuenta"
#: canaille/core/forms.py:314
#: canaille/core/forms.py:315
msgid "group"
msgstr "grupo"
#: canaille/core/forms.py:318 canaille/core/forms.py:332
#: canaille/core/forms.py:319 canaille/core/forms.py:333
#: canaille/templates/partial/groups.html:7
msgid "Description"
msgstr "Descripción"
#: canaille/core/forms.py:343
#: canaille/core/forms.py:344
msgid "Username editable by the invitee"
msgstr "Nombre de usuario editable por el invitado"
@ -1073,36 +1063,28 @@ msgid "Submit"
msgstr "Enviar"
#: canaille/templates/profile_settings.html:70
#, fuzzy
#| msgid "Account deletion"
msgid "Account locking"
msgstr "Eliminar cuenta"
msgstr "Bloqueo de la cuenta"
#: canaille/templates/profile_settings.html:75
#, fuzzy
#| msgid ""
#| "Are you sure you want to revoke this token? This action is unrevokable."
msgid ""
"Are you sure you want to lock this account? The user won't be able to login "
"until their account is unlocked."
msgstr ""
"¿Está seguro de que desea revocar este token? Esta acción es irrevocable."
"¿Está seguro de que deseas bloquear esta cuenta? El usuario no podrá iniciar "
"sesión hasta que la cuenta esté desbloqueada."
#: canaille/templates/profile_settings.html:77
#, fuzzy
#| msgid ""
#| "Are you sure you want to delete your account? This action is unrevokable "
#| "and all your data will be removed forever."
msgid ""
"Are you sure you want to lock your account? You won't be abel to login until "
"your account is unlocked."
msgstr ""
"¿Seguro que quieres eliminar tu cuenta? Esta acción no se puede deshacer y "
"todos tus datos se eliminarán para siempre."
"¿Estás seguro de que desea bloquear tu cuenta? No podrás iniciar sesión "
"hasta que tu cuenta esté desbloqueada."
#: canaille/templates/profile_settings.html:83
msgid "Lock"
msgstr ""
msgstr "Bloquear"
#: canaille/templates/profile_settings.html:124
msgid "Send email"
@ -1153,27 +1135,19 @@ msgstr ""
#: canaille/templates/profile_settings.html:160
msgid "Unlock"
msgstr ""
msgstr "Desbloquear"
#: canaille/templates/profile_settings.html:163
#, fuzzy
#| msgid "This user cannot see this field"
msgid "This user account is locked"
msgstr "Este usuario no puede ver este campo"
msgstr "Esta cuenta de usuario está bloqueada"
#: canaille/templates/profile_settings.html:166
#, fuzzy
#| msgid ""
#| "The user will not be able to authenticate unless the password is set."
msgid "The user won't be able to connect until their account is unlocked."
msgstr ""
"El usuario no podrá iniciar sesión a menos que se establezca una contraeña."
msgstr "El usuario no podrá conectarse hasta que la cuenta esté desbloqueada."
#: canaille/templates/profile_settings.html:178
#, fuzzy
#| msgid "Delete my account"
msgid "Lock the account"
msgstr "Eliminar mi cuenta"
msgstr "Bloquear la cuenta"
#: canaille/templates/profile_settings.html:185
msgid "Delete the user"
@ -1420,10 +1394,8 @@ msgid "View an authorization"
msgstr "Ver una autorización"
#: canaille/templates/oidc/admin/client_add.html:39
#, fuzzy
#| msgid "Created"
msgid "Create"
msgstr "Creado"
msgstr "Crear"
#: canaille/templates/oidc/admin/client_edit.html:5
#: canaille/templates/oidc/admin/client_edit.html:54
@ -1665,7 +1637,7 @@ msgstr "¿Quizás probando con otros criterios?"
#: canaille/templates/partial/users.html:29
msgid "This account is locked"
msgstr ""
msgstr "Esta cuenta está bloqueada"
#: canaille/templates/partial/oidc/admin/authorization_list.html:5
msgid "Code"

View file

@ -9,8 +9,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: contact@yaal.coop\n"
"POT-Creation-Date: 2023-05-26 15:31+0200\n"
"PO-Revision-Date: 2023-05-27 14:50+0000\n"
"POT-Creation-Date: 2023-05-30 09:43+0200\n"
"PO-Revision-Date: 2023-05-31 19:28+0000\n"
"Last-Translator: Éloi Rivard <eloi.rivard@nubla.fr>\n"
"Language-Team: French <https://hosted.weblate.org/projects/canaille/canaille/"
"fr/>\n"
@ -48,7 +48,7 @@ msgid "John Doe"
msgstr "Camille Dupont"
#: canaille/backends/ldap/backend.py:217 canaille/core/forms.py:124
#: canaille/core/forms.py:340
#: canaille/core/forms.py:341
msgid "jdoe"
msgstr "cdupont"
@ -74,7 +74,7 @@ msgstr "Vous devriez changer votre mot de passe."
msgid "Login failed, please check your information"
msgstr "La connexion a échoué, veuillez vérifier vos informations"
#: canaille/core/account.py:136 canaille/core/account.py:667
#: canaille/core/account.py:136 canaille/core/account.py:660
#, python-format
msgid "Connection successful. Welcome %(user)s"
msgstr "Connexion réussie. Bienvenue %(user)s"
@ -113,7 +113,7 @@ msgid "You are already logged in, you cannot create an account."
msgstr "Vous êtes déjà connectés, vous ne pouvez pas créer de compte."
#: canaille/core/account.py:319 canaille/core/forms.py:263
#: canaille/core/forms.py:358 canaille/templates/groups.html:5
#: canaille/core/forms.py:359 canaille/templates/groups.html:5
#: canaille/templates/groups.html:25 canaille/templates/partial/users.html:18
#: canaille/themes/default/base.html:61
msgid "Groups"
@ -124,7 +124,7 @@ msgid "User account creation failed."
msgstr "La création du compte utilisateur a échoué."
#: canaille/core/account.py:346
msgid "Your account has been created successfuly."
msgid "Your account has been created successfully."
msgstr "Votre compte utilisateur a été créé avec succès."
#: canaille/core/account.py:412
@ -135,8 +135,8 @@ msgstr "La création du compte utilisateur a réussi."
msgid "Profile edition failed."
msgstr "L'édition du profil a échoué."
#: canaille/core/account.py:495 canaille/core/account.py:624
msgid "Profile updated successfuly."
#: canaille/core/account.py:495 canaille/core/account.py:617
msgid "Profile updated successfully."
msgstr "Le profil a été mis à jour avec succès."
#: canaille/core/account.py:531
@ -167,16 +167,16 @@ msgstr "Le compte a été verrouillé"
msgid "The account has been unlocked"
msgstr "Le compte a été déverrouillé"
#: canaille/core/account.py:644
#: canaille/core/account.py:637
#, python-format
msgid "The user %(user)s has been sucessfuly deleted"
msgstr "L'utilisateur %(user)s a bien été supprimé"
#: canaille/core/account.py:684
#: canaille/core/account.py:677
msgid "Could not send the password reset link."
msgstr "Impossible d'envoyer le lien de réinitialisation."
#: canaille/core/account.py:688
#: canaille/core/account.py:681
msgid ""
"A password reset link has been sent at your email address. You should "
"receive it within a few minutes."
@ -184,7 +184,7 @@ msgstr ""
"Un lien de ré-initialisation de votre mot de passe vous a été envoyé par "
"courriel. Vous devriez le recevoir d'ici quelques minutes."
#: canaille/core/account.py:699
#: canaille/core/account.py:692
#, python-format
msgid ""
"The user '%(user)s' does not have permissions to update their password. We "
@ -193,18 +193,18 @@ msgstr ""
"L'utilisateur '%(user)s' n'a pas la permission d'éditer son mot de passe. "
"Nous ne pouvons pas lui envoyer un courriel de réinitialisation."
#: canaille/core/account.py:714
#: canaille/core/account.py:707
msgid "We encountered an issue while we sent the password recovery email."
msgstr ""
"Nous avons rencontré un problème lors de l'envoi du courriel de "
"réinitialisation de mot de passe."
#: canaille/core/account.py:735
#: canaille/core/account.py:728
msgid "The password reset link that brought you here was invalid."
msgstr "Le lien de réinitialisation qui vous a amené ici est invalide."
#: canaille/core/account.py:744
msgid "Your password has been updated successfuly"
#: canaille/core/account.py:737
msgid "Your password has been updated successfully"
msgstr "Votre mot de passe a correctement été mis à jour"
#: canaille/core/admin.py:23 canaille/templates/partial/users.html:15
@ -213,7 +213,7 @@ msgstr "Courriel"
#: canaille/core/admin.py:29 canaille/core/forms.py:56
#: canaille/core/forms.py:80 canaille/core/forms.py:168
#: canaille/core/forms.py:352
#: canaille/core/forms.py:353
msgid "jane@doe.com"
msgstr "camille@dupont.fr"
@ -275,12 +275,12 @@ msgstr "Le mot de passe et sa confirmation ne correspondent pas."
msgid "Automatic"
msgstr "Automatique"
#: canaille/core/forms.py:123 canaille/core/forms.py:339
#: canaille/core/forms.py:123 canaille/core/forms.py:340
msgid "Username"
msgstr "Identifiant"
#: canaille/core/forms.py:127 canaille/core/forms.py:311
#: canaille/core/forms.py:325 canaille/oidc/forms.py:22
#: canaille/core/forms.py:127 canaille/core/forms.py:312
#: canaille/core/forms.py:326 canaille/oidc/forms.py:22
#: canaille/templates/partial/groups.html:6
#: canaille/templates/partial/oidc/admin/client_list.html:6
#: canaille/templates/partial/users.html:12
@ -319,7 +319,7 @@ msgstr "Nom d'affichage"
msgid "Johnny"
msgstr "Martin.D"
#: canaille/core/forms.py:158 canaille/core/forms.py:345
#: canaille/core/forms.py:158 canaille/core/forms.py:346
msgid "Email address"
msgstr "Courriel"
@ -419,20 +419,20 @@ msgstr "Langue préférée"
msgid "users, admins …"
msgstr "utilisateurs, administrateurs …"
#: canaille/core/forms.py:289
#: canaille/core/forms.py:290
msgid "Account expiration"
msgstr "Expiration du compte"
#: canaille/core/forms.py:314
#: canaille/core/forms.py:315
msgid "group"
msgstr "groupe"
#: canaille/core/forms.py:318 canaille/core/forms.py:332
#: canaille/core/forms.py:319 canaille/core/forms.py:333
#: canaille/templates/partial/groups.html:7
msgid "Description"
msgstr "Description"
#: canaille/core/forms.py:343
#: canaille/core/forms.py:344
msgid "Username editable by the invitee"
msgstr "L'identifiant sera éditable par la personne invitée"

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2023-05-26 15:31+0200\n"
"POT-Creation-Date: 2023-05-30 09:43+0200\n"
"PO-Revision-Date: 2023-02-19 22:35+0000\n"
"Last-Translator: Jesús P Rey <i18n@chuso.net>\n"
"Language-Team: Galician <https://hosted.weblate.org/projects/canaille/"
@ -45,7 +45,7 @@ msgid "John Doe"
msgstr "John Doe"
#: canaille/backends/ldap/backend.py:217 canaille/core/forms.py:124
#: canaille/core/forms.py:340
#: canaille/core/forms.py:341
msgid "jdoe"
msgstr "jdoe"
@ -73,7 +73,7 @@ msgstr ""
msgid "Login failed, please check your information"
msgstr "Fallou o inicio de sesión, comproba as túas credenciais"
#: canaille/core/account.py:136 canaille/core/account.py:667
#: canaille/core/account.py:136 canaille/core/account.py:660
#, python-format
msgid "Connection successful. Welcome %(user)s"
msgstr "Acceso correcto. Benvido/a %(user)s"
@ -112,7 +112,7 @@ msgid "You are already logged in, you cannot create an account."
msgstr "Xa iniciaches sesión, non podes crear unha conta."
#: canaille/core/account.py:319 canaille/core/forms.py:263
#: canaille/core/forms.py:358 canaille/templates/groups.html:5
#: canaille/core/forms.py:359 canaille/templates/groups.html:5
#: canaille/templates/groups.html:25 canaille/templates/partial/users.html:18
#: canaille/themes/default/base.html:61
msgid "Groups"
@ -125,7 +125,7 @@ msgstr "Fallou a creación da conta de usuario."
#: canaille/core/account.py:346
#, fuzzy
#| msgid "You account has been created successfuly."
msgid "Your account has been created successfuly."
msgid "Your account has been created successfully."
msgstr "A túa conta creouse correctamente."
#: canaille/core/account.py:412
@ -136,8 +136,10 @@ msgstr "Conta creada correctamente."
msgid "Profile edition failed."
msgstr "Fallou a actualización do perfil."
#: canaille/core/account.py:495 canaille/core/account.py:624
msgid "Profile updated successfuly."
#: canaille/core/account.py:495 canaille/core/account.py:617
#, fuzzy
#| msgid "Profile updated successfuly."
msgid "Profile updated successfully."
msgstr "Perfil actualizado correctamente."
#: canaille/core/account.py:531
@ -172,16 +174,16 @@ msgstr "O acceso foi revogado"
msgid "The account has been unlocked"
msgstr "O acceso foi revogado"
#: canaille/core/account.py:644
#: canaille/core/account.py:637
#, python-format
msgid "The user %(user)s has been sucessfuly deleted"
msgstr "O usuario %(user)s eliminouse correctamente"
#: canaille/core/account.py:684
#: canaille/core/account.py:677
msgid "Could not send the password reset link."
msgstr "Non foi posible enviar o email para restablecer o contrasinal."
#: canaille/core/account.py:688
#: canaille/core/account.py:681
msgid ""
"A password reset link has been sent at your email address. You should "
"receive it within a few minutes."
@ -189,7 +191,7 @@ msgstr ""
"Enviouse unha ligazón para restablecer o contrasinal ó teu correo "
"electrónico. Deberías recibilo nun prazo de 10 minutos."
#: canaille/core/account.py:699
#: canaille/core/account.py:692
#, python-format
msgid ""
"The user '%(user)s' does not have permissions to update their password. We "
@ -198,17 +200,19 @@ msgstr ""
"O usuario '%(user)s' non ten permiso para actualizar o seu contrasinal. Non "
"é posible enviarlle un email para que restableza o seu contrasinal."
#: canaille/core/account.py:714
#: canaille/core/account.py:707
msgid "We encountered an issue while we sent the password recovery email."
msgstr "Produciuse un problema enviando o email para recuperar o contrasinal."
#: canaille/core/account.py:735
#: canaille/core/account.py:728
msgid "The password reset link that brought you here was invalid."
msgstr ""
"A ligazón para restablecer o contrasinal co que accediches non é válido."
#: canaille/core/account.py:744
msgid "Your password has been updated successfuly"
#: canaille/core/account.py:737
#, fuzzy
#| msgid "Your password has been updated successfuly"
msgid "Your password has been updated successfully"
msgstr "O teu contrasinal actualizouse correctamente"
#: canaille/core/admin.py:23 canaille/templates/partial/users.html:15
@ -217,7 +221,7 @@ msgstr "Correo electrónico"
#: canaille/core/admin.py:29 canaille/core/forms.py:56
#: canaille/core/forms.py:80 canaille/core/forms.py:168
#: canaille/core/forms.py:352
#: canaille/core/forms.py:353
msgid "jane@doe.com"
msgstr "jane@doe.com"
@ -281,12 +285,12 @@ msgstr "Os dous contrasinais non coinciden."
msgid "Automatic"
msgstr "Automático"
#: canaille/core/forms.py:123 canaille/core/forms.py:339
#: canaille/core/forms.py:123 canaille/core/forms.py:340
msgid "Username"
msgstr "Nome de usuario"
#: canaille/core/forms.py:127 canaille/core/forms.py:311
#: canaille/core/forms.py:325 canaille/oidc/forms.py:22
#: canaille/core/forms.py:127 canaille/core/forms.py:312
#: canaille/core/forms.py:326 canaille/oidc/forms.py:22
#: canaille/templates/partial/groups.html:6
#: canaille/templates/partial/oidc/admin/client_list.html:6
#: canaille/templates/partial/users.html:12
@ -325,7 +329,7 @@ msgstr "Nome a amosar"
msgid "Johnny"
msgstr "Johnny"
#: canaille/core/forms.py:158 canaille/core/forms.py:345
#: canaille/core/forms.py:158 canaille/core/forms.py:346
msgid "Email address"
msgstr "Correo electrónico"
@ -433,22 +437,22 @@ msgstr "Idioma preferido"
msgid "users, admins …"
msgstr "usuarios, administradores…"
#: canaille/core/forms.py:289
#: canaille/core/forms.py:290
#, fuzzy
#| msgid "Account creation"
msgid "Account expiration"
msgstr "Creación de conta"
#: canaille/core/forms.py:314
#: canaille/core/forms.py:315
msgid "group"
msgstr "grupo"
#: canaille/core/forms.py:318 canaille/core/forms.py:332
#: canaille/core/forms.py:319 canaille/core/forms.py:333
#: canaille/templates/partial/groups.html:7
msgid "Description"
msgstr "Descripción"
#: canaille/core/forms.py:343
#: canaille/core/forms.py:344
msgid "Username editable by the invitee"
msgstr "Nome de usuario editable polo invitado"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2023-05-26 15:31+0200\n"
"POT-Creation-Date: 2023-05-30 09:43+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -42,7 +42,7 @@ msgid "John Doe"
msgstr ""
#: canaille/backends/ldap/backend.py:217 canaille/core/forms.py:124
#: canaille/core/forms.py:340
#: canaille/core/forms.py:341
msgid "jdoe"
msgstr ""
@ -68,7 +68,7 @@ msgstr ""
msgid "Login failed, please check your information"
msgstr ""
#: canaille/core/account.py:136 canaille/core/account.py:667
#: canaille/core/account.py:136 canaille/core/account.py:660
#, python-format
msgid "Connection successful. Welcome %(user)s"
msgstr ""
@ -105,7 +105,7 @@ msgid "You are already logged in, you cannot create an account."
msgstr ""
#: canaille/core/account.py:319 canaille/core/forms.py:263
#: canaille/core/forms.py:358 canaille/templates/groups.html:5
#: canaille/core/forms.py:359 canaille/templates/groups.html:5
#: canaille/templates/groups.html:25 canaille/templates/partial/users.html:18
#: canaille/themes/default/base.html:61
msgid "Groups"
@ -116,7 +116,7 @@ msgid "User account creation failed."
msgstr ""
#: canaille/core/account.py:346
msgid "Your account has been created successfuly."
msgid "Your account has been created successfully."
msgstr ""
#: canaille/core/account.py:412
@ -127,8 +127,8 @@ msgstr ""
msgid "Profile edition failed."
msgstr ""
#: canaille/core/account.py:495 canaille/core/account.py:624
msgid "Profile updated successfuly."
#: canaille/core/account.py:495 canaille/core/account.py:617
msgid "Profile updated successfully."
msgstr ""
#: canaille/core/account.py:531
@ -155,38 +155,38 @@ msgstr ""
msgid "The account has been unlocked"
msgstr ""
#: canaille/core/account.py:644
#: canaille/core/account.py:637
#, python-format
msgid "The user %(user)s has been sucessfuly deleted"
msgstr ""
#: canaille/core/account.py:684
#: canaille/core/account.py:677
msgid "Could not send the password reset link."
msgstr ""
#: canaille/core/account.py:688
#: canaille/core/account.py:681
msgid ""
"A password reset link has been sent at your email address. You should "
"receive it within a few minutes."
msgstr ""
#: canaille/core/account.py:699
#: canaille/core/account.py:692
#, python-format
msgid ""
"The user '%(user)s' does not have permissions to update their password. "
"We cannot send a password reset email."
msgstr ""
#: canaille/core/account.py:714
#: canaille/core/account.py:707
msgid "We encountered an issue while we sent the password recovery email."
msgstr ""
#: canaille/core/account.py:735
#: canaille/core/account.py:728
msgid "The password reset link that brought you here was invalid."
msgstr ""
#: canaille/core/account.py:744
msgid "Your password has been updated successfuly"
#: canaille/core/account.py:737
msgid "Your password has been updated successfully"
msgstr ""
#: canaille/core/admin.py:23 canaille/templates/partial/users.html:15
@ -195,7 +195,7 @@ msgstr ""
#: canaille/core/admin.py:29 canaille/core/forms.py:56
#: canaille/core/forms.py:80 canaille/core/forms.py:168
#: canaille/core/forms.py:352
#: canaille/core/forms.py:353
msgid "jane@doe.com"
msgstr ""
@ -257,12 +257,12 @@ msgstr ""
msgid "Automatic"
msgstr ""
#: canaille/core/forms.py:123 canaille/core/forms.py:339
#: canaille/core/forms.py:123 canaille/core/forms.py:340
msgid "Username"
msgstr ""
#: canaille/core/forms.py:127 canaille/core/forms.py:311
#: canaille/core/forms.py:325 canaille/oidc/forms.py:22
#: canaille/core/forms.py:127 canaille/core/forms.py:312
#: canaille/core/forms.py:326 canaille/oidc/forms.py:22
#: canaille/templates/partial/groups.html:6
#: canaille/templates/partial/oidc/admin/client_list.html:6
#: canaille/templates/partial/users.html:12
@ -301,7 +301,7 @@ msgstr ""
msgid "Johnny"
msgstr ""
#: canaille/core/forms.py:158 canaille/core/forms.py:345
#: canaille/core/forms.py:158 canaille/core/forms.py:346
msgid "Email address"
msgstr ""
@ -400,20 +400,20 @@ msgstr ""
msgid "users, admins …"
msgstr ""
#: canaille/core/forms.py:289
#: canaille/core/forms.py:290
msgid "Account expiration"
msgstr ""
#: canaille/core/forms.py:314
#: canaille/core/forms.py:315
msgid "group"
msgstr ""
#: canaille/core/forms.py:318 canaille/core/forms.py:332
#: canaille/core/forms.py:319 canaille/core/forms.py:333
#: canaille/templates/partial/groups.html:7
msgid "Description"
msgstr ""
#: canaille/core/forms.py:343
#: canaille/core/forms.py:344
msgid "Username editable by the invitee"
msgstr ""

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2023-05-26 15:31+0200\n"
"POT-Creation-Date: 2023-05-30 09:43+0200\n"
"PO-Revision-Date: 2023-03-24 22:36+0000\n"
"Last-Translator: Sofi <sofi+git@mailbox.org>\n"
"Language-Team: Norwegian Bokmål <https://hosted.weblate.org/projects/"
@ -46,7 +46,7 @@ msgid "John Doe"
msgstr "Ola Nordmann"
#: canaille/backends/ldap/backend.py:217 canaille/core/forms.py:124
#: canaille/core/forms.py:340
#: canaille/core/forms.py:341
msgid "jdoe"
msgstr "onord"
@ -75,7 +75,7 @@ msgstr ""
msgid "Login failed, please check your information"
msgstr "Kunne ikke logge inn. Sjekk at alt er riktig."
#: canaille/core/account.py:136 canaille/core/account.py:667
#: canaille/core/account.py:136 canaille/core/account.py:660
#, fuzzy, python-format
msgid "Connection successful. Welcome %(user)s"
msgstr "Velkommen %(user)s"
@ -114,7 +114,7 @@ msgid "You are already logged in, you cannot create an account."
msgstr "Du er allerede innlogget. Du kan ikke opprette konto."
#: canaille/core/account.py:319 canaille/core/forms.py:263
#: canaille/core/forms.py:358 canaille/templates/groups.html:5
#: canaille/core/forms.py:359 canaille/templates/groups.html:5
#: canaille/templates/groups.html:25 canaille/templates/partial/users.html:18
#: canaille/themes/default/base.html:61
msgid "Groups"
@ -127,7 +127,7 @@ msgstr "Kunne ikke opprette brukerkonto."
#: canaille/core/account.py:346
#, fuzzy
msgid "Your account has been created successfuly."
msgid "Your account has been created successfully."
msgstr "Kontoen din ble opprettet."
#: canaille/core/account.py:412
@ -140,8 +140,10 @@ msgstr "Brukerkonto opprettet."
msgid "Profile edition failed."
msgstr "Kunne ikke redigere profil."
#: canaille/core/account.py:495 canaille/core/account.py:624
msgid "Profile updated successfuly."
#: canaille/core/account.py:495 canaille/core/account.py:617
#, fuzzy
#| msgid "Profile updated successfuly."
msgid "Profile updated successfully."
msgstr "Profil redigert."
#: canaille/core/account.py:531
@ -176,23 +178,23 @@ msgstr "Tilgangen har blitt tilbakekalt"
msgid "The account has been unlocked"
msgstr "Tilgangen har blitt tilbakekalt"
#: canaille/core/account.py:644
#: canaille/core/account.py:637
#, python-format
msgid "The user %(user)s has been sucessfuly deleted"
msgstr "Brukeren %(user)s ble slettet"
#: canaille/core/account.py:684
#: canaille/core/account.py:677
msgid "Could not send the password reset link."
msgstr "Kunne ikke sende lenke for passordgjenoppretting."
#: canaille/core/account.py:688
#: canaille/core/account.py:681
#, fuzzy
msgid ""
"A password reset link has been sent at your email address. You should "
"receive it within a few minutes."
msgstr "Lenke for tilbakestilling av passord sendt til din e-postadresse."
#: canaille/core/account.py:699
#: canaille/core/account.py:692
#, fuzzy, python-format
msgid ""
"The user '%(user)s' does not have permissions to update their password. We "
@ -201,18 +203,18 @@ msgstr ""
"Brukeren «%(user)s» har ikke tilgang til å oppdatere passordet sitt. Kan "
"ikke sende e-post om tilbakestilling av passord."
#: canaille/core/account.py:714
#: canaille/core/account.py:707
#, fuzzy
msgid "We encountered an issue while we sent the password recovery email."
msgstr "Kunne ikke sende e-post for gjenoppretting av passord."
#: canaille/core/account.py:735
#: canaille/core/account.py:728
msgid "The password reset link that brought you here was invalid."
msgstr "Passordtilbakestillingslenken som tok deg hit er ikke gyldig."
#: canaille/core/account.py:744
#: canaille/core/account.py:737
#, fuzzy
msgid "Your password has been updated successfuly"
msgid "Your password has been updated successfully"
msgstr "Passordet ditt er oppdatert."
#: canaille/core/admin.py:23 canaille/templates/partial/users.html:15
@ -222,7 +224,7 @@ msgstr "E-postadresse"
#: canaille/core/admin.py:29 canaille/core/forms.py:56
#: canaille/core/forms.py:80 canaille/core/forms.py:168
#: canaille/core/forms.py:352
#: canaille/core/forms.py:353
msgid "jane@doe.com"
msgstr "ola@nordmann.no"
@ -290,12 +292,12 @@ msgstr "Passordet og bekreftelsen samsvarer ikke."
msgid "Automatic"
msgstr "Automatisk"
#: canaille/core/forms.py:123 canaille/core/forms.py:339
#: canaille/core/forms.py:123 canaille/core/forms.py:340
msgid "Username"
msgstr "Brukernavn"
#: canaille/core/forms.py:127 canaille/core/forms.py:311
#: canaille/core/forms.py:325 canaille/oidc/forms.py:22
#: canaille/core/forms.py:127 canaille/core/forms.py:312
#: canaille/core/forms.py:326 canaille/oidc/forms.py:22
#: canaille/templates/partial/groups.html:6
#: canaille/templates/partial/oidc/admin/client_list.html:6
#: canaille/templates/partial/users.html:12
@ -334,7 +336,7 @@ msgstr "Visningsnavn"
msgid "Johnny"
msgstr "Ola"
#: canaille/core/forms.py:158 canaille/core/forms.py:345
#: canaille/core/forms.py:158 canaille/core/forms.py:346
msgid "Email address"
msgstr "E-postadresse"
@ -439,22 +441,22 @@ msgstr "Foretrukket språk"
msgid "users, admins …"
msgstr "brukere, administratorer …"
#: canaille/core/forms.py:289
#: canaille/core/forms.py:290
#, fuzzy
#| msgid "Account creation"
msgid "Account expiration"
msgstr "Kontoopprettelse"
#: canaille/core/forms.py:314
#: canaille/core/forms.py:315
msgid "group"
msgstr "gruppe"
#: canaille/core/forms.py:318 canaille/core/forms.py:332
#: canaille/core/forms.py:319 canaille/core/forms.py:333
#: canaille/templates/partial/groups.html:7
msgid "Description"
msgstr "Beskrivelse"
#: canaille/core/forms.py:343
#: canaille/core/forms.py:344
msgid "Username editable by the invitee"
msgstr ""

View file

@ -25,6 +25,10 @@ FAVICON = "/static/img/canaille-c.png"
# 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"

View file

@ -25,6 +25,10 @@ FAVICON = "/static/img/canaille-c.png"
# 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"

36
doc/backends.rst Normal file
View file

@ -0,0 +1,36 @@
Backends
#############
.. contents::
:local:
LDAP
====
Canaille can integrate with several OpenLDAP overlays:
memberof / refint
-----------------
*memberof* and *refint* 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
:language: ldif
.. literalinclude :: ../demo/ldif/refint-config.ldif
:language: ldif
ppolicy
-------
If *ppolicy* is configured and the ``pwdEndTime`` attribute is available (since OpenLDAP 2.6), then account locking support will be enabled in canaille. To allow users to manage account expiration, they need to have a *write* permission on the ``lock_date`` attribute.
Here is a configuration example compatible with canaille:
.. literalinclude :: ../demo/ldif/ppolicy-config.ldif
:language: ldif
.. literalinclude :: ../demo/ldif/ppolicy.ldif
:language: ldif

View file

@ -2,15 +2,12 @@
import datetime
import os
import sys
from importlib import metadata
from unittest import mock
sys.path.insert(0, os.path.abspath(".."))
sys.path.insert(0, os.path.abspath("../canaille"))
if sys.version_info[:2] >= (3, 8):
from importlib import metadata
else:
import importlib_metadata as metadata
# Readthedocs does not support C modules, so
# we have to mock them.

View file

@ -34,6 +34,9 @@ Canaille is based on Flask, so any `flask configuration <https://flask.palletspr
:LANGUAGE:
*Optional.* The locale code of the language to use. If not set, the language of the browser will be used.
:TIMEZONE:
*Optional.* The timezone in which datetimes will be displayed to the users. If unset, the server timezone will be used.
:SENTRY_DSN:
*Optional.* A DSN to a sentry instance.
This needs the ``sentry_sdk`` python package to be installed.

View file

@ -34,6 +34,7 @@ Table of contents
:maxdepth: 2
install
backends
configuration
contributing
specifications

466
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@ build-backend = "poetry.masonry.api"
[tool]
[tool.poetry]
name = "Canaille"
version = "0.0.25"
version = "0.0.26"
description = "Minimalistic identity provider"
license = "MIT"
keywords = ["oidc", "oauth", "oauth2", "openid", "identity"]
@ -36,7 +36,7 @@ readme = "README.md"
include = ["canaille/translations/*/LC_MESSAGES/*.mo"]
[tool.poetry.dependencies]
python = ">=3.7, <4"
python = ">=3.8, <4"
authlib = ">1,<2"
click = "<9"
email_validator = "<3"
@ -46,6 +46,7 @@ flask-themer = "<2"
flask-wtf = "<2"
pycountry = "^22.3.5"
python-ldap = "<4"
pytz = "^2023.3"
toml = "<1"
wtforms = "<4"
@ -58,6 +59,7 @@ optional = true
"sphinx" = "*"
"sphinx-rtd-theme" = "*"
"sphinx-issues" = "*"
pygments-ldif = "^1.0.1"
[tool.poetry.group.dev.dependencies]
coverage = {version = "*", extras=["toml"]}
@ -74,6 +76,7 @@ pytest-httpserver = "*"
slapd = "*"
smtpdfix = "*"
pytest-flask = "^1.2.0"
pytest-lazy-fixture = "^0.6.3"
[tool.poetry.group.demo]
optional = true
@ -120,7 +123,6 @@ isolated_build = true
skipsdist = true
envlist =
style
py37
py38
py39
py310

View file

@ -8,7 +8,7 @@ def test_check_command(testclient):
def test_check_command_fail(testclient):
testclient.app.config["SMTP"]["HOST"] = "ldap://invalid-ldap.com"
testclient.app.config["SMTP"]["HOST"] = "invalid-domain.com"
runner = testclient.app.test_cli_runner()
res = runner.invoke(cli, ["check"])
assert res.exit_code == 1, res.stdout

View file

@ -7,6 +7,11 @@ from canaille.app.flask import set_parameter_in_url_query
from flask_webtest import TestApp
@pytest.fixture
def configuration(ldap_configuration):
yield ldap_configuration
def test_set_parameter_in_url_query():
assert (
set_parameter_in_url_query("https://auth.mydomain.tld", foo="bar")

186
tests/app/test_forms.py Normal file
View file

@ -0,0 +1,186 @@
import datetime
import wtforms
from babel.dates import LOCALTZ
from canaille.app.forms import DateTimeUTCField
from flask import current_app
from werkzeug.datastructures import ImmutableMultiDict
def test_datetime_utc_field_no_timezone_is_local_timezone(testclient):
del current_app.config["TIMEZONE"]
offset = LOCALTZ.utcoffset(datetime.datetime.utcnow())
class TestForm(wtforms.Form):
dt = DateTimeUTCField()
form = TestForm()
form.validate()
assert form.dt.data is None
utc_date = datetime.datetime(2023, 6, 1, 12, tzinfo=datetime.timezone.utc)
locale_date = datetime.datetime(2023, 6, 1, 12) + offset
rendered_locale_date = locale_date.strftime("%Y-%m-%d %H:%M:%S")
rendered_locale_date_form = locale_date.strftime("%Y-%m-%d %H:%M:%S")
request_form = ImmutableMultiDict({"dt": rendered_locale_date_form})
form = TestForm(request_form)
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date_form}">'
)
form = TestForm(data={"dt": utc_date})
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date}">'
)
class Foobar:
dt = utc_date
form = TestForm(obj=Foobar())
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date}">'
)
def test_datetime_utc_field_utc(testclient):
current_app.config["TIMEZONE"] = "UTC"
class TestForm(wtforms.Form):
dt = DateTimeUTCField()
form = TestForm()
form.validate()
assert form.dt.data is None
date = datetime.datetime(2023, 6, 1, 12, tzinfo=datetime.timezone.utc)
rendered_date = date.strftime("%Y-%m-%d %H:%M:%S")
rendered_date_form = date.strftime("%Y-%m-%d %H:%M:%S")
request_form = ImmutableMultiDict({"dt": rendered_date_form})
form = TestForm(request_form)
assert form.validate()
assert form.dt.data == date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_date_form}">'
)
form = TestForm(data={"dt": date})
assert form.validate()
assert form.dt.data == date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_date}">'
)
class Foobar:
dt = date
form = TestForm(obj=Foobar())
assert form.validate()
assert form.dt.data == date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_date}">'
)
def test_datetime_utc_field_japan_timezone(testclient):
current_app.config["TIMEZONE"] = "Japan"
class TestForm(wtforms.Form):
dt = DateTimeUTCField()
form = TestForm()
form.validate()
assert form.dt.data is None
utc_date = datetime.datetime(2023, 6, 1, 12, tzinfo=datetime.timezone.utc)
locale_date = datetime.datetime(2023, 6, 1, 21)
rendered_locale_date = locale_date.strftime("%Y-%m-%d %H:%M:%S")
rendered_locale_date_form = locale_date.strftime("%Y-%m-%d %H:%M:%S")
request_form = ImmutableMultiDict({"dt": rendered_locale_date_form})
form = TestForm(request_form)
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date_form}">'
)
form = TestForm(data={"dt": utc_date})
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date}">'
)
class Foobar:
dt = utc_date
form = TestForm(obj=Foobar())
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date}">'
)
def test_datetime_utc_field_invalid_timezone(testclient):
current_app.config["TIMEZONE"] = "invalid"
offset = LOCALTZ.utcoffset(datetime.datetime.utcnow())
class TestForm(wtforms.Form):
dt = DateTimeUTCField()
form = TestForm()
form.validate()
assert form.dt.data is None
utc_date = datetime.datetime(2023, 6, 1, 12, tzinfo=datetime.timezone.utc)
locale_date = datetime.datetime(2023, 6, 1, 12) + offset
rendered_locale_date = locale_date.strftime("%Y-%m-%d %H:%M:%S")
rendered_locale_date_form = locale_date.strftime("%Y-%m-%d %H:%M:%S")
request_form = ImmutableMultiDict({"dt": rendered_locale_date_form})
form = TestForm(request_form)
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date_form}">'
)
form = TestForm(data={"dt": utc_date})
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date}">'
)
class Foobar:
dt = utc_date
form = TestForm(obj=Foobar())
assert form.validate()
assert form.dt.data == utc_date
assert (
form.dt()
== f'<input id="dt" name="dt" type="datetime-local" value="{rendered_locale_date}">'
)

View file

@ -24,7 +24,7 @@ def test_preferred_language(testclient, logged_user):
res.form["preferred_language"] = "en"
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfuly.")]
assert res.flashes == [("success", "Profile updated successfully.")]
res = res.follow()
logged_user.reload()
assert logged_user.preferred_language == "en"
@ -35,7 +35,7 @@ def test_preferred_language(testclient, logged_user):
res.form["preferred_language"] = "auto"
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfuly.")]
assert res.flashes == [("success", "Profile updated successfully.")]
res = res.follow()
logged_user.reload()
assert logged_user.preferred_language is None

View file

@ -0,0 +1,42 @@
import os
import slapd
class CustomSlapdObject(slapd.Slapd):
def __init__(self):
schemas = [
schema
for schema in [
"core.ldif",
"cosine.ldif",
"nis.ldif",
"inetorgperson.ldif",
"ppolicy.ldif",
]
if os.path.exists(os.path.join(self.SCHEMADIR, schema))
]
super().__init__(
suffix="dc=mydomain,dc=tld",
schemas=schemas,
)
def init_tree(self):
suffix_dc = self.suffix.split(",")[0][3:]
self.ldapadd(
"\n".join(
[
"dn: " + self.suffix,
"objectClass: dcObject",
"objectClass: organization",
"dc: " + suffix_dc,
"o: " + suffix_dc,
"",
"dn: " + self.root_dn,
"objectClass: applicationProcess",
"cn: " + self.root_cn,
]
)
+ "\n"
)

View file

@ -0,0 +1,50 @@
import pytest
from canaille.backends.ldap.backend import LDAPBackend
from tests.backends.ldap import CustomSlapdObject
@pytest.fixture(scope="session")
def slapd_server():
slapd = CustomSlapdObject()
try:
slapd.start()
slapd.init_tree()
for ldif in (
"demo/ldif/memberof-config.ldif",
"demo/ldif/ppolicy-config.ldif",
"demo/ldif/ppolicy.ldif",
"canaille/backends/ldap/schemas/oauth2-openldap.ldif",
"demo/ldif/bootstrap-users-tree.ldif",
"demo/ldif/bootstrap-oidc-tree.ldif",
):
with open(ldif) as fd:
slapd.ldapadd(fd.read())
yield slapd
finally:
slapd.stop()
@pytest.fixture
def ldap_configuration(configuration, slapd_server):
configuration["BACKENDS"] = {
"LDAP": {
"ROOT_DN": slapd_server.suffix,
"URI": slapd_server.ldap_uri,
"BIND_DN": slapd_server.root_dn,
"BIND_PW": slapd_server.root_pw,
"USER_BASE": "ou=users",
"USER_FILTER": "(uid={login})",
"GROUP_BASE": "ou=groups",
"TIMEOUT": 0.1,
},
}
yield configuration
del configuration["BACKENDS"]
@pytest.fixture
def ldap_backend(slapd_server, ldap_configuration):
backend = LDAPBackend(ldap_configuration)
with backend.session():
yield backend

View file

@ -1,7 +1,13 @@
import pytest
from canaille import create_app
from flask_webtest import TestApp
@pytest.fixture
def configuration(slapd_server, ldap_configuration):
yield ldap_configuration
def test_ldap_connection_remote_ldap_unreachable(configuration):
app = create_app(configuration)
testclient = TestApp(app)

View file

@ -6,7 +6,13 @@ from canaille.backends.ldap.backend import LDAPBackend
from canaille.backends.ldap.ldapobject import LDAPObject
from canaille.commands import cli
from flask_webtest import TestApp
from tests.conftest import CustomSlapdObject
from . import CustomSlapdObject
@pytest.fixture
def configuration(ldap_configuration):
yield ldap_configuration
@pytest.fixture

View file

@ -1,93 +1,23 @@
import os
import pytest
import slapd
from canaille import create_app
from canaille.app import models
from canaille.backends.ldap.backend import LDAPBackend
from flask_webtest import TestApp
from pytest_lazyfixture import lazy_fixture
from werkzeug.security import gen_salt
class CustomSlapdObject(slapd.Slapd):
def __init__(self):
schemas = [
schema
for schema in [
"core.ldif",
"cosine.ldif",
"nis.ldif",
"inetorgperson.ldif",
"ppolicy.ldif",
]
if os.path.exists(os.path.join(self.SCHEMADIR, schema))
]
super().__init__(
suffix="dc=mydomain,dc=tld",
schemas=schemas,
)
def init_tree(self):
suffix_dc = self.suffix.split(",")[0][3:]
self.ldapadd(
"\n".join(
[
"dn: " + self.suffix,
"objectClass: dcObject",
"objectClass: organization",
"dc: " + suffix_dc,
"o: " + suffix_dc,
"",
"dn: " + self.root_dn,
"objectClass: applicationProcess",
"cn: " + self.root_cn,
]
)
+ "\n"
)
@pytest.fixture(scope="session")
def slapd_server():
slapd = CustomSlapdObject()
try:
slapd.start()
slapd.init_tree()
for ldif in (
"demo/ldif/memberof-config.ldif",
"demo/ldif/ppolicy-config.ldif",
"demo/ldif/ppolicy.ldif",
"canaille/backends/ldap/schemas/oauth2-openldap.ldif",
"demo/ldif/bootstrap-users-tree.ldif",
"demo/ldif/bootstrap-oidc-tree.ldif",
):
with open(ldif) as fd:
slapd.ldapadd(fd.read())
yield slapd
finally:
slapd.stop()
pytest_plugins = [
"tests.backends.ldap.fixtures",
]
@pytest.fixture
def configuration(slapd_server, smtpd):
def configuration(smtpd):
smtpd.config.use_starttls = True
conf = {
"SECRET_KEY": gen_salt(24),
"LOGO": "/static/img/canaille-head.png",
"BACKENDS": {
"LDAP": {
"ROOT_DN": slapd_server.suffix,
"URI": slapd_server.ldap_uri,
"BIND_DN": slapd_server.root_dn,
"BIND_PW": slapd_server.root_pw,
"USER_BASE": "ou=users",
"USER_FILTER": "(uid={login})",
"GROUP_BASE": "ou=groups",
"TIMEOUT": 0.1,
},
},
"TIMEZONE": "UTC",
"ACL": {
"DEFAULT": {
"READ": ["user_name", "groups"],
@ -146,11 +76,9 @@ def configuration(slapd_server, smtpd):
return conf
@pytest.fixture
def backend(slapd_server, configuration):
backend = LDAPBackend(configuration)
with backend.session():
yield backend
@pytest.fixture(params=[lazy_fixture("ldap_backend")])
def backend(request):
yield request.param
@pytest.fixture

View file

@ -36,7 +36,7 @@ def test_invitation(testclient, logged_admin, foo_group, smtpd):
res = res.form.submit(status=302)
assert ("success", "Your account has been created successfuly.") in res.flashes
assert ("success", "Your account has been created successfully.") in res.flashes
res = res.follow(status=200)
user = models.User.get_from_login("someone")
@ -86,7 +86,7 @@ def test_invitation_editable_user_name(testclient, logged_admin, foo_group, smtp
res = res.form.submit(status=302)
assert ("success", "Your account has been created successfuly.") in res.flashes
assert ("success", "Your account has been created successfully.") in res.flashes
res = res.follow(status=200)
user = models.User.get_from_login("djorje")

View file

@ -10,7 +10,7 @@ def test_password_reset(testclient, user):
res.form["password"] = "foobarbaz"
res.form["confirmation"] = "foobarbaz"
res = res.form.submit()
assert ("success", "Your password has been updated successfuly") in res.flashes
assert ("success", "Your password has been updated successfully") in res.flashes
user.reload()
assert user.check_password("foobarbaz")[0]

View file

@ -161,7 +161,7 @@ def test_edition_remove_fields(
res.form["phone_number"] = ""
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfuly.")], res.text
assert res.flashes == [("success", "Profile updated successfully.")], res.text
res = res.follow()
logged_user.reload()

View file

@ -57,7 +57,7 @@ def test_photo_on_profile_edition(
res.form["photo"] = Upload("logo.jpg", jpeg_photo)
res.form["photo_delete"] = False
res = res.form.submit(name="action", value="edit")
assert ("success", "Profile updated successfuly.") in res.flashes
assert ("success", "Profile updated successfully.") in res.flashes
res = res.follow()
logged_user.reload()
@ -68,7 +68,7 @@ def test_photo_on_profile_edition(
res = testclient.get("/profile/user", status=200)
res.form["photo_delete"] = False
res = res.form.submit(name="action", value="edit")
assert ("success", "Profile updated successfuly.") in res.flashes
assert ("success", "Profile updated successfully.") in res.flashes
res = res.follow()
logged_user.reload()
@ -79,7 +79,7 @@ def test_photo_on_profile_edition(
res = testclient.get("/profile/user", status=200)
res.form["photo_delete"] = True
res = res.form.submit(name="action", value="edit")
assert ("success", "Profile updated successfuly.") in res.flashes
assert ("success", "Profile updated successfully.") in res.flashes
res = res.follow()
logged_user.reload()
@ -91,7 +91,7 @@ def test_photo_on_profile_edition(
res.form["photo"] = Upload("logo.jpg", jpeg_photo)
res.form["photo_delete"] = True
res = res.form.submit(name="action", value="edit")
assert ("success", "Profile updated successfuly.") in res.flashes
assert ("success", "Profile updated successfully.") in res.flashes
res = res.follow()
logged_user.reload()

View file

@ -24,7 +24,7 @@ def test_edition(
res.form["user_name"] = "toto"
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfuly.")]
assert res.flashes == [("success", "Profile updated successfully.")]
res = res.follow()
logged_user.reload()
@ -68,7 +68,7 @@ def test_edition_without_groups(
testclient.app.config["ACL"]["DEFAULT"]["READ"] = []
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfuly.")]
assert res.flashes == [("success", "Profile updated successfully.")]
res = res.follow()
logged_user.reload()
@ -97,7 +97,7 @@ def test_password_change(testclient, logged_user):
res.form["password2"] = "correct horse battery staple"
res = res.form.submit(name="action", value="edit")
assert ("success", "Profile updated successfuly.") in res.flashes
assert ("success", "Profile updated successfully.") in res.flashes
res = res.follow()
logged_user.reload()
@ -349,7 +349,7 @@ def test_past_lock_date(
) - datetime.timedelta(days=30)
res.form["lock_date"] = expiration_datetime.strftime("%Y-%m-%d %H:%M")
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfuly.")]
assert res.flashes == [("success", "Profile updated successfully.")]
res = res.follow()
user = models.User.get(id=user.id)
@ -372,7 +372,7 @@ def test_future_lock_date(
) + datetime.timedelta(days=30)
res.form["lock_date"] = expiration_datetime.strftime("%Y-%m-%d %H:%M")
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfuly.")]
assert res.flashes == [("success", "Profile updated successfully.")]
res = res.follow()
user = models.User.get(id=user.id)
@ -396,7 +396,7 @@ def test_empty_lock_date(
res = testclient.get("/profile/user/settings", status=200)
res.form["lock_date"] = ""
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfuly.")]
assert res.flashes == [("success", "Profile updated successfully.")]
res = res.follow()
user.reload()
@ -418,7 +418,7 @@ def test_account_limit_values(
)
res.form["lock_date"] = expiration_datetime.strftime("%Y-%m-%d %H:%M:%S")
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfuly.")]
assert res.flashes == [("success", "Profile updated successfully.")]
res = res.follow()
user = models.User.get(id=user.id)

View file

@ -1,10 +1,16 @@
import os
import pytest
from canaille import create_app
from canaille.commands import cli
from flask_webtest import TestApp
@pytest.fixture
def configuration(ldap_configuration):
yield ldap_configuration
def test_install_keypair(configuration, tmpdir):
keys_dir = os.path.join(tmpdir, "keys")
os.makedirs(keys_dir)

View file

@ -14,7 +14,9 @@ from werkzeug.security import gen_salt
@pytest.fixture
def app(app):
# For some reason all the params from the overriden fixture must be present here
# https://github.com/pytest-dev/pytest/issues/11075
def app(app, configuration, backend):
os.environ["AUTHLIB_INSECURE_TRANSPORT"] = "true"
yield app