jpegPhoto profile form

This commit is contained in:
Éloi Rivard 2021-12-08 18:06:50 +01:00 committed by Éloi Rivard
parent 5ba87a2ddc
commit 0053369604
19 changed files with 432 additions and 221 deletions

View file

@ -22,7 +22,7 @@ from flask_babel import Babel, gettext as _
from flask_themer import Themer, render_template, FileSystemThemeLoader
from logging.config import dictConfig
from .flaskutils import current_user
from .flaskutils import current_user, base64picture
from .ldaputils import LDAPObject
from .oauth2utils import setup_oauth
from .models import User, Group
@ -163,6 +163,10 @@ def teardown_ldap_connection(app):
g.ldap.unbind_s()
def setup_jinja(app):
app.jinja_env.filters["base64picture"] = base64picture
def setup_babel(app):
babel = Babel(app)
@ -231,6 +235,7 @@ def create_app(config=None, validate=True):
setup_ldap_models(app)
setup_oauth(app)
setup_blueprints(app)
setup_jinja(app)
setup_babel(app)
setup_themer(app)

View file

@ -14,7 +14,13 @@ from flask import (
from flask_babel import gettext as _
from flask_themer import render_template
from werkzeug.datastructures import CombinedMultiDict, FileStorage
from .apputils import default_fields, b64_to_obj, login_placeholder, profile_hash, obj_to_b64
from .apputils import (
default_fields,
b64_to_obj,
login_placeholder,
profile_hash,
obj_to_b64,
)
from .forms import (
InvitationForm,
LoginForm,
@ -54,7 +60,9 @@ def about():
@bp.route("/login", methods=("GET", "POST"))
def login():
if current_user():
return redirect(url_for("account.profile_edition", username=current_user().uid[0]))
return redirect(
url_for("account.profile_edition", username=current_user().uid[0])
)
form = LoginForm(request.form or None)
form["login"].render_kw["placeholder"] = login_placeholder()
@ -295,6 +303,9 @@ def profile_create(current_app, form):
else:
user[attribute.name] = [data]
if "jpegPhoto" in form and form["jpegPhoto_delete"].data:
user["jpegPhoto"] = None
user.cn = [f"{user.givenName[0]} {user.sn[0]}"]
user.save()
@ -391,6 +402,9 @@ def profile_edit(editor, username):
elif attribute.name == "groups" and "groups" in editor.write:
user.set_groups(attribute.data)
if "jpegPhoto" in form and form["jpegPhoto_delete"].data:
user["jpegPhoto"] = None
if (
"password1" not in request.form
or not form["password1"].data

View file

@ -111,9 +111,9 @@ GROUP_USER_FILTER = "member={user.dn}"
# The 'READ' and 'WRITE' attributes are the LDAP attributes of the user
# object that users will be able to read and/or write.
[ACL.DEFAULT]
READ = ["uid", "groups"]
PERMISSIONS = ["use_oidc"]
WRITE = ["givenName", "sn", "userPassword", "telephoneNumber"]
READ = ["uid", "groups"]
WRITE = ["givenName", "sn", "userPassword", "telephoneNumber", "jpegPhoto", "mail"]
[ACL.ADMIN]
FILTER = "memberof=cn=moderators,ou=groups,dc=mydomain,dc=tld"

View file

@ -1,3 +1,4 @@
import base64
import ldap
import logging
from functools import wraps
@ -82,3 +83,8 @@ def smtp_needed():
return decorator
return wrapper
def base64picture(data):
data = base64.b64encode(data).decode("utf-8")
return f"data:image/jpeg;base64,{data}"

View file

@ -3,7 +3,7 @@ import wtforms.form
from flask import current_app
from flask_babel import lazy_gettext as _
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileRequired
from flask_wtf.file import FileField, FileAllowed
from .models import User, Group
@ -122,7 +122,12 @@ PROFILE_FORM_FIELDS = dict(
telephoneNumber=wtforms.TelField(
_("Phone number"), render_kw={"placeholder": _("555-000-555")}
),
jpegPhoto=FileField(_("Photo"), validators=[FileRequired()]),
jpegPhoto=FileField(
_("Photo"),
validators=[FileAllowed(["jpg", "jpeg"])],
render_kw={"accept": "image/jpg, image/jpeg"},
),
jpegPhoto_delete=wtforms.BooleanField(_("Delete the photo")),
password1=wtforms.PasswordField(
_("Password"),
validators=[wtforms.validators.Optional(), wtforms.validators.Length(min=8)],
@ -148,6 +153,9 @@ def profile_form(write_field_names, readonly_field_names):
if "userPassword" in write_field_names:
write_field_names |= {"password1", "password2"}
if "jpegPhoto" in write_field_names:
write_field_names |= {"jpegPhoto_delete"}
fields = {
name: PROFILE_FORM_FIELDS.get(name)
for name in write_field_names | readonly_field_names

View file

@ -166,6 +166,9 @@ class LDAPObject:
if syntax == "1.3.6.1.4.1.1466.115.121.1.27": # Integer
return int(value.decode("utf-8"))
if syntax == "1.3.6.1.4.1.1466.115.121.1.28": # JPEG
return value
if syntax == "1.3.6.1.4.1.1466.115.121.1.7": # Boolean
return value.decode("utf-8").upper() == "TRUE"
@ -181,6 +184,9 @@ class LDAPObject:
if syntax == "1.3.6.1.4.1.1466.115.121.1.27": # Integer
return str(value).encode("utf-8")
if syntax == "1.3.6.1.4.1.1466.115.121.1.28": # JPEG
return value
if syntax == "1.3.6.1.4.1.1466.115.121.1.7": # Boolean
return ("TRUE" if value else "FALSE").encode("utf-8")
@ -215,13 +221,18 @@ class LDAPObject:
# Object already exists in the LDAP database
if match:
deletions = [
name
for name, value in self.changes.items()
if value is None and name in self.attrs
]
changes = {
name: value
for name, value in self.changes.items()
if value and value[0] and self.attrs.get(name) != value
}
formatted_changes = self.python_attrs_to_ldap(changes)
modlist = [
modlist = [(ldap.MOD_DELETE, name, None) for name in deletions] + [
(ldap.MOD_REPLACE, name, values)
for name, values in formatted_changes.items()
]

View file

@ -148,6 +148,12 @@ class User(LDAPObject):
self.read |= set(details.get("READ", []))
self.write |= set(details.get("WRITE", []))
def can_read(self, field):
return field in self.read | self.write
def can_writec(self, field):
return field in self.write
@property
def can_use_oidc(self):
return "use_oidc" in self.permissions

View file

@ -42,3 +42,11 @@ footer a {
.ui.corner.labeled.input .ui.dropdown .dropdown.icon {
margin-right: 1.5em;
}
.profile-form label img{
cursor: pointer;
}
.profile-form label i{
cursor: pointer;
}

View file

@ -0,0 +1,17 @@
$(function(){
$(".photo-delete-icon").click(function(event){
event.preventDefault();
$(".photo-content").hide();
$(".photo-placeholder").show();
$(".photo-field").val("");
$(".photo-delete-button").prop("checked", true);
});
$(".photo-field").change(function(event){
var upload = URL.createObjectURL(event.target.files[0]);
$(".photo-content").show();
$(".photo-placeholder").hide();
$(".photo-content img").attr("src", upload);
$(".photo-delete-button").prop("checked", false);
});
});

View file

@ -5,13 +5,16 @@
container=true,
noindicator=false,
indicator_icon=none,
indicator_text=none
indicator_text=none,
display=true
) -%}
{% if container %}
<div class="field {{ kwargs.pop('class_', '') }}
{%- if field.errors %} error{% endif -%}
{%- if field.render_kw and "disabled" in field.render_kw %} disabled{% endif -%}">
{%- if field.render_kw and "disabled" in field.render_kw %} disabled{% endif -%}"
{% if not display %}style="display: none"{% endif %}
>
{% endif %}
{% if (field.type != 'HiddenField' and field.type !='CSRFTokenField') and label_visible %}
@ -31,7 +34,6 @@
{% if field.type not in ("SelectField", "SelectMultipleField") %}
{{ field(**kwargs) }}
{% elif field.render_kw and "readonly" in field.render_kw %}
{# This will be un-needed when https://github.com/fomantic/Fomantic-UI/issues/2173 is solved #}
{{ field(class_="ui fluid dropdown multiple read-only", **kwargs) }}
{% else %}
{{ field(class_="ui fluid dropdown multiple", **kwargs) }}

View file

@ -1,5 +1,6 @@
{% extends theme('base.html') %}
{% import 'fomanticui.html' as sui %}
{% import 'userlist.html' as user_list %}
{% block style %}
<link href="/static/datatables/jquery.dataTables.min.css" rel="stylesheet">
@ -85,27 +86,7 @@
</div>
{% if edited_group %}
<table class="ui table">
<thead>
<th>{% trans %}Login{% endtrans %}</th>
<th>{% trans %}Name{% endtrans %}</th>
<th>{% trans %}Email{% endtrans %}</th>
<th>{% trans %}Groups{% endtrans %}</th>
</thead>
{% for user_account in members %}
<tr>
<td><a href="{{ url_for('account.profile_edition', username=user_account.uid[0]) }}">{{ user_account.uid[0] }}</a></td>
<td>{{ user_account.name }}</td>
<td><a href="mailto:{{ user_account.mail[0] }}">{{ user_account.mail[0] }}</a></td>
<td>
{% for group in user_account.groups %}
<a class="ui label" href="{{ url_for('groups.group', groupname=group.name) }}">{{ group.name }}</a>
{% endfor %}
</td>
</tr>
{% endfor %}
</table>
{{ user_list.user_list(user, members) }}
{% endif %}
{% endblock %}

View file

@ -3,20 +3,21 @@
{% block script %}
<script src="/static/js/confirm.js"></script>
<script src="/static/js/profile.js"></script>
{% endblock %}
{% macro render_field(field, noindicator=false) %}
{% set lock_indicator = field.render_kw and ("readonly" in field.render_kw or "disabled" in field.render_kw) %}
{% if not edited_user %}
{{ sui.render_field(field) }}
{{ sui.render_field(field, **kwargs) }}
{% elif edited_user.uid == user.uid or lock_indicator or noindicator %}
{{ sui.render_field(field) }}
{{ sui.render_field(field, **kwargs) }}
{% elif field.name in edited_user.write %}
{{ sui.render_field(field, noindicator=true) }}
{{ sui.render_field(field, noindicator=true, **kwargs) }}
{% elif field.name in edited_user.read %}
{{ sui.render_field(field, indicator_icon="eye", indicator_text=_("This user cannot edit this field")) }}
{{ sui.render_field(field, indicator_icon="eye", indicator_text=_("This user cannot edit this field"), **kwargs) }}
{% else %}
{{ sui.render_field(field, indicator_icon="eye slash", indicator_text=_("This user cannot see this field")) }}
{{ sui.render_field(field, indicator_icon="eye slash", indicator_text=_("This user cannot see this field"), **kwargs) }}
{% endif %}
{% endmacro %}
@ -71,12 +72,42 @@
action="{{ request.url }}"
role="form"
enctype="multipart/form-data"
class="ui form info{% if user.can_manage_users and edited_user and not edited_user.has_password() %} warning{% endif %}"
class="ui form info{% if user.can_manage_users and edited_user and not edited_user.has_password() %} warning{% endif %} profile-form"
>
{#{ render_field(form.csrf_token) }#}
<h4 class="ui dividing header">{% trans %}Personal information{% endtrans %}</h4>
{% if "jpegPhoto" in form %}
<div class="ui grid">
<div class="three wide column">
{{ render_field(form.jpegPhoto, display=false, class="photo-field") }}
{{ render_field(form.jpegPhoto_delete, display=false, class="photo-delete-button") }}
{% set photo = edited_user.jpegPhoto and edited_user.jpegPhoto[0] %}
<label
class="ui small bordered image photo-content"
for="{{ form.jpegPhoto.id }}"
title="{{ _("Click to upload a photo") }}"
{% if not photo %}style="display: none;"{% endif %}>
<a class="ui right corner label photo-delete-icon" title="{{ _("Delete the photo") }}">
<i class="times icon"></i>
</a>
<img src="{% if photo %}{{ edited_user.jpegPhoto[0]|base64picture }}{% endif %}" alt="User photo">
</label>
<label
class="ui centered photo-placeholder"
for="{{ form.jpegPhoto.id }}"
title="{{ _("Click to upload a photo") }}"
{% if photo %}style="display: none;"{% endif %}>
<i class="massive centered portrait icon"></i>
</label>
</div>
<div class="thirteen wide column">
{% endif %}
<div class="two fields">
{% if "givenName" in form %}
{{ render_field(form.givenName) }}
@ -85,9 +116,13 @@
{{ render_field(form.sn) }}
{% endif %}
</div>
{% if "mail" in form %}
{{ render_field(form.mail) }}
{% endif %}
{% if "jpegPhoto" in form %}</div></div>{% endif %}
{% if "telephoneNumber" in form %}
{{ render_field(form.telephoneNumber) }}
{% endif %}

View file

@ -0,0 +1,52 @@
{% macro user_list(watcher, users) %}
<table class="ui table">
<thead>
{% if watcher.can_read("jpegPhoto") %}
<th></th>
{% endif %}
{% if watcher.can_read("uid") %}
<th>{% trans %}Login{% endtrans %}</th>
{% endif %}
{% if watcher.can_read("sn") or watcher.can_read("givenName") %}
<th>{% trans %}Name{% endtrans %}</th>
{% endif %}
{% if watcher.can_read("mail") %}
<th>{% trans %}Email{% endtrans %}</th>
{% endif %}
{% if watcher.can_manage_groups %}
<th>{% trans %}Groups{% endtrans %}</th>
{% endif %}
</thead>
{% for user in users %}
<tr>
{% if watcher.can_read("jpegPhoto") %}
<td>
<a href="{{ url_for('account.profile_edition', username=user.uid[0]) }}">
{% if user.jpegPhoto and user.jpegPhoto[0] %}
<img class="ui avatar image" src="{{ user.jpegPhoto[0]|base64picture }}" alt="User photo">
{% else %}
<i class="user circle big black icon"></i>
{% endif %}
</a>
</td>
{% endif %}
{% if watcher.can_read("uid") %}
<td><a href="{{ url_for('account.profile_edition', username=user.uid[0]) }}">{{ user.uid[0] }}</a></td>
{% endif %}
{% if watcher.can_read("sn") or watcher.can_read("givenName") %}
<td>{{ user.name }}</td>
{% endif %}
{% if watcher.can_read("mail") %}
<td><a href="mailto:{{ user.mail[0] }}">{{ user.mail[0] }}</a></td>
{% endif %}
{% if watcher.can_manage_groups %}
<td>
{% for group in user.groups %}
<a class="ui label" href="{{ url_for('groups.group', groupname=group.name) }}">{{ group.name }}</a>
{% endfor %}
</td>
{% endif %}
</tr>
{% endfor %}
</table>
{% endmacro %}

View file

@ -1,4 +1,5 @@
{% extends theme('base.html') %}
{% import 'userlist.html' as user_list %}
{% block style %}
<link href="/static/datatables/jquery.dataTables.min.css" rel="stylesheet">
@ -21,27 +22,5 @@
</div>
</div>
<table class="ui table">
<thead>
<th>{% trans %}Login{% endtrans %}</th>
<th>{% trans %}Name{% endtrans %}</th>
<th>{% trans %}Email{% endtrans %}</th>
{% if user.can_manage_groups %}<th>{% trans %}Groups{% endtrans %}</th>{% endif %}
</thead>
{% for user_account in users %}
<tr>
<td><a href="{{ url_for('account.profile_edition', username=user_account.uid[0]) }}">{{ user_account.uid[0] }}</a></td>
<td>{{ user_account.name }}</td>
<td><a href="mailto:{{ user_account.mail[0] }}">{{ user_account.mail[0] }}</a></td>
{% if user.can_manage_groups %}
<td>
{% for group in user_account.groups %}
<a class="ui label" href="{{ url_for('groups.group', groupname=group.name) }}">{{ group.name }}</a>
{% endfor %}
</td>
{% endif %}
</tr>
{% endfor %}
</table>
{{ user_list.user_list(user, users) }}
{% endblock %}

View file

@ -9,46 +9,46 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: contact@yaal.fr\n"
"POT-Creation-Date: 2021-12-07 19:58+0100\n"
"PO-Revision-Date: 2021-12-07 20:03+0100\n"
"POT-Creation-Date: 2021-12-08 18:56+0100\n"
"PO-Revision-Date: 2021-12-08 18:56+0100\n"
"Last-Translator: Éloi Rivard <eloi.rivard@aquilenet.fr>\n"
"Language: fr\n"
"Language-Team: French <traduc@traduc.org>\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.9.1\n"
"X-Generator: Gtranslator 40.0\n"
#: canaille/__init__.py:123
#: canaille/__init__.py:129
msgid "Could not connect to the LDAP server '{uri}'"
msgstr "Impossible de se connecter au serveur LDAP '{uri}'"
#: canaille/__init__.py:139
#: canaille/__init__.py:145
msgid "LDAP authentication failed with user '{user}'"
msgstr ""
"L'authentification au serveur LDAP a échoué pour l'utilisateur '{user}'"
#: canaille/account.py:69 canaille/account.py:94 canaille/oauth.py:74
#: canaille/account.py:77 canaille/account.py:102 canaille/oauth.py:74
msgid "Login failed, please check your information"
msgstr "La connexion a échoué, veuillez vérifier vos informations."
#: canaille/account.py:100
#: canaille/account.py:108
#, python-format
msgid "Connection successful. Welcome %(user)s"
msgstr "Connexion réussie. Bienvenue %(user)s"
#: canaille/account.py:113
#: canaille/account.py:121
#, python-format
msgid "You have been disconnected. See you next time %(user)s"
msgstr "Vous avez été déconnectés. À bientôt %(user)s"
#: canaille/account.py:130
#: canaille/account.py:138
msgid "Could not send the password initialization link."
msgstr "Impossible d'envoyer le courriel d'initialisation de mot de passe."
#: canaille/account.py:135
#: canaille/account.py:143
msgid ""
"A password initialization link has been sent at your email address. You "
"should receive it within 10 minutes."
@ -56,35 +56,35 @@ msgstr ""
"Un lien d'initialisation de votre mot de passe vous a été envoyé par mail. "
"Vous devriez le recevoir d'ici une dizaine de minutes."
#: canaille/account.py:141 canaille/account.py:326
#: canaille/account.py:149 canaille/account.py:340
msgid "Could not send the password initialization email"
msgstr "Impossible d'envoyer le courriel d'initialisation de mot de passe."
#: canaille/account.py:192 canaille/account.py:264
#: canaille/account.py:204 canaille/account.py:275
msgid "User account creation failed."
msgstr "La création du compte utilisateur a échoué."
#: canaille/account.py:213 canaille/account.py:234
#: canaille/account.py:225 canaille/account.py:246
msgid "The invitation link that brought you here was invalid."
msgstr "Le lien d'invitation qui vous a amené ici est invalide."
#: canaille/account.py:220
#: canaille/account.py:232
msgid "Your account has already been created."
msgstr "Votre compte a déjà été créé."
#: canaille/account.py:227
#: canaille/account.py:239
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/account.py:269
#: canaille/account.py:280
msgid "You account has been created successfuly."
msgstr "Votre compte utilisateur a été créé avec succès."
#: canaille/account.py:299
#: canaille/account.py:313
msgid "User account creation succeed."
msgstr "La création du compte utilisateur a réussi."
#: canaille/account.py:320
#: canaille/account.py:334
msgid ""
"A password initialization link has been sent at the user email address. It "
"should be received within 10 minutes."
@ -92,7 +92,7 @@ msgstr ""
"Un lien d'initialisation de mot de passe a été envoyé à l'utilisateur par "
"mail. Il devrait arriver d'ici une dizaine de minutes."
#: canaille/account.py:334
#: canaille/account.py:348
msgid ""
"A password reset link has been sent at the user email address. It should be "
"received within 10 minutes."
@ -100,28 +100,28 @@ msgstr ""
"Un lien de réinitialisation de mot de passe a été envoyé à l'utilisateur par "
"mail. Il devrait arriver d'ici une dizaine de minutes."
#: canaille/account.py:340
#: canaille/account.py:354
msgid "Could not send the password reset email"
msgstr "Impossible d'envoyer le lien de réinitialisation."
#: canaille/account.py:371
#: canaille/account.py:385
msgid "Profile edition failed."
msgstr "L'édition du profil a échoué."
#: canaille/account.py:396
#: canaille/account.py:413
msgid "Profile updated successfuly."
msgstr "Le profil a été mis à jour avec succès."
#: canaille/account.py:416
#: canaille/account.py:433
#, python-format
msgid "The user %(user)s has been sucessfuly deleted"
msgstr "L'utilisateur %(user)s a bien été supprimé"
#: canaille/account.py:440
#: canaille/account.py:457
msgid "Could not send the password reset link."
msgstr "Impossible d'envoyer le lien de réinitialisation."
#: canaille/account.py:447 canaille/account.py:458
#: canaille/account.py:464 canaille/account.py:475
msgid ""
"A password reset link has been sent at your email address. You should "
"receive it within 10 minutes."
@ -129,15 +129,15 @@ msgstr ""
"Un lien de ré-initialisation de votre mot de passe vous a été envoyé par "
"mail. Vous devriez le recevoir d'ici une dizaine de minutes."
#: canaille/account.py:464
#: canaille/account.py:481
msgid "Could not reset your password"
msgstr "Impossible de réinitialiser votre mot de passe"
#: canaille/account.py:478
#: canaille/account.py:495
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/account.py:487
#: canaille/account.py:504
msgid "Your password has been updated successfuly"
msgstr "Votre mot de passe a correctement été mis à jour."
@ -145,7 +145,7 @@ msgstr "Votre mot de passe a correctement été mis à jour."
msgid "John Doe"
msgstr "Camille Dupont"
#: canaille/apputils.py:37 canaille/forms.py:93 canaille/forms.py:188
#: canaille/apputils.py:37 canaille/forms.py:93 canaille/forms.py:196
msgid "jdoe"
msgstr "cdupont"
@ -165,7 +165,7 @@ msgstr "Impossible de supprimer cet accès."
msgid "The access has been revoked"
msgstr "L'accès a été révoqué."
#: canaille/flaskutils.py:69
#: canaille/flaskutils.py:70
msgid "No SMTP server has been configured"
msgstr "Aucun serveur SMTP n'a été configuré"
@ -191,27 +191,27 @@ msgid "Login"
msgstr "Identifiant"
#: canaille/forms.py:45 canaille/forms.py:69 canaille/forms.py:117
#: canaille/forms.py:199
#: canaille/forms.py:207
msgid "jane@doe.com"
msgstr "camille@dupont.fr"
#: canaille/forms.py:55 canaille/forms.py:78 canaille/forms.py:127
#: canaille/forms.py:55 canaille/forms.py:78 canaille/forms.py:132
msgid "Password"
msgstr "Mot de passe"
#: canaille/forms.py:81 canaille/forms.py:131
#: canaille/forms.py:81 canaille/forms.py:136
msgid "Password confirmation"
msgstr "Confirmation du mot de passe"
#: canaille/forms.py:84 canaille/forms.py:134
#: canaille/forms.py:84 canaille/forms.py:139
msgid "Password and confirmation do not match."
msgstr "Le mot de passe et sa confirmation ne correspondent pas."
#: canaille/forms.py:92 canaille/forms.py:187
#: canaille/forms.py:92 canaille/forms.py:195
msgid "Username"
msgstr "Identifiant"
#: canaille/admin/clients.py:28 canaille/forms.py:96 canaille/forms.py:177
#: canaille/admin/clients.py:28 canaille/forms.py:96 canaille/forms.py:185
#: canaille/templates/admin/client_list.html:22
#: canaille/templates/group.html:91 canaille/templates/users.html:27
msgid "Name"
@ -233,7 +233,7 @@ msgstr "Nom de famille"
msgid "Doe"
msgstr "Dupont"
#: canaille/forms.py:114 canaille/forms.py:192
#: canaille/forms.py:114 canaille/forms.py:200
msgid "Email address"
msgstr "Courriel"
@ -245,25 +245,29 @@ msgstr "Numéro de téléphone"
msgid "555-000-555"
msgstr "06 01 02 03 04"
#: canaille/forms.py:125
#: canaille/forms.py:126
msgid "Photo"
msgstr "Photo"
#: canaille/forms.py:139
#: canaille/forms.py:130 canaille/templates/profile.html:91
msgid "Delete the photo"
msgstr "Supprimer la photo"
#: canaille/forms.py:144
msgid "Number"
msgstr "Numéro"
#: canaille/forms.py:141
#: canaille/forms.py:146
msgid "1234"
msgstr "1234"
#: canaille/forms.py:162 canaille/forms.py:205 canaille/templates/group.html:93
#: canaille/forms.py:170 canaille/forms.py:213 canaille/templates/group.html:93
#: canaille/templates/groups.html:9 canaille/templates/users.html:29
#: canaille/themes/default/base.html:59
msgid "Groups"
msgstr "Groupes"
#: canaille/forms.py:180
#: canaille/forms.py:188
msgid "group"
msgstr "groupe"
@ -553,11 +557,11 @@ msgstr "Envoyer le courriel d'initialisation"
#: canaille/templates/admin/client_edit.html:35
#: canaille/templates/admin/client_edit.html:44
#: canaille/templates/admin/client_edit.html:53
#: canaille/templates/fomanticui.html:46
#: canaille/templates/fomanticui.html:48
msgid "This field is not editable"
msgstr "Ce champ n'est pas modifiable"
#: canaille/templates/fomanticui.html:50
#: canaille/templates/fomanticui.html:52
msgid "This field is required"
msgstr "Ce champ est requis"
@ -584,7 +588,7 @@ msgstr ""
" "
#: canaille/templates/forgotten-password.html:38
#: canaille/templates/profile.html:137 canaille/templates/profile.html:165
#: canaille/templates/profile.html:164 canaille/templates/profile.html:192
msgid "Send again"
msgstr "Envoyer à nouveau"
@ -605,12 +609,12 @@ msgstr ""
"irrévocable, et toutes les données de cet utilisateur seront supprimées."
#: canaille/templates/admin/client_edit.html:18
#: canaille/templates/group.html:29 canaille/templates/profile.html:38
#: canaille/templates/group.html:29 canaille/templates/profile.html:41
msgid "Cancel"
msgstr "Annuler"
#: canaille/templates/admin/client_edit.html:19
#: canaille/templates/group.html:30 canaille/templates/profile.html:39
#: canaille/templates/group.html:30 canaille/templates/profile.html:42
msgid "Delete"
msgstr "Supprimer"
@ -648,7 +652,7 @@ msgstr "Supprimer le groupe"
msgid "Create group"
msgstr "Créer le groupe"
#: canaille/templates/group.html:79 canaille/templates/profile.html:205
#: canaille/templates/group.html:79 canaille/templates/profile.html:232
msgid "Submit"
msgstr "Valider"
@ -775,19 +779,19 @@ msgstr "Je ne suis pas %(username)s"
msgid "Sign in"
msgstr "Se connecter"
#: canaille/templates/profile.html:15
#: canaille/templates/profile.html:18
msgid "This user cannot edit this field"
msgstr "Cet utilisateur ne peut pas éditer ce champ"
#: canaille/templates/profile.html:17
#: canaille/templates/profile.html:20
msgid "This user cannot see this field"
msgstr "Cet utilisateur ne peut pas voir ce champ"
#: canaille/templates/profile.html:26
#: canaille/templates/profile.html:29
msgid "Account deletion"
msgstr "Suppression d'un compte"
#: canaille/templates/profile.html:31
#: canaille/templates/profile.html:34
msgid ""
"Are you sure you want to delete this user? This action is unrevokable and "
"all the data about this user will be removed."
@ -795,7 +799,7 @@ msgstr ""
"Êtes-vous sûrs de vouloir supprimer cet utilisateur ? Cette action est "
"irrévocable, et toutes les données de cet utilisateur seront supprimées."
#: canaille/templates/profile.html:33
#: canaille/templates/profile.html:36
msgid ""
"Are you sure you want to delete your account? This action is unrevokable and "
"all your data will be removed forever."
@ -803,51 +807,55 @@ msgstr ""
"Êtes-vous sûrs de vouloir supprimer votre compte ? Cette action est "
"irrévocable et toutes vos données seront supprimées pour toujours."
#: canaille/templates/profile.html:48
#: canaille/templates/profile.html:51
msgid "User creation"
msgstr "Nouvel utilisateur"
#: canaille/templates/profile.html:50 canaille/themes/default/base.html:39
#: canaille/templates/profile.html:53 canaille/themes/default/base.html:39
msgid "My profile"
msgstr "Mon profil"
#: canaille/templates/profile.html:52
#: canaille/templates/profile.html:55
msgid "User profile edition"
msgstr "Édition d'un profil utilisateur"
#: canaille/templates/profile.html:58
#: canaille/templates/profile.html:61
msgid "Create a new user account"
msgstr "Création d'un nouveau compte utilisateur"
#: canaille/templates/profile.html:60
#: canaille/templates/profile.html:63
msgid "Edit your personal informations"
msgstr "Éditez vos informations personnelles"
#: canaille/templates/profile.html:62
#: canaille/templates/profile.html:65
msgid "Edit informations about an user"
msgstr "Éditez les informations d'un utilisateur"
#: canaille/templates/profile.html:77
#: canaille/templates/profile.html:80
msgid "Personal information"
msgstr "Informations personnelles"
#: canaille/templates/profile.html:99
#: canaille/templates/profile.html:88 canaille/templates/profile.html:99
msgid "Click to upload a photo"
msgstr "Cliquez pour mettre en ligne une photo"
#: canaille/templates/profile.html:126
msgid "Account information"
msgstr "Informations sur le compte"
#: canaille/templates/profile.html:117
#: canaille/templates/profile.html:144
msgid "User password is not mandatory"
msgstr "Le mot de passe utilisateur n'est pas requis à la création"
#: canaille/templates/profile.html:120
#: canaille/templates/profile.html:147
msgid "The user password can be set:"
msgstr "Il pourra être renseigné :"
#: canaille/templates/profile.html:122
#: canaille/templates/profile.html:149
msgid "by filling this form;"
msgstr "en remplissant ce formulaire ;"
#: canaille/templates/profile.html:123
#: canaille/templates/profile.html:150
msgid ""
"by sending the user a password initialization mail, after the account "
"creation;"
@ -855,7 +863,7 @@ msgstr ""
"en envoyant un lien d'initialisation de mot de passe, par mail à "
"l'utilisateur, après la création de son compte;"
#: canaille/templates/profile.html:124 canaille/templates/profile.html:153
#: canaille/templates/profile.html:151 canaille/templates/profile.html:180
msgid ""
"or simply waiting for the user to sign-in a first time, and then receive a "
"password initialization mail."
@ -863,46 +871,46 @@ msgstr ""
"ou simplement en attendant la première connexion de l'utilisateur, afin "
"qu'il reçoive un lien d'initialisation de mot de passe par email."
#: canaille/templates/profile.html:127 canaille/templates/profile.html:156
#: canaille/templates/profile.html:154 canaille/templates/profile.html:183
msgid "The user will not be able to authenticate unless the password is set"
msgstr ""
"L'utilisateur ne pourra pas se connecter tant que son mot de passe n'est pas "
"défini"
#: canaille/templates/profile.html:141
#: canaille/templates/profile.html:168
msgid "Send email"
msgstr "Envoyer l'email"
#: canaille/templates/profile.html:146
#: canaille/templates/profile.html:173
msgid "This user does not have a password yet"
msgstr "L'utilisateur n'a pas encore de mot de passe"
#: canaille/templates/profile.html:149
#: canaille/templates/profile.html:176
msgid "You can solve this by:"
msgstr "Vous pouvez régler ceci en :"
#: canaille/templates/profile.html:151
#: canaille/templates/profile.html:178
msgid "setting a password using this form;"
msgstr "renseignant un mot de passe via ce formulaire ;"
#: canaille/templates/profile.html:152
#: canaille/templates/profile.html:179
msgid ""
"sending the user a password initialization mail, by clicking this button;"
msgstr ""
"envoyant un lien d'initialisation de mot de passe, par mail à l'utilisateur, "
"en cliquant sur ce bouton;"
#: canaille/templates/profile.html:167
#: canaille/templates/profile.html:194
msgid "Send mail"
msgstr "Envoyer l'email"
#: canaille/templates/admin/mails.html:34 canaille/templates/profile.html:171
#: canaille/templates/admin/mails.html:34 canaille/templates/profile.html:198
#: canaille/templates/reset-password.html:11
#: canaille/templates/reset-password.html:16
msgid "Password reset"
msgstr "Réinitialisation du mot de passe"
#: canaille/templates/profile.html:173
#: canaille/templates/profile.html:200
msgid ""
"If the user has forgotten his password, you can send him a password reset "
"email by clicking this button."
@ -910,19 +918,19 @@ msgstr ""
"Si l'utilisateur a oublié son mot de passe, vous pouvez lui envoyer un email "
"contenant un lien de réinitilisation en cliquant sur ce bouton."
#: canaille/templates/profile.html:185
#: canaille/templates/profile.html:212
msgid "Delete the user"
msgstr "Supprimer l'utilisateur"
#: canaille/templates/profile.html:187
#: canaille/templates/profile.html:214
msgid "Delete my account"
msgstr "Supprimer mon compte"
#: canaille/templates/profile.html:194
#: canaille/templates/profile.html:221
msgid "Impersonate"
msgstr "Prendre l'identité"
#: canaille/templates/profile.html:200 canaille/templates/users.html:19
#: canaille/templates/profile.html:227 canaille/templates/users.html:19
msgid "Invite a user"
msgstr "Inviter un utilisateur"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2021-12-07 19:58+0100\n"
"POT-Creation-Date: 2021-12-08 18:56+0100\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"
@ -17,114 +17,114 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.9.1\n"
#: canaille/__init__.py:123
#: canaille/__init__.py:129
msgid "Could not connect to the LDAP server '{uri}'"
msgstr ""
#: canaille/__init__.py:139
#: canaille/__init__.py:145
msgid "LDAP authentication failed with user '{user}'"
msgstr ""
#: canaille/account.py:69 canaille/account.py:94 canaille/oauth.py:74
#: canaille/account.py:77 canaille/account.py:102 canaille/oauth.py:74
msgid "Login failed, please check your information"
msgstr ""
#: canaille/account.py:100
#: canaille/account.py:108
#, python-format
msgid "Connection successful. Welcome %(user)s"
msgstr ""
#: canaille/account.py:113
#: canaille/account.py:121
#, python-format
msgid "You have been disconnected. See you next time %(user)s"
msgstr ""
#: canaille/account.py:130
#: canaille/account.py:138
msgid "Could not send the password initialization link."
msgstr ""
#: canaille/account.py:135
#: canaille/account.py:143
msgid ""
"A password initialization link has been sent at your email address. You "
"should receive it within 10 minutes."
msgstr ""
#: canaille/account.py:141 canaille/account.py:326
#: canaille/account.py:149 canaille/account.py:340
msgid "Could not send the password initialization email"
msgstr ""
#: canaille/account.py:192 canaille/account.py:264
#: canaille/account.py:204 canaille/account.py:275
msgid "User account creation failed."
msgstr ""
#: canaille/account.py:213 canaille/account.py:234
#: canaille/account.py:225 canaille/account.py:246
msgid "The invitation link that brought you here was invalid."
msgstr ""
#: canaille/account.py:220
#: canaille/account.py:232
msgid "Your account has already been created."
msgstr ""
#: canaille/account.py:227
#: canaille/account.py:239
msgid "You are already logged in, you cannot create an account."
msgstr ""
#: canaille/account.py:269
#: canaille/account.py:280
msgid "You account has been created successfuly."
msgstr ""
#: canaille/account.py:299
#: canaille/account.py:313
msgid "User account creation succeed."
msgstr ""
#: canaille/account.py:320
#: canaille/account.py:334
msgid ""
"A password initialization link has been sent at the user email address. "
"It should be received within 10 minutes."
msgstr ""
#: canaille/account.py:334
#: canaille/account.py:348
msgid ""
"A password reset link has been sent at the user email address. It should "
"be received within 10 minutes."
msgstr ""
#: canaille/account.py:340
#: canaille/account.py:354
msgid "Could not send the password reset email"
msgstr ""
#: canaille/account.py:371
#: canaille/account.py:385
msgid "Profile edition failed."
msgstr ""
#: canaille/account.py:396
#: canaille/account.py:413
msgid "Profile updated successfuly."
msgstr ""
#: canaille/account.py:416
#: canaille/account.py:433
#, python-format
msgid "The user %(user)s has been sucessfuly deleted"
msgstr ""
#: canaille/account.py:440
#: canaille/account.py:457
msgid "Could not send the password reset link."
msgstr ""
#: canaille/account.py:447 canaille/account.py:458
#: canaille/account.py:464 canaille/account.py:475
msgid ""
"A password reset link has been sent at your email address. You should "
"receive it within 10 minutes."
msgstr ""
#: canaille/account.py:464
#: canaille/account.py:481
msgid "Could not reset your password"
msgstr ""
#: canaille/account.py:478
#: canaille/account.py:495
msgid "The password reset link that brought you here was invalid."
msgstr ""
#: canaille/account.py:487
#: canaille/account.py:504
msgid "Your password has been updated successfuly"
msgstr ""
@ -132,7 +132,7 @@ msgstr ""
msgid "John Doe"
msgstr ""
#: canaille/apputils.py:37 canaille/forms.py:93 canaille/forms.py:188
#: canaille/apputils.py:37 canaille/forms.py:93 canaille/forms.py:196
msgid "jdoe"
msgstr ""
@ -152,7 +152,7 @@ msgstr ""
msgid "The access has been revoked"
msgstr ""
#: canaille/flaskutils.py:69
#: canaille/flaskutils.py:70
msgid "No SMTP server has been configured"
msgstr ""
@ -178,27 +178,27 @@ msgid "Login"
msgstr ""
#: canaille/forms.py:45 canaille/forms.py:69 canaille/forms.py:117
#: canaille/forms.py:199
#: canaille/forms.py:207
msgid "jane@doe.com"
msgstr ""
#: canaille/forms.py:55 canaille/forms.py:78 canaille/forms.py:127
#: canaille/forms.py:55 canaille/forms.py:78 canaille/forms.py:132
msgid "Password"
msgstr ""
#: canaille/forms.py:81 canaille/forms.py:131
#: canaille/forms.py:81 canaille/forms.py:136
msgid "Password confirmation"
msgstr ""
#: canaille/forms.py:84 canaille/forms.py:134
#: canaille/forms.py:84 canaille/forms.py:139
msgid "Password and confirmation do not match."
msgstr ""
#: canaille/forms.py:92 canaille/forms.py:187
#: canaille/forms.py:92 canaille/forms.py:195
msgid "Username"
msgstr ""
#: canaille/admin/clients.py:28 canaille/forms.py:96 canaille/forms.py:177
#: canaille/admin/clients.py:28 canaille/forms.py:96 canaille/forms.py:185
#: canaille/templates/admin/client_list.html:22
#: canaille/templates/group.html:91 canaille/templates/users.html:27
msgid "Name"
@ -220,7 +220,7 @@ msgstr ""
msgid "Doe"
msgstr ""
#: canaille/forms.py:114 canaille/forms.py:192
#: canaille/forms.py:114 canaille/forms.py:200
msgid "Email address"
msgstr ""
@ -232,25 +232,29 @@ msgstr ""
msgid "555-000-555"
msgstr ""
#: canaille/forms.py:125
#: canaille/forms.py:126
msgid "Photo"
msgstr ""
#: canaille/forms.py:139
#: canaille/forms.py:130 canaille/templates/profile.html:91
msgid "Delete the photo"
msgstr ""
#: canaille/forms.py:144
msgid "Number"
msgstr ""
#: canaille/forms.py:141
#: canaille/forms.py:146
msgid "1234"
msgstr ""
#: canaille/forms.py:162 canaille/forms.py:205 canaille/templates/group.html:93
#: canaille/forms.py:170 canaille/forms.py:213 canaille/templates/group.html:93
#: canaille/templates/groups.html:9 canaille/templates/users.html:29
#: canaille/themes/default/base.html:59
msgid "Groups"
msgstr ""
#: canaille/forms.py:180
#: canaille/forms.py:188
msgid "group"
msgstr ""
@ -533,11 +537,11 @@ msgstr ""
#: canaille/templates/admin/client_edit.html:35
#: canaille/templates/admin/client_edit.html:44
#: canaille/templates/admin/client_edit.html:53
#: canaille/templates/fomanticui.html:46
#: canaille/templates/fomanticui.html:48
msgid "This field is not editable"
msgstr ""
#: canaille/templates/fomanticui.html:50
#: canaille/templates/fomanticui.html:52
msgid "This field is required"
msgstr ""
@ -558,7 +562,7 @@ msgid ""
msgstr ""
#: canaille/templates/forgotten-password.html:38
#: canaille/templates/profile.html:137 canaille/templates/profile.html:165
#: canaille/templates/profile.html:164 canaille/templates/profile.html:192
msgid "Send again"
msgstr ""
@ -577,12 +581,12 @@ msgid ""
msgstr ""
#: canaille/templates/admin/client_edit.html:18
#: canaille/templates/group.html:29 canaille/templates/profile.html:38
#: canaille/templates/group.html:29 canaille/templates/profile.html:41
msgid "Cancel"
msgstr ""
#: canaille/templates/admin/client_edit.html:19
#: canaille/templates/group.html:30 canaille/templates/profile.html:39
#: canaille/templates/group.html:30 canaille/templates/profile.html:42
msgid "Delete"
msgstr ""
@ -617,7 +621,7 @@ msgstr ""
msgid "Create group"
msgstr ""
#: canaille/templates/group.html:79 canaille/templates/profile.html:205
#: canaille/templates/group.html:79 canaille/templates/profile.html:232
msgid "Submit"
msgstr ""
@ -735,139 +739,143 @@ msgstr ""
msgid "Sign in"
msgstr ""
#: canaille/templates/profile.html:15
#: canaille/templates/profile.html:18
msgid "This user cannot edit this field"
msgstr ""
#: canaille/templates/profile.html:17
#: canaille/templates/profile.html:20
msgid "This user cannot see this field"
msgstr ""
#: canaille/templates/profile.html:26
#: canaille/templates/profile.html:29
msgid "Account deletion"
msgstr ""
#: canaille/templates/profile.html:31
#: canaille/templates/profile.html:34
msgid ""
"Are you sure you want to delete this user? This action is unrevokable and"
" all the data about this user will be removed."
msgstr ""
#: canaille/templates/profile.html:33
#: canaille/templates/profile.html:36
msgid ""
"Are you sure you want to delete your account? This action is unrevokable "
"and all your data will be removed forever."
msgstr ""
#: canaille/templates/profile.html:48
#: canaille/templates/profile.html:51
msgid "User creation"
msgstr ""
#: canaille/templates/profile.html:50 canaille/themes/default/base.html:39
#: canaille/templates/profile.html:53 canaille/themes/default/base.html:39
msgid "My profile"
msgstr ""
#: canaille/templates/profile.html:52
#: canaille/templates/profile.html:55
msgid "User profile edition"
msgstr ""
#: canaille/templates/profile.html:58
#: canaille/templates/profile.html:61
msgid "Create a new user account"
msgstr ""
#: canaille/templates/profile.html:60
#: canaille/templates/profile.html:63
msgid "Edit your personal informations"
msgstr ""
#: canaille/templates/profile.html:62
#: canaille/templates/profile.html:65
msgid "Edit informations about an user"
msgstr ""
#: canaille/templates/profile.html:77
#: canaille/templates/profile.html:80
msgid "Personal information"
msgstr ""
#: canaille/templates/profile.html:99
#: canaille/templates/profile.html:88 canaille/templates/profile.html:99
msgid "Click to upload a photo"
msgstr ""
#: canaille/templates/profile.html:126
msgid "Account information"
msgstr ""
#: canaille/templates/profile.html:117
#: canaille/templates/profile.html:144
msgid "User password is not mandatory"
msgstr ""
#: canaille/templates/profile.html:120
#: canaille/templates/profile.html:147
msgid "The user password can be set:"
msgstr ""
#: canaille/templates/profile.html:122
#: canaille/templates/profile.html:149
msgid "by filling this form;"
msgstr ""
#: canaille/templates/profile.html:123
#: canaille/templates/profile.html:150
msgid ""
"by sending the user a password initialization mail, after the account "
"creation;"
msgstr ""
#: canaille/templates/profile.html:124 canaille/templates/profile.html:153
#: canaille/templates/profile.html:151 canaille/templates/profile.html:180
msgid ""
"or simply waiting for the user to sign-in a first time, and then receive "
"a password initialization mail."
msgstr ""
#: canaille/templates/profile.html:127 canaille/templates/profile.html:156
#: canaille/templates/profile.html:154 canaille/templates/profile.html:183
msgid "The user will not be able to authenticate unless the password is set"
msgstr ""
#: canaille/templates/profile.html:141
#: canaille/templates/profile.html:168
msgid "Send email"
msgstr ""
#: canaille/templates/profile.html:146
#: canaille/templates/profile.html:173
msgid "This user does not have a password yet"
msgstr ""
#: canaille/templates/profile.html:149
#: canaille/templates/profile.html:176
msgid "You can solve this by:"
msgstr ""
#: canaille/templates/profile.html:151
#: canaille/templates/profile.html:178
msgid "setting a password using this form;"
msgstr ""
#: canaille/templates/profile.html:152
#: canaille/templates/profile.html:179
msgid "sending the user a password initialization mail, by clicking this button;"
msgstr ""
#: canaille/templates/profile.html:167
#: canaille/templates/profile.html:194
msgid "Send mail"
msgstr ""
#: canaille/templates/admin/mails.html:34 canaille/templates/profile.html:171
#: canaille/templates/admin/mails.html:34 canaille/templates/profile.html:198
#: canaille/templates/reset-password.html:11
#: canaille/templates/reset-password.html:16
msgid "Password reset"
msgstr ""
#: canaille/templates/profile.html:173
#: canaille/templates/profile.html:200
msgid ""
"If the user has forgotten his password, you can send him a password reset"
" email by clicking this button."
msgstr ""
#: canaille/templates/profile.html:185
#: canaille/templates/profile.html:212
msgid "Delete the user"
msgstr ""
#: canaille/templates/profile.html:187
#: canaille/templates/profile.html:214
msgid "Delete my account"
msgstr ""
#: canaille/templates/profile.html:194
#: canaille/templates/profile.html:221
msgid "Impersonate"
msgstr ""
#: canaille/templates/profile.html:200 canaille/templates/users.html:19
#: canaille/templates/profile.html:227 canaille/templates/users.html:19
msgid "Invite a user"
msgstr ""

View file

@ -113,9 +113,9 @@ GROUP_USER_FILTER = "member={user.dn}"
# The 'READ' and 'WRITE' attributes are the LDAP attributes of the user
# object that users will be able to read and/or write.
[ACL.DEFAULT]
READ = ["uid", "groups"]
PERMISSIONS = ["use_oidc"]
WRITE = ["givenName", "sn", "userPassword", "telephoneNumber"]
READ = ["uid", "groups"]
WRITE = ["jpegPhoto", "givenName", "sn", "userPassword", "telephoneNumber", "mail"]
[ACL.ADMIN]
FILTER = "memberof=cn=admins,ou=groups,dc=mydomain,dc=tld"
@ -154,7 +154,7 @@ GIVEN_NAME = "givenName"
FAMILY_NAME = "sn"
PREFERRED_USERNAME = "displayName"
LOCALE = "preferredLanguage"
PICTURE = "photo"
PICTURE = "jpegPhoto"
ADDRESS = "postalAddress"
# The SMTP server options. If not set, mail related features such as

View file

@ -151,6 +151,7 @@ def configuration(slapd_server, smtpd, keypair_path):
"WRITE": [
"mail",
"givenName",
"jpegPhoto",
"sn",
"userPassword",
"telephoneNumber",
@ -446,3 +447,8 @@ def bar_group(app, admin, slapd_connection):
yield g
admin._groups = []
g.delete(conn=slapd_connection)
@pytest.fixture
def jpeg_photo():
return b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x01,\x01,\x00\x00\xff\xfe\x00\x13Created with GIMP\xff\xe2\x02\xb0ICC_PROFILE\x00\x01\x01\x00\x00\x02\xa0lcms\x040\x00\x00mntrRGB XYZ \x07\xe5\x00\x0c\x00\x08\x00\x0f\x00\x16\x00(acspAPPL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xd6\x00\x01\x00\x00\x00\x00\xd3-lcms\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\rdesc\x00\x00\x01 \x00\x00\x00@cprt\x00\x00\x01`\x00\x00\x006wtpt\x00\x00\x01\x98\x00\x00\x00\x14chad\x00\x00\x01\xac\x00\x00\x00,rXYZ\x00\x00\x01\xd8\x00\x00\x00\x14bXYZ\x00\x00\x01\xec\x00\x00\x00\x14gXYZ\x00\x00\x02\x00\x00\x00\x00\x14rTRC\x00\x00\x02\x14\x00\x00\x00 gTRC\x00\x00\x02\x14\x00\x00\x00 bTRC\x00\x00\x02\x14\x00\x00\x00 chrm\x00\x00\x024\x00\x00\x00$dmnd\x00\x00\x02X\x00\x00\x00$dmdd\x00\x00\x02|\x00\x00\x00$mluc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0cenUS\x00\x00\x00$\x00\x00\x00\x1c\x00G\x00I\x00M\x00P\x00 \x00b\x00u\x00i\x00l\x00t\x00-\x00i\x00n\x00 \x00s\x00R\x00G\x00Bmluc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0cenUS\x00\x00\x00\x1a\x00\x00\x00\x1c\x00P\x00u\x00b\x00l\x00i\x00c\x00 \x00D\x00o\x00m\x00a\x00i\x00n\x00\x00XYZ \x00\x00\x00\x00\x00\x00\xf6\xd6\x00\x01\x00\x00\x00\x00\xd3-sf32\x00\x00\x00\x00\x00\x01\x0cB\x00\x00\x05\xde\xff\xff\xf3%\x00\x00\x07\x93\x00\x00\xfd\x90\xff\xff\xfb\xa1\xff\xff\xfd\xa2\x00\x00\x03\xdc\x00\x00\xc0nXYZ \x00\x00\x00\x00\x00\x00o\xa0\x00\x008\xf5\x00\x00\x03\x90XYZ \x00\x00\x00\x00\x00\x00$\x9f\x00\x00\x0f\x84\x00\x00\xb6\xc4XYZ \x00\x00\x00\x00\x00\x00b\x97\x00\x00\xb7\x87\x00\x00\x18\xd9para\x00\x00\x00\x00\x00\x03\x00\x00\x00\x02ff\x00\x00\xf2\xa7\x00\x00\rY\x00\x00\x13\xd0\x00\x00\n[chrm\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\xa3\xd7\x00\x00T|\x00\x00L\xcd\x00\x00\x99\x9a\x00\x00&g\x00\x00\x0f\\mluc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0cenUS\x00\x00\x00\x08\x00\x00\x00\x1c\x00G\x00I\x00M\x00Pmluc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0cenUS\x00\x00\x00\x08\x00\x00\x00\x1c\x00s\x00R\x00G\x00B\xff\xdb\x00C\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xff\xdb\x00C\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xff\xc2\x00\x11\x08\x00\x01\x00\x01\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\xff\xc4\x00\x14\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03\x01\x00\x02\x10\x03\x10\x00\x00\x01\x7f\x0f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x01\x00\x01\x05\x02\x7f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x03\x01\x01?\x01\x7f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x02\x01\x01?\x01\x7f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x01\x00\x06?\x02\x7f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x01\x00\x01?!\x7f\xff\xda\x00\x0c\x03\x01\x00\x02\x00\x03\x00\x00\x00\x10\x1f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x03\x01\x01?\x10\x7f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x02\x01\x01?\x10\x7f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x01\x00\x01?\x10\x7f\xff\xd9"

View file

@ -1,8 +1,16 @@
from canaille.models import User
from webtest import Upload
def test_edition(
testclient, slapd_server, slapd_connection, logged_user, admin, foo_group, bar_group
testclient,
slapd_server,
slapd_connection,
logged_user,
admin,
foo_group,
bar_group,
jpeg_photo,
):
res = testclient.get("/profile/user", status=200)
assert set(res.form["groups"].options) == set(
@ -27,6 +35,7 @@ def test_edition(
"cn=foo,ou=groups,dc=slapd-test,dc=python-ldap,dc=org",
"cn=bar,ou=groups,dc=slapd-test,dc=python-ldap,dc=org",
]
res.form["jpegPhoto"] = Upload("logo.jpg", jpeg_photo)
res = res.form.submit(name="action", value="edit", status=200)
assert "Profile updated successfuly." in res, str(res)
@ -40,6 +49,7 @@ def test_edition(
assert ["email@mydomain.tld"] == logged_user.mail
assert ["555-666-777"] == logged_user.telephoneNumber
assert "666" == logged_user.employeeNumber
assert [jpeg_photo] == logged_user.jpegPhoto
foo_group.reload(slapd_connection)
bar_group.reload(slapd_connection)
@ -312,3 +322,58 @@ def test_email_reset_button(smtpd, testclient, slapd_connection, logged_admin):
)
assert "Send again" in res
assert len(smtpd.messages) == 1
def test_photo_edition(
testclient,
slapd_server,
slapd_connection,
logged_user,
jpeg_photo,
):
# Add a photo
res = testclient.get("/profile/user", status=200)
res.form["jpegPhoto"] = Upload("logo.jpg", jpeg_photo)
res.form["jpegPhoto_delete"] = False
res = res.form.submit(name="action", value="edit", status=200)
assert "Profile updated successfuly." in res, str(res)
with testclient.app.app_context():
logged_user = User.get(dn=logged_user.dn, conn=slapd_connection)
assert [jpeg_photo] == logged_user.jpegPhoto
# No change
res = testclient.get("/profile/user", status=200)
res.form["jpegPhoto_delete"] = False
res = res.form.submit(name="action", value="edit", status=200)
assert "Profile updated successfuly." in res, str(res)
with testclient.app.app_context():
logged_user = User.get(dn=logged_user.dn, conn=slapd_connection)
assert [jpeg_photo] == logged_user.jpegPhoto
# Photo deletion
res = testclient.get("/profile/user", status=200)
res.form["jpegPhoto_delete"] = True
res = res.form.submit(name="action", value="edit", status=200)
assert "Profile updated successfuly." in res, str(res)
with testclient.app.app_context():
logged_user = User.get(dn=logged_user.dn, conn=slapd_connection)
assert [] == logged_user.jpegPhoto
# Photo deletion AND upload, this should never happen
res = testclient.get("/profile/user", status=200)
res.form["jpegPhoto"] = Upload("logo.jpg", jpeg_photo)
res.form["jpegPhoto_delete"] = True
res = res.form.submit(name="action", value="edit", status=200)
assert "Profile updated successfuly." in res, str(res)
with testclient.app.app_context():
logged_user = User.get(dn=logged_user.dn, conn=slapd_connection)
assert [] == logged_user.jpegPhoto