Renamed user attributes to match SCIM naming convention

This commit is contained in:
Éloi Rivard 2023-02-05 18:57:18 +01:00
parent ff87975601
commit db3a4a74ff
47 changed files with 835 additions and 750 deletions

View file

@ -3,6 +3,12 @@ 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>`_.
Changed
*******
- Renamed user model attributes to match SCIM naming convention. :pr:`123`
🚨Configuration files must be updated. See the changes in the PR.🚨
[0.0.24] - 2023-04-07
=====================

View file

@ -68,7 +68,7 @@ def index():
if user.can_edit_self or user.can_manage_users:
return redirect(
url_for("account.profile_edition", username=current_user().uid[0])
url_for("account.profile_edition", username=current_user().user_name[0])
)
if user.can_use_oidc:
@ -90,7 +90,7 @@ def about():
def login():
if current_user():
return redirect(
url_for("account.profile_edition", username=current_user().uid[0])
url_for("account.profile_edition", username=current_user().user_name[0])
)
form = LoginForm(request.form or None)
@ -99,7 +99,7 @@ def login():
if request.form:
user = User.get(form.login.data)
if user and not user.has_password():
return redirect(url_for("account.firstlogin", uid=user.uid[0]))
return redirect(url_for("account.firstlogin", user_name=user.user_name[0]))
if not form.validate():
User.logout()
@ -122,7 +122,7 @@ def password():
if request.form:
user = User.get(session["attempt_login"])
if user and not user.has_password():
return redirect(url_for("account.firstlogin", uid=user.uid[0]))
return redirect(url_for("account.firstlogin", user_name=user.user_name[0]))
if not form.validate() or not User.authenticate(
session["attempt_login"], form.password.data, True
@ -154,17 +154,18 @@ def logout():
return redirect("/")
@bp.route("/firstlogin/<uid>", methods=("GET", "POST"))
def firstlogin(uid):
user = User.get(uid)
@bp.route("/firstlogin/<user_name>", methods=("GET", "POST"))
def firstlogin(user_name):
user = User.get(user_name)
if not user or user.has_password():
abort(404)
form = FirstLoginForm(request.form or None)
if not request.form:
return render_template("firstlogin.html", form=form, uid=uid)
return render_template("firstlogin.html", form=form, user_name=user_name)
form.validate()
if send_password_initialization_mail(user):
flash(
_(
@ -175,7 +176,7 @@ def firstlogin(uid):
else:
flash(_("Could not send the password initialization email"), "error")
return render_template("firstlogin.html", form=form, uid=uid)
return render_template("firstlogin.html", form=form, user_name=user_name)
@bp.route("/users", methods=["GET", "POST"])
@ -195,9 +196,9 @@ def users(user):
@dataclass
class Invitation:
creation_date_isoformat: str
uid: str
uid_editable: bool
mail: str
user_name: str
user_name_editable: bool
email: str
groups: List[str]
@property
@ -227,16 +228,16 @@ class Invitation:
def user_invitation(user):
form = InvitationForm(request.form or None)
mail_sent = None
email_sent = None
registration_url = None
form_validated = False
if request.form and form.validate():
form_validated = True
invitation = Invitation(
datetime.datetime.now(datetime.timezone.utc).isoformat(),
form.uid.data,
form.uid_editable.data,
form.mail.data,
form.user_name.data,
form.user_name_editable.data,
form.email.data,
form.groups.data,
)
registration_url = url_for(
@ -247,14 +248,14 @@ def user_invitation(user):
)
if request.form["action"] == "send":
mail_sent = send_invitation_mail(form.mail.data, registration_url)
email_sent = send_invitation_mail(form.email.data, registration_url)
return render_template(
"invite.html",
form=form,
menuitems="users",
form_validated=form_validated,
mail_sent=mail_sent,
email_sent=email_sent,
registration_url=registration_url,
)
@ -277,7 +278,7 @@ def registration(data, hash):
)
return redirect(url_for("account.index"))
if User.get(invitation.uid):
if User.get(invitation.user_name):
flash(
_("Your account has already been created."),
"error",
@ -298,7 +299,11 @@ def registration(data, hash):
)
return redirect(url_for("account.index"))
data = {"uid": invitation.uid, "mail": invitation.mail, "groups": invitation.groups}
data = {
"user_name": invitation.user_name,
"email": invitation.email,
"groups": invitation.groups,
}
readable_fields, writable_fields = default_fields()
@ -311,8 +316,8 @@ def registration(data, hash):
)
form.process(CombinedMultiDict((request.files, request.form)) or None, data=data)
if "readonly" in form["uid"].render_kw and invitation.uid_editable:
del form["uid"].render_kw["readonly"]
if "readonly" in form["user_name"].render_kw and invitation.user_name_editable:
del form["user_name"].render_kw["readonly"]
form["password1"].validators = [
wtforms.validators.DataRequired(),
@ -333,7 +338,9 @@ def registration(data, hash):
user = profile_create(current_app, form)
user.login()
flash(_("Your account has been created successfuly."), "success")
return redirect(url_for("account.profile_edition", username=user.uid[0]))
return redirect(
url_for("account.profile_edition", username=user.user_name[0])
)
return render_template(
"profile_add.html",
@ -360,7 +367,9 @@ def profile_creation(user):
else:
user = profile_create(current_app, form)
return redirect(url_for("account.profile_edition", username=user.uid[0]))
return redirect(
url_for("account.profile_edition", username=user.user_name[0])
)
return render_template(
"profile_add.html",
@ -374,7 +383,7 @@ def profile_creation(user):
def profile_create(current_app, form):
user = User()
for attribute in form:
if attribute.name in user.may() + user.must():
if attribute.name in user.attribute_table:
if isinstance(attribute.data, FileStorage):
data = attribute.data.stream.read()
else:
@ -382,10 +391,10 @@ def profile_create(current_app, form):
user[attribute.name] = data
if "jpegPhoto" in form and form["jpegPhoto_delete"].data:
user["jpegPhoto"] = None
if "photo" in form and form["photo_delete"].data:
user["photo"] = None
user.cn = [f"{user.givenName[0]} {user.sn[0]}".strip()]
user.formatted_name = [f"{user.given_name[0]} {user.family_name[0]}".strip()]
user.save()
if "groups" in form:
@ -408,13 +417,13 @@ def profile_create(current_app, form):
def profile_edition(user, username):
editor = user
if not user.can_manage_users and not (
user.can_edit_self and username == user.uid[0]
user.can_edit_self and username == user.user_name[0]
):
abort(403)
menuitem = "profile" if username == editor.uid[0] else "users"
menuitem = "profile" if username == editor.user_name[0] else "users"
fields = editor.read | editor.write
if username != editor.uid[0]:
if username != editor.user_name[0]:
user = User.get(username)
else:
user = editor
@ -423,25 +432,25 @@ def profile_edition(user, username):
abort(404)
available_fields = {
"cn",
"formatted_name",
"title",
"givenName",
"sn",
"displayName",
"mail",
"telephoneNumber",
"postalAddress",
"given_name",
"family_name",
"display_name",
"email",
"phone_number",
"formatted_address",
"street",
"postalCode",
"l",
"st",
"jpegPhoto",
"jpegPhoto_delete",
"employeeNumber",
"departmentNumber",
"labeledURI",
"preferredLanguage",
"o",
"postal_code",
"locality",
"region",
"photo",
"photo_delete",
"employee_number",
"department",
"profile_url",
"preferred_language",
"organization",
}
data = {
k: getattr(user, k)[0]
@ -463,7 +472,7 @@ def profile_edition(user, username):
else:
for attribute in form:
if (
attribute.name in user.may() + user.must()
attribute.name in user.attribute_table
and attribute.name in editor.write
):
if isinstance(attribute.data, FileStorage):
@ -473,15 +482,15 @@ def profile_edition(user, username):
user[attribute.name] = data
if "jpegPhoto" in form and form["jpegPhoto_delete"].data:
user["jpegPhoto"] = None
if "photo" in form and form["photo_delete"].data:
user["photo"] = None
if "preferredLanguage" in request.form:
if "preferred_language" in request.form:
# Refresh the babel cache in case the lang is updated
refresh()
if form["preferredLanguage"].data == "auto":
user.preferredLanguage = None
if form["preferred_language"].data == "auto":
user.preferred_language = None
user.save()
flash(_("Profile updated successfuly."), "success")
@ -499,7 +508,7 @@ def profile_edition(user, username):
@user_needed()
def profile_settings(user, username):
if not user.can_manage_users and not (
user.can_edit_self and username == user.uid[0]
user.can_edit_self and username == user.user_name[0]
):
abort(403)
@ -550,7 +559,7 @@ def profile_settings_edit(editor, edited_user):
menuitem = "profile" if editor.id == editor.id else "users"
fields = editor.read | editor.write
available_fields = {"userPassword", "groups", "uid"}
available_fields = {"password", "groups", "user_name"}
data = {
k: getattr(edited_user, k)[0]
if getattr(edited_user, k) and isinstance(getattr(edited_user, k), list)
@ -586,7 +595,7 @@ def profile_settings_edit(editor, edited_user):
edited_user.save()
flash(_("Profile updated successfuly."), "success")
return redirect(
url_for("account.profile_edition", username=edited_user.uid[0])
url_for("account.profile_edition", username=edited_user.user_name[0])
)
return render_template(
@ -675,16 +684,18 @@ def forgotten():
return render_template("forgotten-password.html", form=form)
@bp.route("/reset/<uid>/<hash>", methods=["GET", "POST"])
def reset(uid, hash):
@bp.route("/reset/<user_name>/<hash>", methods=["GET", "POST"])
def reset(user_name, hash):
if not current_app.config.get("ENABLE_PASSWORD_RECOVERY", True):
abort(404)
form = PasswordResetForm(request.form)
user = User.get(uid)
user = User.get(user_name)
if not user or hash != profile_hash(
user.uid[0], user.mail[0], user.userPassword[0] if user.has_password() else ""
user.user_name[0],
user.email[0],
user.password[0] if user.has_password() else "",
):
flash(
_("The password reset link that brought you here was invalid."),
@ -697,25 +708,27 @@ def reset(uid, hash):
user.login()
flash(_("Your password has been updated successfuly"), "success")
return redirect(url_for("account.profile_edition", username=uid))
return redirect(url_for("account.profile_edition", username=user_name))
return render_template("reset-password.html", form=form, uid=uid, hash=hash)
return render_template(
"reset-password.html", form=form, user_name=user_name, hash=hash
)
@bp.route("/profile/<uid>/<field>")
def photo(uid, field):
if field.lower() != "jpegphoto":
@bp.route("/profile/<user_name>/<field>")
def photo(user_name, field):
if field.lower() != "photo":
abort(404)
user = User.get(uid)
user = User.get(user_name)
if not user:
abort(404)
etag = None
if request.if_modified_since and request.if_modified_since >= user.modifyTimestamp:
if request.if_modified_since and request.if_modified_since >= user.last_modified:
return "", 304
etag = profile_hash(uid, user.modifyTimestamp.isoformat())
etag = profile_hash(user_name, user.last_modified.isoformat())
if request.if_none_match and etag in request.if_none_match:
return "", 304
@ -725,5 +738,5 @@ def photo(uid, field):
stream = io.BytesIO(photos[0])
return send_file(
stream, mimetype="image/jpeg", last_modified=user.modifyTimestamp, etag=etag
stream, mimetype="image/jpeg", last_modified=user.last_modified, etag=etag
)

View file

@ -19,7 +19,7 @@ bp = Blueprint("admin", __name__, url_prefix="/admin")
class MailTestForm(HTMXForm):
mail = StringField(
email = StringField(
_("Email"),
validators=[
DataRequired(),
@ -38,7 +38,7 @@ class MailTestForm(HTMXForm):
def mail_index(user):
form = MailTestForm(request.form or None)
if request.form and form.validate():
if send_test_mail(form.mail.data):
if send_test_mail(form.email.data):
flash(_("The test invitation mail has been sent correctly"), "success")
else:
flash(_("The test invitation mail has not been sent correctly"), "error")
@ -75,8 +75,8 @@ def password_init_html(user):
base_url = url_for("account.index", _external=True)
reset_url = url_for(
"account.reset",
uid=user.uid[0],
hash=profile_hash(user.uid[0], user.mail[0], user.userPassword[0]),
user_name=user.user_name[0],
hash=profile_hash(user.user_name[0], user.email[0], user.password[0]),
_external=True,
)
@ -98,8 +98,8 @@ def password_init_txt(user):
base_url = url_for("account.index", _external=True)
reset_url = url_for(
"account.reset",
uid=user.uid[0],
hash=profile_hash(user.uid[0], user.mail[0], user.userPassword[0]),
user_name=user.user_name[0],
hash=profile_hash(user.user_name[0], user.email[0], user.password[0]),
_external=True,
)
@ -117,8 +117,8 @@ def password_reset_html(user):
base_url = url_for("account.index", _external=True)
reset_url = url_for(
"account.reset",
uid=user.uid[0],
hash=profile_hash(user.uid[0], user.mail[0], user.userPassword[0]),
user_name=user.user_name[0],
hash=profile_hash(user.user_name[0], user.email[0], user.password[0]),
_external=True,
)
@ -140,8 +140,8 @@ def password_reset_txt(user):
base_url = url_for("account.index", _external=True)
reset_url = url_for(
"account.reset",
uid=user.uid[0],
hash=profile_hash(user.uid[0], user.mail[0], user.userPassword[0]),
user_name=user.user_name[0],
hash=profile_hash(user.user_name[0], user.email[0], user.password[0]),
_external=True,
)
@ -153,14 +153,14 @@ def password_reset_txt(user):
)
@bp.route("/mail/<uid>/<email>/invitation.html")
@bp.route("/mail/<user_name>/<email>/invitation.html")
@permissions_needed("manage_oidc")
def invitation_html(user, uid, email):
def invitation_html(user, user_name, email):
base_url = url_for("account.index", _external=True)
registration_url = url_for(
"account.registration",
data=obj_to_b64([uid, email]),
hash=profile_hash(uid, email),
data=obj_to_b64([user_name, email]),
hash=profile_hash(user_name, email),
_external=True,
)
@ -176,14 +176,14 @@ def invitation_html(user, uid, email):
)
@bp.route("/mail/<uid>/<email>/invitation.txt")
@bp.route("/mail/<user_name>/<email>/invitation.txt")
@permissions_needed("manage_oidc")
def invitation_txt(user, uid, email):
def invitation_txt(user, user_name, email):
base_url = url_for("account.index", _external=True)
registration_url = url_for(
"account.registration",
data=obj_to_b64([uid, email]),
hash=profile_hash(uid, email),
data=obj_to_b64([user_name, email]),
hash=profile_hash(user_name, email),
_external=True,
)

View file

@ -130,26 +130,26 @@ GROUP_BASE = "ou=groups,dc=mydomain,dc=tld"
# object that users will be able to read and/or write.
[ACL.DEFAULT]
PERMISSIONS = ["edit_self", "use_oidc"]
READ = ["uid", "groups"]
READ = ["user_name", "groups"]
WRITE = [
"jpegPhoto",
"givenName",
"sn",
"displayName",
"userPassword",
"telephoneNumber",
"mail",
"labeledURI",
"postalAddress",
"photo",
"given_name",
"family_name",
"display_name",
"password",
"phone_number",
"email",
"profile_url",
"formatted_address",
"street",
"postalCode",
"l",
"st",
"preferredLanguage",
"employeeNumber",
"departmentNumber",
"postal_code",
"locality",
"region",
"preferred_language",
"employee_number",
"department",
"title",
"o",
"organization",
]
[ACL.ADMIN]
@ -185,17 +185,17 @@ PUBLIC_KEY = "canaille/conf/public.pem"
# User objectClass.
# {attribute} will be replaced by the user ldap attribute value.
# Default values fits inetOrgPerson.
SUB = "{{ user.uid[0] }}"
NAME = "{{ user.cn[0] }}"
PHONE_NUMBER = "{{ user.telephoneNumber[0] }}"
SUB = "{{ user.user_name[0] }}"
NAME = "{{ user.formatted_name[0] }}"
PHONE_NUMBER = "{{ user.phone_number[0] }}"
EMAIL = "{{ user.mail[0] }}"
GIVEN_NAME = "{{ user.givenName[0] }}"
FAMILY_NAME = "{{ user.sn[0] }}"
PREFERRED_USERNAME = "{{ user.displayName }}"
LOCALE = "{{ user.preferredLanguage }}"
ADDRESS = "{{ user.postalAddress[0] }}"
PICTURE = "{% if user.jpegPhoto %}{{ url_for('account.photo', uid=user.uid[0], field='jpegPhoto', _external=True) }}{% endif %}"
WEBSITE = "{{ user.labeledURI[0] }}"
GIVEN_NAME = "{{ user.given_name[0] }}"
FAMILY_NAME = "{{ user.family_name[0] }}"
PREFERRED_USERNAME = "{{ user.display_name }}"
LOCALE = "{{ user.preferred_language }}"
ADDRESS = "{{ user.formatted_address[0] }}"
PICTURE = "{% if user.photo %}{{ url_for('account.photo', user_name=user.user_name[0], field='photo', _external=True) }}{% endif %}"
WEBSITE = "{{ user.profile_url[0] }}"
# The SMTP server options. If not set, mail related features such as
# user invitations, and password reset emails, will be disabled.

View file

@ -122,16 +122,16 @@ def available_language_choices():
PROFILE_FORM_FIELDS = dict(
uid=wtforms.StringField(
user_name=wtforms.StringField(
_("Username"),
render_kw={"placeholder": _("jdoe")},
validators=[wtforms.validators.DataRequired(), unique_login],
),
cn=wtforms.StringField(_("Name")),
formatted_name=wtforms.StringField(_("Name")),
title=wtforms.StringField(
_("Title"), render_kw={"placeholder": _("Vice president")}
),
givenName=wtforms.StringField(
given_name=wtforms.StringField(
_("Given name"),
render_kw={
"placeholder": _("John"),
@ -139,7 +139,7 @@ PROFILE_FORM_FIELDS = dict(
"autocorrect": "off",
},
),
sn=wtforms.StringField(
family_name=wtforms.StringField(
_("Family Name"),
validators=[wtforms.validators.DataRequired()],
render_kw={
@ -148,7 +148,7 @@ PROFILE_FORM_FIELDS = dict(
"autocorrect": "off",
},
),
displayName=wtforms.StringField(
display_name=wtforms.StringField(
_("Display Name"),
validators=[wtforms.validators.Optional()],
render_kw={
@ -157,7 +157,7 @@ PROFILE_FORM_FIELDS = dict(
"autocorrect": "off",
},
),
mail=wtforms.EmailField(
email=wtforms.EmailField(
_("Email address"),
validators=[
wtforms.validators.DataRequired(),
@ -173,10 +173,10 @@ PROFILE_FORM_FIELDS = dict(
"autocorrect": "off",
},
),
telephoneNumber=wtforms.TelField(
phone_number=wtforms.TelField(
_("Phone number"), render_kw={"placeholder": _("555-000-555")}
),
postalAddress=wtforms.StringField(
formatted_address=wtforms.StringField(
_("Address"),
render_kw={
"placeholder": _("132, Foobar Street, Gotham City 12401, XX"),
@ -188,30 +188,30 @@ PROFILE_FORM_FIELDS = dict(
"placeholder": _("132, Foobar Street"),
},
),
postalCode=wtforms.StringField(
postal_code=wtforms.StringField(
_("Postal Code"),
render_kw={
"placeholder": "12401",
},
),
l=wtforms.StringField(
locality=wtforms.StringField(
_("Locality"),
render_kw={
"placeholder": _("Gotham City"),
},
),
st=wtforms.StringField(
region=wtforms.StringField(
_("Region"),
render_kw={
"placeholder": _("North Pole"),
},
),
jpegPhoto=FileField(
photo=FileField(
_("Photo"),
validators=[FileAllowed(["jpg", "jpeg"])],
render_kw={"accept": "image/jpg, image/jpeg"},
),
jpegPhoto_delete=wtforms.BooleanField(_("Delete the photo")),
photo_delete=wtforms.BooleanField(_("Delete the photo")),
password1=wtforms.PasswordField(
_("Password"),
validators=[wtforms.validators.Optional(), wtforms.validators.Length(min=8)],
@ -230,32 +230,32 @@ PROFILE_FORM_FIELDS = dict(
"autocomplete": "new-password",
},
),
employeeNumber=wtforms.StringField(
employee_number=wtforms.StringField(
_("User number"),
render_kw={
"placeholder": _("1234"),
},
),
departmentNumber=wtforms.StringField(
_("Department number"),
department=wtforms.StringField(
_("Department"),
render_kw={
"placeholder": _("1234"),
},
),
o=wtforms.StringField(
organization=wtforms.StringField(
_("Organization"),
render_kw={
"placeholder": _("Cogip LTD."),
},
),
labeledURI=wtforms.URLField(
profile_url=wtforms.URLField(
_("Website"),
render_kw={
"placeholder": _("https://mywebsite.tld"),
},
validators=[wtforms.validators.Optional(), is_uri],
),
preferredLanguage=wtforms.SelectField(
preferred_language=wtforms.SelectField(
_("Preferred language"),
choices=available_language_choices,
),
@ -268,11 +268,11 @@ PROFILE_FORM_FIELDS = dict(
def profile_form(write_field_names, readonly_field_names, user=None):
if "userPassword" in write_field_names:
if "password" in write_field_names:
write_field_names |= {"password1", "password2"}
if "jpegPhoto" in write_field_names:
write_field_names |= {"jpegPhoto_delete"}
if "photo" in write_field_names:
write_field_names |= {"photo_delete"}
fields = {
name: PROFILE_FORM_FIELDS.get(name)
@ -321,13 +321,13 @@ class EditGroupForm(HTMXForm):
class InvitationForm(HTMXForm):
uid = wtforms.StringField(
user_name = wtforms.StringField(
_("Username"),
render_kw={"placeholder": _("jdoe")},
validators=[wtforms.validators.DataRequired(), unique_login],
)
uid_editable = wtforms.BooleanField(_("Username editable by the invitee"))
mail = wtforms.EmailField(
user_name_editable = wtforms.BooleanField(_("Username editable by the invitee"))
email = wtforms.EmailField(
_("Email address"),
validators=[
wtforms.validators.DataRequired(),

View file

@ -132,11 +132,11 @@ def validate_configuration(config):
try:
User.ldap_object_classes(conn)
user = User(
cn=f"canaille_{uuid.uuid4()}",
sn=f"canaille_{uuid.uuid4()}",
uid=f"canaille_{uuid.uuid4()}",
mail=f"canaille_{uuid.uuid4()}@mydomain.tld",
userPassword="{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz",
formatted_name=f"canaille_{uuid.uuid4()}",
family_name=f"canaille_{uuid.uuid4()}",
user_name=f"canaille_{uuid.uuid4()}",
email=f"canaille_{uuid.uuid4()}@mydomain.tld",
password="{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz",
)
user.save(conn)
user.delete(conn)
@ -152,10 +152,10 @@ def validate_configuration(config):
user = User(
cn=f"canaille_{uuid.uuid4()}",
sn=f"canaille_{uuid.uuid4()}",
uid=f"canaille_{uuid.uuid4()}",
mail=f"canaille_{uuid.uuid4()}@mydomain.tld",
userPassword="{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz",
family_name=f"canaille_{uuid.uuid4()}",
user_name=f"canaille_{uuid.uuid4()}",
email=f"canaille_{uuid.uuid4()}@mydomain.tld",
password="{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz",
)
user.save(conn)

View file

@ -43,7 +43,7 @@ def ldap_to_python(value, syntax):
if syntax == Syntax.INTEGER:
return int(value.decode("utf-8"))
if syntax == Syntax.JPEG:
if syntax in (Syntax.JPEG, Syntax.FAX_IMAGE):
return value
if syntax == Syntax.BOOLEAN:
@ -68,7 +68,7 @@ def python_to_ldap(value, syntax, encode=True):
if syntax == Syntax.INTEGER and isinstance(value, int):
value = str(value)
if syntax == Syntax.JPEG:
if syntax in (Syntax.JPEG, Syntax.FAX_IMAGE):
encodable = False
if syntax == Syntax.BOOLEAN and isinstance(value, bool):

View file

@ -134,11 +134,11 @@ def send_password_reset_mail(user):
base_url = url_for("account.index", _external=True)
reset_url = url_for(
"account.reset",
uid=user.uid[0],
user_name=user.user_name[0],
hash=profile_hash(
user.uid[0],
user.mail[0],
user.userPassword[0] if user.has_password() else "",
user.user_name[0],
user.email[0],
user.password[0] if user.has_password() else "",
),
_external=True,
)
@ -163,7 +163,7 @@ def send_password_reset_mail(user):
return send_email(
subject=subject,
recipient=user.mail[0],
recipient=user.email[0],
text=text_body,
html=html_body,
attachements=[(logo_cid, logo_filename, logo_raw)] if logo_filename else None,
@ -174,11 +174,11 @@ def send_password_initialization_mail(user):
base_url = url_for("account.index", _external=True)
reset_url = url_for(
"account.reset",
uid=user.uid[0],
user_name=user.user_name[0],
hash=profile_hash(
user.uid[0],
user.mail[0],
user.userPassword[0] if user.has_password() else "",
user.user_name[0],
user.email[0],
user.password[0] if user.has_password() else "",
),
_external=True,
)
@ -203,7 +203,7 @@ def send_password_initialization_mail(user):
return send_email(
subject=subject,
recipient=user.mail[0],
recipient=user.email[0],
text=text_body,
html=html_body,
attachements=[(logo_cid, logo_filename, logo_raw)] if logo_filename else None,

View file

@ -12,6 +12,27 @@ class User(LDAPObject):
attribute_table = {
"id": "dn",
"user_name": "uid",
"password": "userPassword",
"preferred_language": "preferredLanguage",
"family_name": "sn",
"given_name": "givenName",
"formatted_name": "cn",
"display_name": "displayName",
"email": "mail",
"phone_number": "telephoneNumber",
"formatted_address": "postalAddress",
"street": "street",
"postal_code": "postalCode",
"locality": "l",
"region": "st",
"photo": "jpegPhoto",
"profile_url": "labeledURI",
"employee_number": "employeeNumber",
"department": "departmentNumber",
"title": "title",
"organization": "o",
"last_modified": "modifyTimestamp",
}
def __init__(self, *args, **kwargs):
@ -79,7 +100,7 @@ class User(LDAPObject):
pass
def has_password(self):
return bool(self.userPassword)
return bool(self.password)
def check_password(self, password):
conn = ldap.initialize(current_app.config["LDAP"]["URI"])
@ -106,7 +127,7 @@ class User(LDAPObject):
@property
def name(self):
return self.cn[0]
return self.formatted_name[0]
@property
def groups(self):

View file

@ -276,7 +276,7 @@ def end_session():
if (
not data.get("id_token_hint")
or (data.get("logout_hint") and data["logout_hint"] != user.uid[0])
or (data.get("logout_hint") and data["logout_hint"] != user.user_name[0])
) and not session.get("end_session_confirmation"):
session["end_session_data"] = data
return render_template(
@ -316,7 +316,7 @@ def end_session():
if client:
valid_uris.extend(client.post_logout_redirect_uris or [])
if user.uid[0] != id_token["sub"] and not session.get(
if user.user_name[0] != id_token["sub"] and not session.get(
"end_session_confirmation"
):
session["end_session_data"] = data

View file

@ -297,7 +297,7 @@ class IntrospectionEndpoint(_IntrospectionEndpoint):
"token_type": token.type,
"username": token.subject.name,
"scope": token.get_scope(),
"sub": token.subject.uid[0],
"sub": token.subject.user_name[0],
"aud": audience,
"iss": get_issuer(),
"exp": token.get_expires_at(),

View file

@ -21,23 +21,23 @@ def fake_users(nb=1):
fake = random.choice(fakes)
name = fake.unique.name()
user = User(
cn=name,
givenName=name.split(" ")[0],
sn=name.split(" ")[1],
uid=fake.unique.user_name(),
mail=fake.unique.email(),
telephoneNumber=fake.unique.ssn(),
labeledURI=fake.unique.uri(),
postalAddress=fake.unique.address(),
formatted_name=name,
given_name=name.split(" ")[0],
family_name=name.split(" ")[1],
user_name=fake.unique.user_name(),
email=fake.unique.email(),
phone_number=fake.unique.ssn(),
profile_url=fake.unique.uri(),
address=fake.unique.address(),
street=fake.street_name(),
postalCode=fake.postcode(),
l=fake.city(),
st=fake.state(),
employeeNumber=str(fake.unique.random_number()),
departmentNumber=fake.word(),
postal_code=fake.postcode(),
locality=fake.city(),
region=fake.state(),
employee_number=str(fake.unique.random_number()),
department=fake.word(),
title=fake.job(),
userPassword=fake.password(),
preferredLanguage=fake._locales[0],
password=fake.password(),
preferred_language=fake._locales[0],
)
user.save()
users.append(user)

View file

@ -35,7 +35,7 @@
<div class="content">
{% if request.form["action"] == "generate" %}
{{ _("Invitation link") }}
{% elif mail_sent %}
{% elif email_sent %}
{{ _("Invitation sent") }}
{% else %}
{{ _("Invitation not sent") }}
@ -49,18 +49,18 @@
{% trans %}Here is the invitation link you can provide to the user you want to invite:{% endtrans %}
</div>
{% elif mail_sent %}
{% elif email_sent %}
<div class="ui success message">
{% set email = form.mail.data %}
<div class="ui attached success message">
{% set email = form.email.data %}
{% trans %}This invitation link has been sent to {{ email }}{% endtrans %}
{% trans %}If you need to provide this link by other ways than email, you can copy it there:{% endtrans %}
</div>
{% else %}
<div class="ui error message">
{% set email = form.mail.data %}
<div class="ui attached error message">
{% set email = form.email.data %}
{% trans %}This invitation link could not be sent to {{ email }} due to technical issues.{% endtrans %}
{% trans %}However you can copy the link there to provide it by other ways than email:{% endtrans %}
</div>
@ -82,7 +82,7 @@
<a class="ui right floated button" href="{{ url_for("account.profile_creation") }}">
{{ _("Create a user") }}
</a>
{% if request.form["action"] == "generate" or mail_sent %}
{% if request.form["action"] == "generate" or email_sent %}
<a href="{{ url_for('account.user_invitation') }}" class="ui right floated button" name="action" value="invite" id="invite">
{{ _("Invite another user") }}
</a>
@ -108,16 +108,16 @@
</div>
{% call fui.render_form(form) %}
{% block uid_field scoped %}
{{ fui.render_field(form.uid, icon="user") }}
{% block user_name_field scoped %}
{{ fui.render_field(form.user_name, icon="user") }}
{% endblock %}
{% block uid_editable_field scoped %}
{{ fui.render_checkbox(form.uid_editable) }}
{% block user_name_editable_field scoped %}
{{ fui.render_checkbox(form.user_name_editable) }}
{% endblock %}
{% block mail_field scoped %}
{{ fui.render_field(form.mail, icon="mail") }}
{% block email_field scoped %}
{{ fui.render_field(form.email, icon="email") }}
{% endblock %}
{% block groups_field scoped %}

View file

@ -46,7 +46,7 @@
</div>
{% call fui.render_form(form) %}
{{ fui.render_field(form.mail) }}
{{ fui.render_field(form.email) }}
<div class="ui right aligned container">
<div class="ui stackable buttons">
<input type="submit" class="ui primary button" value="{{ _("Send") }}">
@ -101,8 +101,8 @@
<div class="item">
<div class="right floated content">
<div class="ui buttons">
<a class="ui button primary" href="{{ url_for("admin.invitation_txt", uid=user.uid, email=user.mail[0]) }}">TXT</a>
<a class="ui button primary" href="{{ url_for("admin.invitation_html", uid=user.uid, email=user.mail[0]) }}">HTML</a>
<a class="ui button primary" href="{{ url_for("admin.invitation_txt", user_name=user.user_name, email=user.email[0]) }}">TXT</a>
<a class="ui button primary" href="{{ url_for("admin.invitation_html", user_name=user.user_name, email=user.email[0]) }}">HTML</a>
</div>
</div>
<div class="middle aligned content">

View file

@ -70,8 +70,8 @@
<tr>
<td>{{ _("Subject") }}</td>
<td>
<a href="{{ url_for("account.profile_edition", username=token.subject.uid[0]) }}">
{{ token.subject.name }} - {{ token.subject.uid[0] }}
<a href="{{ url_for("account.profile_edition", username=token.subject.user_name[0]) }}">
{{ token.subject.name }} - {{ token.subject.user_name[0] }}
</a>
</td>
</tr>

View file

@ -22,8 +22,8 @@
</a>
</td>
<td>
<a href="{{ url_for("account.profile_edition", username=token.subject.uid[0]) }}">
{{ token.subject.uid[0] }}
<a href="{{ url_for("account.profile_edition", username=token.subject.user_name[0]) }}">
{{ token.subject.user_name[0] }}
</a>
</td>
<td>{{ token.issue_date }}</td>

View file

@ -2,16 +2,16 @@
<table class="ui bottom attached table users">
<thead>
<tr>
{% if user.can_read("jpegPhoto") %}
{% if user.can_read("photo") %}
<th></th>
{% endif %}
{% if user.can_read("uid") %}
{% if user.can_read("user_name") %}
<th>{% trans %}Login{% endtrans %}</th>
{% endif %}
{% if user.can_read("sn") or user.can_read("givenName") %}
{% if user.can_read("family_name") or user.can_read("given_name") %}
<th>{% trans %}Name{% endtrans %}</th>
{% endif %}
{% if user.can_read("mail") %}
{% if user.can_read("email") %}
<th>{% trans %}Email{% endtrans %}</th>
{% endif %}
{% if user.can_manage_groups %}
@ -22,25 +22,25 @@
<tbody>
{% for watched_user in table_form.items_slice %}
<tr>
{% if user.can_read("jpegPhoto") %}
{% if user.can_read("photo") %}
<td>
<a href="{{ url_for('account.profile_edition', username=watched_user.uid[0]) }}">
{% if watched_user.jpegPhoto and watched_user.jpegPhoto[0] %}
<img class="ui avatar image" src="{{ url_for("account.photo", uid=watched_user.uid[0], field="jpegPhoto") }}" alt="User photo">
<a href="{{ url_for('account.profile_edition', username=watched_user.user_name[0]) }}">
{% if watched_user.photo and watched_user.photo[0] %}
<img class="ui avatar image" src="{{ url_for("account.photo", user_name=watched_user.user_name[0], field="photo") }}" alt="User photo">
{% else %}
<i class="user circle big black icon"></i>
{% endif %}
</a>
</td>
{% endif %}
{% if user.can_read("uid") %}
<td><a href="{{ url_for('account.profile_edition', username=watched_user.uid[0]) }}">{{ watched_user.uid[0] }}</a></td>
{% if user.can_read("user_name") %}
<td><a href="{{ url_for('account.profile_edition', username=watched_user.user_name[0]) }}">{{ watched_user.user_name[0] }}</a></td>
{% endif %}
{% if user.can_read("sn") or user.can_read("givenName") %}
{% if user.can_read("family_name") or user.can_read("given_name") %}
<td>{{ watched_user.name }}</td>
{% endif %}
{% if user.can_read("mail") %}
<td><a href="mailto:{{ watched_user.mail[0] }}">{{ watched_user.mail[0] }}</a></td>
{% if user.can_read("email") %}
<td><a href="mailto:{{ watched_user.email[0] }}">{{ watched_user.email[0] }}</a></td>
{% endif %}
{% if user.can_manage_groups %}
<td>

View file

@ -30,33 +30,79 @@
{% endblock %}
{% macro render_field(field, noindicator=false) %}
{{ fui.render_field(field, **kwargs) }}
{% set lock_indicator = field.render_kw and ("readonly" in field.render_kw or "disabled" in field.render_kw) %}
{% if not edited_user %}
{{ fui.render_field(field, **kwargs) }}
{% elif edited_user.user_name == user.user_name or lock_indicator or noindicator %}
{{ fui.render_field(field, **kwargs) }}
{% elif field.name in edited_user.write %}
{{ fui.render_field(field, **kwargs) }}
{% elif field.name in edited_user.read %}
{{ fui.render_field(field, indicator_icon="eye", indicator_text=_("This user cannot edit this field"), **kwargs) }}
{% else %}
{{ fui.render_field(field, indicator_icon="eye slash", indicator_text=_("This user cannot see this field"), **kwargs) }}
{% endif %}
{% endmacro %}
{% block content %}
{% if self_deletion or (user.can_manage_users and edited_user) %}
<div id="modal-delete" class="ui basic modal">
<div class="ui icon header">
<i class="user minus icon"></i>
{% trans %}Account deletion{% endtrans %}
</div>
<div class="content">
<p>
{% if user.user_name != edited_user.user_name %}
{{ _("Are you sure you want to delete this user? This action is unrevokable and all the data about this user will be removed.") }}
{% else %}
{{ _("Are you sure you want to delete your account? This action is unrevokable and all your data will be removed forever.") }}
{% endif %}
</p>
</div>
<div class="actions">
<div class="ui inverted cancel button">{% trans %}Cancel{% endtrans %}</div>
<div class="ui inverted red approve button">{% trans %}Delete{% endtrans %}</div>
</div>
</div>
{% endif %}
<div class="ui clearing segment">
<h2 class="ui center aligned header">
<div class="content">
{% trans %}User creation{% endtrans %}
{% if not edited_user %}
{% trans %}User creation{% endtrans %}
{% elif user.user_name == edited_user.user_name %}
{% trans %}My profile{% endtrans %}
{% else %}
{% trans %}User profile edition{% endtrans %}
{% endif %}
</div>
<div class="sub header">
{% trans %}Create a new user account{% endtrans %}
{% if not edited_user %}
{% trans %}Create a new user account{% endtrans %}
{% elif user.user_name == edited_user.user_name %}
{% trans %}Edit your personal information{% endtrans %}
{% else %}
{% trans %}Edit information about a user{% endtrans %}
{% endif %}
</div>
</h2>
{% call fui.render_form(form, class_="profile-form info") %}
<h4 class="ui dividing header">{% trans %}Personal information{% endtrans %}</h4>
{% if "jpegPhoto" in form %}
{% if "photo" in form %}
<div class="ui grid">
<div class="three wide column">
{% block photo_field scoped %}
{{ render_field(form.jpegPhoto, display=false, class="photo-field") }}
{{ render_field(form.jpegPhoto_delete, display=false, class="photo-delete-button") }}
{{ render_field(form.photo, display=false, class="photo-field") }}
{{ render_field(form.photo_delete, display=false, class="photo-delete-button") }}
{% set photo = edited_user.photo and edited_user.photo[0] %}
<label
class="ui small bordered image photo-content"
for="{{ form.jpegPhoto.id }}"
for="{{ form.photo.id }}"
title="{{ _("Click to upload a photo") }}"
style="display: none;">
@ -67,7 +113,7 @@
</label>
<label
class="ui centered photo-placeholder"
for="{{ form.jpegPhoto.id }}"
for="{{ form.photo.id }}"
title="{{ _("Click to upload a photo") }}"
{% if photo %}style="display: none;"{% endif %}>
<i class="massive centered portrait icon"></i>
@ -78,34 +124,34 @@
<div class="thirteen wide column">
{% endif %}
{% if "givenName" in form or "sn" in form %}
{% if "given_name" in form or "family_name" in form %}
<div class="equal width fields">
{% if "givenName" in form %}
{% block given_name_field scoped %}{{ render_field(form.givenName) }}{% endblock %}
{% if "given_name" in form %}
{% block given_name_field scoped %}{{ render_field(form.given_name) }}{% endblock %}
{% endif %}
{% if "sn" in form %}
{% block sn_field scoped %}{{ render_field(form.sn) }}{% endblock %}
{% if "family_name" in form %}
{% block family_name_field scoped %}{{ render_field(form.family_name) }}{% endblock %}
{% endif %}
</div>
{% endif %}
{% if "displayName" in form %}
{% block display_name_field scoped %}{{ render_field(form.displayName) }}{% endblock %}
{% if "display_name" in form %}
{% block display_name_field scoped %}{{ render_field(form.display_name) }}{% endblock %}
{% endif %}
{% if "jpegPhoto" in form %}</div></div>{% endif %}
{% if "photo" in form %}</div></div>{% endif %}
{% if "mail" in form %}
{% block mail_field scoped %}{{ render_field(form.mail) }}{% endblock %}
{% if "email" in form %}
{% block email_field scoped %}{{ render_field(form.email) }}{% endblock %}
{% endif %}
{% if "telephoneNumber" in form %}
{% block telephone_number_field scoped %}{{ render_field(form.telephoneNumber) }}{% endblock %}
{% if "phone_number" in form %}
{% block phone_number_field scoped %}{{ render_field(form.phone_number) }}{% endblock %}
{% endif %}
{% if "postalAddress" in form %}
{% block postal_address_field scoped %}{{ render_field(form.postalAddress) }}{% endblock %}
{% if "formatted_address" in form %}
{% block formatted_address_field scoped %}{{ render_field(form.formatted_address) }}{% endblock %}
{% endif %}
{% if "street" in form %}
@ -113,29 +159,28 @@
{% endif %}
<div class="equal width fields">
{% if "postalCode" in form %}
{% block postal_code_field scoped %}{{ render_field(form.postalCode) }}{% endblock %}
{% if "postal_code" in form %}
{% block postal_code_field scoped %}{{ render_field(form.postal_code) }}{% endblock %}
{% endif %}
{% if "l" in form %}
{% block locality_field scoped %}{{ render_field(form.l) }}{% endblock %}
{% if "locality" in form %}
{% block locality_field scoped %}{{ render_field(form.locality) }}{% endblock %}
{% endif %}
{% if "st" in form %}
{% block region_field scoped %}{{ render_field(form.st) }}{% endblock %}
{% if "region" in form %}
{% block region_field scoped %}{{ render_field(form.region) }}{% endblock %}
{% endif %}
</div>
<div class="equal width fields">
{% if "departmentNumber" in form %}
{% block department_number_field scoped %}{{ render_field(form.departmentNumber) }}{% endblock %}
{% if "department" in form %}
{% block department_field scoped %}{{ render_field(form.department) }}{% endblock %}
{% endif %}
{% if "employeeNumber" in form %}
{% block employee_number_field scoped %}{{ render_field(form.employeeNumber) }}{% endblock %}
{% if "employee_number" in form %}
{% block employee_number_field scoped %}{{ render_field(form.employee_number) }}{% endblock %}
{% endif %}
</div>
<div class="equal width fields">
@ -144,24 +189,24 @@
{% block title_field scoped %}{{ render_field(form.title) }}{% endblock %}
{% endif %}
{% if "o" in form %}
{% block organization_field scoped %}{{ render_field(form.o) }}{% endblock %}
{% if "organization" in form %}
{% block organization_field scoped %}{{ render_field(form.organization) }}{% endblock %}
{% endif %}
</div>
{% if "labeledURI" in form %}
{% block labeled_uri_field scoped %}{{ render_field(form.labeledURI) }}{% endblock %}
{% if "profile_url" in form %}
{% block profile_uri_field scoped %}{{ render_field(form.profile_url) }}{% endblock %}
{% endif %}
{% if "preferredLanguage" in form %}
{% block preferred_language_field scoped %}{{ render_field(form.preferredLanguage) }}{% endblock %}
{% if "preferred_language" in form %}
{% block preferred_language_field scoped %}{{ render_field(form.preferred_language) }}{% endblock %}
{% endif %}
<h4 class="ui dividing header">{% trans %}Account information{% endtrans %}</h4>
{% if "uid" in form %}
{% block uid_field scoped %}{{ render_field(form.uid) }}{% endblock %}
{% if "user_name" in form %}
{% block user_name_field scoped %}{{ render_field(form.user_name) }}{% endblock %}
{% endif %}
{% if "groups" in form %}

View file

@ -4,7 +4,7 @@
{%- block title -%}
{% if not edited_user %}
{%- trans %}User creation{% endtrans -%}
{% elif user.uid == edited_user.uid %}
{% elif user.user_name == edited_user.user_name %}
{%- trans %}My profile{% endtrans -%}
{% else %}
{%- trans %}User profile edition{% endtrans -%}
@ -18,11 +18,11 @@
{% block submenu %}
<nav class="ui bottom attached two item borderless menu">
<a class="active item" href="{{ url_for('account.profile_edition', username=edited_user.uid[0]) }}">
<a class="active item" href="{{ url_for('account.profile_edition', username=edited_user.user_name[0]) }}">
<i class="id card icon"></i>
{% trans %}Personal information{% endtrans %}
</a>
<a class="item" href="{{ url_for('account.profile_settings', username=edited_user.uid[0]) }}">
<a class="item" href="{{ url_for('account.profile_settings', username=edited_user.user_name[0]) }}">
<i class="tools icon"></i>
{% trans %}Account information{% endtrans %}
</a>
@ -33,7 +33,7 @@
{% set lock_indicator = field.render_kw and ("readonly" in field.render_kw or "disabled" in field.render_kw) %}
{% if not edited_user %}
{{ fui.render_field(field, **kwargs) }}
{% elif edited_user.uid == user.uid or lock_indicator or noindicator %}
{% elif edited_user.user_name == user.user_name or lock_indicator or noindicator %}
{{ fui.render_field(field, **kwargs) }}
{% elif field.name in edited_user.write %}
{{ fui.render_field(field, **kwargs) }}
@ -48,7 +48,7 @@
<div class="ui clearing segment">
<h2 class="ui center aligned header">
<div class="content">
{% if user.uid == edited_user.uid %}
{% if user.user_name == edited_user.user_name %}
{% trans %}My profile{% endtrans %}
{% else %}
{% trans %}User profile edition{% endtrans %}
@ -56,7 +56,7 @@
</div>
<div class="sub header">
{% if user.uid == edited_user.uid %}
{% if user.user_name == edited_user.user_name %}
{% trans %}Edit your personal information{% endtrans %}
{% else %}
{% trans %}Edit information about a user{% endtrans %}
@ -65,27 +65,27 @@
</h2>
{% call fui.render_form(form) %}
{% if "jpegPhoto" in form %}
{% if "photo" in form %}
<div class="ui grid">
<div class="three wide column">
{% block photo_field scoped %}
{{ 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] %}
{{ render_field(form.photo, display=false, class="photo-field") }}
{{ render_field(form.photo_delete, display=false, class="photo-delete-button") }}
{% set photo = edited_user.photo and edited_user.photo[0] %}
<label
class="ui small bordered image photo-content"
for="{{ form.jpegPhoto.id }}"
for="{{ form.photo.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 %}{{ url_for("account.photo", uid=edited_user.uid[0], field="jpegPhoto") }}{% endif %}" alt="User photo">
<img src="{% if photo %}{{ url_for("account.photo", user_name=edited_user.user_name[0], field="photo") }}{% endif %}" alt="User photo">
</label>
<label
class="ui centered photo-placeholder"
for="{{ form.jpegPhoto.id }}"
for="{{ form.photo.id }}"
title="{{ _("Click to upload a photo") }}"
{% if photo %}style="display: none;"{% endif %}>
<i class="massive centered portrait icon"></i>
@ -96,34 +96,34 @@
<div class="thirteen wide column">
{% endif %}
{% if "givenName" in form or "sn" in form %}
{% if "given_name" in form or "family_name" in form %}
<div class="equal width fields">
{% if "givenName" in form %}
{% block given_name_field scoped %}{{ render_field(form.givenName) }}{% endblock %}
{% if "given_name" in form %}
{% block given_name_field scoped %}{{ render_field(form.given_name) }}{% endblock %}
{% endif %}
{% if "sn" in form %}
{% block sn_field scoped %}{{ render_field(form.sn) }}{% endblock %}
{% if "family_name" in form %}
{% block sn_field scoped %}{{ render_field(form.family_name) }}{% endblock %}
{% endif %}
</div>
{% endif %}
{% if "displayName" in form %}
{% block display_name_field scoped %}{{ render_field(form.displayName) }}{% endblock %}
{% if "display_name" in form %}
{% block display_name_field scoped %}{{ render_field(form.display_name) }}{% endblock %}
{% endif %}
{% if "jpegPhoto" in form %}</div></div>{% endif %}
{% if "photo" in form %}</div></div>{% endif %}
{% if "mail" in form %}
{% block mail_field scoped %}{{ render_field(form.mail) }}{% endblock %}
{% if "email" in form %}
{% block email_field scoped %}{{ render_field(form.email) }}{% endblock %}
{% endif %}
{% if "telephoneNumber" in form %}
{% block telephone_number_field scoped %}{{ render_field(form.telephoneNumber) }}{% endblock %}
{% if "phone_number" in form %}
{% block phone_number_field scoped %}{{ render_field(form.phone_number) }}{% endblock %}
{% endif %}
{% if "postalAddress" in form %}
{% block postal_address_field scoped %}{{ render_field(form.postalAddress) }}{% endblock %}
{% if "formatted_address" in form %}
{% block formatted_address_field scoped %}{{ render_field(form.formatted_address) }}{% endblock %}
{% endif %}
{% if "street" in form %}
@ -131,27 +131,27 @@
{% endif %}
<div class="equal width fields">
{% if "postalCode" in form %}
{% block postal_code_field scoped %}{{ render_field(form.postalCode) }}{% endblock %}
{% if "postal_code" in form %}
{% block postal_code_field scoped %}{{ render_field(form.postal_code) }}{% endblock %}
{% endif %}
{% if "l" in form %}
{% block locality_field scoped %}{{ render_field(form.l) }}{% endblock %}
{% if "locality" in form %}
{% block locality_field scoped %}{{ render_field(form.locality) }}{% endblock %}
{% endif %}
{% if "st" in form %}
{% block region_field scoped %}{{ render_field(form.st) }}{% endblock %}
{% if "region" in form %}
{% block region_field scoped %}{{ render_field(form.region) }}{% endblock %}
{% endif %}
</div>
<div class="equal width fields">
{% if "departmentNumber" in form %}
{% block department_number_field scoped %}{{ render_field(form.departmentNumber) }}{% endblock %}
{% if "department" in form %}
{% block department_number_field scoped %}{{ render_field(form.department) }}{% endblock %}
{% endif %}
{% if "employeeNumber" in form %}
{% block employee_number_field scoped %}{{ render_field(form.employeeNumber) }}{% endblock %}
{% if "employee_number" in form %}
{% block employee_number_field scoped %}{{ render_field(form.employee_number) }}{% endblock %}
{% endif %}
</div>
@ -161,18 +161,18 @@
{% block title_field scoped %}{{ render_field(form.title) }}{% endblock %}
{% endif %}
{% if "o" in form %}
{% block organization_field scoped %}{{ render_field(form.o) }}{% endblock %}
{% if "organization" in form %}
{% block organization_field scoped %}{{ render_field(form.organization) }}{% endblock %}
{% endif %}
</div>
{% if "labeledURI" in form %}
{% block labeled_uri_field scoped %}{{ render_field(form.labeledURI) }}{% endblock %}
{% if "profile_url" in form %}
{% block profile_url_field scoped %}{{ render_field(form.profile_url) }}{% endblock %}
{% endif %}
{% if "preferredLanguage" in form %}
{% block preferred_language_field scoped %}{{ render_field(form.preferredLanguage) }}{% endblock %}
{% if "preferred_language" in form %}
{% block preferred_language_field scoped %}{{ render_field(form.preferred_language) }}{% endblock %}
{% endif %}
<div class="ui right aligned container">

View file

@ -2,7 +2,7 @@
{% import 'macro/form.html' as fui %}
{%- block title -%}
{% if user.uid == edited_user.uid %}
{% if user.user_name == edited_user.user_name %}
{%- trans %}My profile{% endtrans -%}
{% else %}
{%- trans %}User profile edition{% endtrans -%}
@ -16,11 +16,11 @@
{% block submenu %}
<nav class="ui bottom attached two item borderless menu">
<a class="item" href="{{ url_for('account.profile_edition', username=edited_user.uid[0]) }}">
<a class="item" href="{{ url_for('account.profile_edition', username=edited_user.user_name[0]) }}">
<i class="id card icon"></i>
{% trans %}Personal information{% endtrans %}
</a>
<a class="active item" href="{{ url_for('account.profile_settings', username=edited_user.uid[0]) }}">
<a class="active item" href="{{ url_for('account.profile_settings', username=edited_user.user_name[0]) }}">
<i class="tools icon"></i>
{% trans %}Account information{% endtrans %}
</a>
@ -29,7 +29,7 @@
{% 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 edited_user.uid == user.uid or lock_indicator or noindicator %}
{% if edited_user.user_name == user.user_name or lock_indicator or noindicator %}
{{ fui.render_field(field, **kwargs) }}
{% elif field.name in edited_user.write %}
{{ fui.render_field(field, **kwargs) }}
@ -49,7 +49,7 @@
</div>
<div class="content">
<p>
{% if user.uid != edited_user.uid %}
{% if user.user_name != edited_user.user_name %}
{{ _("Are you sure you want to delete this user? This action is unrevokable and all the data about this user will be removed.") }}
{% else %}
{{ _("Are you sure you want to delete your account? This action is unrevokable and all your data will be removed forever.") }}
@ -71,8 +71,8 @@
</h2>
{% call fui.render_form(form, class_="profile-form info warning") %}
{% if "uid" in form %}
{% block uid_field scoped %}{{ render_field(form.uid) }}{% endblock %}
{% if "user_name" in form %}
{% block user_name_field scoped %}{{ render_field(form.user_name) }}{% endblock %}
{% endif %}
{% if "groups" in form %}
@ -113,7 +113,7 @@
{% endif %}
</div>
{% elif has_smtp and edited_user.uid != user.uid and edited_user.has_password() and edited_user.can_edit_self %}
{% elif has_smtp and edited_user.user_name != user.user_name and edited_user.has_password() and edited_user.can_edit_self %}
<div class="ui message info">
<button type="submit" name="action" value="password-reset-mail" class="ui right floated button">
@ -133,7 +133,7 @@
<div class="ui stackable buttons">
{% if user.can_manage_users or self_deletion %}
<button type="submit" class="ui right floated basic negative button confirm" name="action" value="delete" id="delete">
{% if user.uid != edited_user.uid %}
{% if user.user_name != edited_user.user_name %}
{{ _("Delete the user") }}
{% else %}
{{ _("Delete my account") }}
@ -141,8 +141,8 @@
</button>
{% endif %}
{% if user.can_impersonate_users and user.uid != edited_user.uid %}
<a href="{{ url_for('account.impersonate', username=edited_user.uid[0]) }}" class="ui right floated basic button" name="action" value="impersonate" id="impersonate">
{% if user.can_impersonate_users and user.user_name != edited_user.user_name %}
<a href="{{ url_for('account.impersonate', username=edited_user.user_name[0]) }}" class="ui right floated basic button" name="action" value="impersonate" id="impersonate">
{{ _("Impersonate") }}
</a>
{% endif %}

View file

@ -13,7 +13,7 @@
</h3>
<div class="ui attached clearing segment">
{{ fui.render_form(form, _("Password reset"), action=url_for("account.reset", uid=uid, hash=hash)) }}
{{ fui.render_form(form, _("Password reset"), action=url_for("account.reset", user_name=user_name, hash=hash)) }}
</div>
</div>
{% endblock %}

View file

@ -94,10 +94,10 @@
</div>
{% endif %}
{% if user.locale %}
{% if user.preferred_language %}
<div class="extra content">
<i class="flag icon" title="locale"></i>
{{ user.locale }}
<i class="flag icon" title="preferred_language"></i>
{{ user.preferred_language }}
</div>
{% endif %}

View file

@ -131,26 +131,26 @@ GROUP_BASE = "ou=groups,dc=mydomain,dc=tld"
# object that users will be able to read and/or write.
[ACL.DEFAULT]
PERMISSIONS = ["edit_self", "use_oidc"]
READ = ["uid", "groups"]
READ = ["user_name", "groups"]
WRITE = [
"jpegPhoto",
"givenName",
"sn",
"displayName",
"userPassword",
"telephoneNumber",
"mail",
"labeledURI",
"postalAddress",
"photo",
"given_name",
"family_name",
"display_name",
"password",
"phone_number",
"email",
"profile_url",
"formatted_address",
"street",
"postalCode",
"l",
"st",
"preferredLanguage",
"employeeNumber",
"departmentNumber",
"postal_code",
"locality",
"region",
"preferred_language",
"employee_number",
"department",
"title",
"o",
"organization",
]
[ACL.ADMIN]
@ -191,17 +191,17 @@ PUBLIC_KEY = "conf/public.pem"
# User objectClass.
# {attribute} will be replaced by the user ldap attribute value.
# Default values fits inetOrgPerson.
SUB = "{{ user.uid[0] }}"
NAME = "{{ user.cn[0] }}"
PHONE_NUMBER = "{{ user.telephoneNumber[0] }}"
EMAIL = "{{ user.mail[0] }}"
GIVEN_NAME = "{{ user.givenName[0] }}"
FAMILY_NAME = "{{ user.sn[0] }}"
PREFERRED_USERNAME = "{{ user.displayName }}"
LOCALE = "{{ user.preferredLanguage }}"
ADDRESS = "{{ user.postalAddress[0] }}"
PICTURE = "{% if user.jpegPhoto %}{{ url_for('account.photo', uid=user.uid[0], field='jpegPhoto', _external=True) }}{% endif %}"
WEBSITE = "{{ user.labeledURI[0] }}"
SUB = "{{ user.user_name[0] }}"
NAME = "{{ user.formatted_name[0] }}"
PHONE_NUMBER = "{{ user.phone_number[0] }}"
EMAIL = "{{ user.email[0] }}"
GIVEN_NAME = "{{ user.given_name[0] }}"
FAMILY_NAME = "{{ user.family_name[0] }}"
PREFERRED_USERNAME = "{{ user.display_name }}"
LOCALE = "{{ user.preferred_language }}"
ADDRESS = "{{ user.formatted_address[0] }}"
PICTURE = "{% if user.photo %}{{ url_for('account.photo', user_name=user.user_name[0], field='photo', _external=True) }}{% endif %}"
WEBSITE = "{{ user.profile_url[0] }}"
# The SMTP server options. If not set, mail related features such as
# user invitations, and password reset emails, will be disabled.

View file

@ -131,26 +131,26 @@ GROUP_BASE = "ou=groups,dc=mydomain,dc=tld"
# object that users will be able to read and/or write.
[ACL.DEFAULT]
PERMISSIONS = ["edit_self", "use_oidc"]
READ = ["uid", "groups"]
READ = ["user_name", "groups"]
WRITE = [
"jpegPhoto",
"givenName",
"sn",
"displayName",
"userPassword",
"telephoneNumber",
"mail",
"labeledURI",
"postalAddress",
"photo",
"given_name",
"family_name",
"display_name",
"password",
"phone_number",
"email",
"profile_url",
"formatted_address",
"street",
"postalCode",
"l",
"st",
"preferredLanguage",
"employeeNumber",
"departmentNumber",
"postal_code",
"locality",
"region",
"preferred_language",
"employee_number",
"department",
"title",
"o",
"organization",
]
[ACL.ADMIN]
@ -191,17 +191,17 @@ PUBLIC_KEY = "conf/public.pem"
# User objectClass.
# {attribute} will be replaced by the user ldap attribute value.
# Default values fits inetOrgPerson.
SUB = "{{ user.uid[0] }}"
NAME = "{{ user.cn[0] }}"
PHONE_NUMBER = "{{ user.telephoneNumber[0] }}"
EMAIL = "{{ user.mail[0] }}"
GIVEN_NAME = "{{ user.givenName[0] }}"
FAMILY_NAME = "{{ user.sn[0] }}"
PREFERRED_USERNAME = "{{ user.displayName }}"
LOCALE = "{{ user.preferredLanguage }}"
ADDRESS = "{{ user.postalAddress[0] }}"
PICTURE = "{% if user.jpegPhoto %}{{ url_for('account.photo', uid=user.uid[0], field='jpegPhoto', _external=True) }}{% endif %}"
WEBSITE = "{{ user.labeledURI[0] }}"
SUB = "{{ user.user_name[0] }}"
NAME = "{{ user.formatted_name[0] }}"
PHONE_NUMBER = "{{ user.phone_number[0] }}"
EMAIL = "{{ user.email[0] }}"
GIVEN_NAME = "{{ user.given_name[0] }}"
FAMILY_NAME = "{{ user.family_name[0] }}"
PREFERRED_USERNAME = "{{ user.display_name }}"
LOCALE = "{{ user.preferred_language }}"
ADDRESS = "{{ user.formatted_address[0] }}"
PICTURE = "{% if user.photo %}{{ url_for('account.photo', user_name=user.user_name[0], field='photo', _external=True) }}{% endif %}"
WEBSITE = "{{ user.profile_url[0] }}"
# The SMTP server options. If not set, mail related features such as
# user invitations, and password reset emails, will be disabled.

View file

@ -196,37 +196,37 @@ A mapping where keys are JWT claims, and values are LDAP user object attributes.
Attributes are rendered using jinja2, and can use a ``user`` variable.
:SUB:
*Optional.* Defaults to ``{{ user.uid[0] }}``
*Optional.* Defaults to ``{{ user.user_name[0] }}``
:NAME:
*Optional.* Defaults to ``{{ user.cn[0] }}``
:PHONE_NUMBER:
*Optional.* Defaults to ``{{ user.telephoneNumber[0] }}``
*Optional.* Defaults to ``{{ user.phone_number[0] }}``
:EMAIL:
*Optional.* Defaults to ``{{ user.mail[0] }}``
:GIVEN_NAME:
*Optional.* Defaults to ``{{ user.givenName[0] }}``
*Optional.* Defaults to ``{{ user.given_name[0] }}``
:FAMILY_NAME:
*Optional.* Defaults to ``{{ user.sn[0] }}``
*Optional.* Defaults to ``{{ user.family_name[0] }}``
:PREFERRED_USERNAME:
*Optional.* Defaults to ``{{ user.displayName[0] }}``
*Optional.* Defaults to ``{{ user.display_name[0] }}``
:LOCALE:
*Optional.* Defaults to ``{{ user.preferredLanguage }}``
*Optional.* Defaults to ``{{ user.locale }}``
:ADDRESS:
*Optional.* Defaults to ``{{ user.postalAddress[0] }}``
*Optional.* Defaults to ``{{ user.address[0] }}``
:PICTURE:
*Optional.* Defaults to ``{% if user.jpegPhoto %}{{ url_for('account.photo', uid=user.uid[0], field='jpegPhoto', _external=True) }}{% endif %}``
*Optional.* Defaults to ``{% if user.photo %}{{ url_for('account.photo', user_name=user.user_name[0], field='photo', _external=True) }}{% endif %}``
:WEBSITE:
*Optional.* Defaults to ``{{ user.labeledURI[0] }}``
*Optional.* Defaults to ``{{ user.profile_url[0] }}``
SMTP

View file

@ -91,26 +91,26 @@ def configuration(slapd_server, smtpd):
},
"ACL": {
"DEFAULT": {
"READ": ["uid", "groups"],
"READ": ["user_name", "groups"],
"PERMISSIONS": ["edit_self", "use_oidc"],
"WRITE": [
"mail",
"givenName",
"jpegPhoto",
"sn",
"displayName",
"userPassword",
"telephoneNumber",
"postalAddress",
"email",
"given_name",
"photo",
"family_name",
"display_name",
"password",
"phone_number",
"formatted_address",
"street",
"postalCode",
"l",
"st",
"employeeNumber",
"preferredLanguage",
"departmentNumber",
"postal_code",
"locality",
"region",
"employee_number",
"department",
"preferred_language",
"title",
"o",
"organization",
],
},
"ADMIN": {
@ -164,17 +164,17 @@ def testclient(app):
@pytest.fixture
def user(app, slapd_connection):
u = User(
cn="John (johnny) Doe",
givenName="John",
sn="Doe",
uid="user",
mail="john@doe.com",
userPassword="{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz",
displayName="Johnny",
preferredLanguage="en",
telephoneNumber="555-000-000",
labeledURI="https://john.example",
postalAddress="1235, somewhere",
formatted_name="John (johnny) Doe",
given_name="John",
family_name="Doe",
user_name="user",
email="john@doe.com",
password="{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz",
display_name="Johnny",
preferred_language="en",
phone_number="555-000-000",
profile_url="https://john.example",
formatted_address="1235, somewhere",
)
u.save()
yield u
@ -184,11 +184,11 @@ def user(app, slapd_connection):
@pytest.fixture
def admin(app, slapd_connection):
u = User(
cn="Jane Doe",
sn="Doe",
uid="admin",
mail="jane@doe.com",
userPassword="{SSHA}Vmgh2jkD0idX3eZHf8RzGos31oerjGiU",
formatted_name="Jane Doe",
family_name="Doe",
user_name="admin",
email="jane@doe.com",
password="{SSHA}Vmgh2jkD0idX3eZHf8RzGos31oerjGiU",
)
u.save()
yield u
@ -198,11 +198,11 @@ def admin(app, slapd_connection):
@pytest.fixture
def moderator(app, slapd_connection):
u = User(
cn="Jack Doe",
sn="Doe",
uid="moderator",
mail="jack@doe.com",
userPassword="{SSHA}+eHyxWqajMHsOWnhONC2vbtfNZzKTkag",
formatted_name="Jack Doe",
family_name="Doe",
user_name="moderator",
email="jack@doe.com",
password="{SSHA}+eHyxWqajMHsOWnhONC2vbtfNZzKTkag",
)
u.save()
yield u

View file

@ -34,7 +34,7 @@
</div>
{% endif %}
<a class="item {% if menuitem == "profile" %}active{% endif %}"
href="{{ url_for('account.profile_edition', username=user.uid[0]) }}">
href="{{ url_for('account.profile_edition', username=user.user_name[0]) }}">
<i class="id card icon"></i>
{% trans %}My profile{% endtrans %}
</a>

View file

@ -14,10 +14,10 @@ from canaille.models import User
def test_object_creation(slapd_connection):
User.initialize(slapd_connection)
user = User(
cn="Doe", # leading space
sn="Doe",
uid="user",
mail="john@doe.com",
formatted_name="Doe", # leading space
family_name="Doe",
user_name="user",
email="john@doe.com",
)
assert not user.exists
user.save()
@ -31,7 +31,7 @@ def test_object_creation(slapd_connection):
def test_repr(slapd_connection, foo_group, user):
assert repr(foo_group) == "<Group display_name=foo>"
assert repr(user) == "<User cn=John (johnny) Doe>"
assert repr(user) == "<User formatted_name=John (johnny) Doe>"
def test_equality(slapd_connection, foo_group, bar_group):
@ -43,10 +43,10 @@ def test_equality(slapd_connection, foo_group, bar_group):
def test_dn_when_leading_space_in_id_attribute(slapd_connection):
User.initialize(slapd_connection)
user = User(
cn=" Doe", # leading space
sn="Doe",
uid="user",
mail="john@doe.com",
formatted_name=" Doe", # leading space
family_name="Doe",
user_name="user",
email="john@doe.com",
)
user.save()
@ -60,10 +60,10 @@ def test_dn_when_leading_space_in_id_attribute(slapd_connection):
def test_dn_when_ldap_special_char_in_id_attribute(slapd_connection):
User.initialize(slapd_connection)
user = User(
cn="#Doe", # special char
sn="Doe",
uid="user",
mail="john@doe.com",
formatted_name="#Doe", # special char
family_name="Doe",
user_name="user",
email="john@doe.com",
)
user.save()

View file

@ -63,17 +63,17 @@ def configuration(configuration, keypair_path):
"PRIVATE_KEY": private_key_path,
"ISS": "https://auth.mydomain.tld",
"MAPPING": {
"SUB": "{{ user.uid[0] }}",
"SUB": "{{ user.user_name[0] }}",
"NAME": "{{ user.cn[0] }}",
"PHONE_NUMBER": "{{ user.telephoneNumber[0] }}",
"EMAIL": "{{ user.mail[0] }}",
"GIVEN_NAME": "{{ user.givenName[0] }}",
"FAMILY_NAME": "{{ user.sn[0] }}",
"PREFERRED_USERNAME": "{{ user.displayName }}",
"LOCALE": "{{ user.preferredLanguage }}",
"PICTURE": "{% if user.jpegPhoto %}{{ url_for('account.photo', uid=user.uid[0], field='jpegPhoto', _external=True) }}{% endif %}",
"ADDRESS": "{{ user.postalAddress[0] }}",
"WEBSITE": "{{ user.labeledURI[0] }}",
"PHONE_NUMBER": "{{ user.phone_number[0] }}",
"EMAIL": "{{ user.email[0] }}",
"GIVEN_NAME": "{{ user.given_name[0] }}",
"FAMILY_NAME": "{{ user.family_name[0] }}",
"PREFERRED_USERNAME": "{{ user.display_name }}",
"LOCALE": "{{ user.preferred_language }}",
"PICTURE": "{% if user.photo %}{{ url_for('account.photo', user_name=user.user_name[0], field='photo', _external=True) }}{% endif %}",
"ADDRESS": "{{ user.formatted_address[0] }}",
"WEBSITE": "{{ user.profile_url[0] }}",
},
},
}

View file

@ -82,14 +82,14 @@ def test_authorization_code_flow(
"phone",
}
claims = jwt.decode(access_token, keypair[1])
assert claims["sub"] == logged_user.uid[0]
assert claims["name"] == logged_user.cn[0]
assert claims["sub"] == logged_user.user_name[0]
assert claims["name"] == logged_user.formatted_name[0]
assert claims["aud"] == [client.client_id, other_client.client_id]
id_token = res.json["id_token"]
claims = jwt.decode(id_token, keypair[1])
assert claims["sub"] == logged_user.uid[0]
assert claims["name"] == logged_user.cn[0]
assert claims["sub"] == logged_user.user_name[0]
assert claims["name"] == logged_user.formatted_name[0]
assert claims["aud"] == [client.client_id, other_client.client_id]
res = testclient.get(
@ -210,8 +210,8 @@ def test_authorization_code_flow_preconsented(
id_token = res.json["id_token"]
claims = jwt.decode(id_token, keypair[1])
assert logged_user.uid[0] == claims["sub"]
assert logged_user.cn[0] == claims["name"]
assert logged_user.user_name[0] == claims["sub"]
assert logged_user.formatted_name[0] == claims["name"]
assert [client.client_id, other_client.client_id] == claims["aud"]
res = testclient.get(
@ -761,8 +761,8 @@ def test_authorization_code_request_scope_too_large(
id_token = res.json["id_token"]
claims = jwt.decode(id_token, keypair[1])
assert logged_user.uid[0] == claims["sub"]
assert logged_user.cn[0] == claims["name"]
assert logged_user.user_name[0] == claims["sub"]
assert logged_user.formatted_name[0] == claims["name"]
res = testclient.get(
"/oauth/userinfo",
@ -814,11 +814,11 @@ def test_authorization_code_expired(testclient, user, client):
def test_code_with_invalid_user(testclient, admin, client):
user = User(
cn="John Doe",
sn="Doe",
uid="temp",
mail="temp@temp.com",
userPassword="{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz",
formatted_name="John Doe",
family_name="Doe",
user_name="temp",
email="temp@temp.com",
password="{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz",
)
user.save()
@ -862,11 +862,11 @@ def test_code_with_invalid_user(testclient, admin, client):
def test_refresh_token_with_invalid_user(testclient, client):
user = User(
cn="John Doe",
sn="Doe",
uid="temp",
mail="temp@temp.com",
userPassword="{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz",
formatted_name="John Doe",
family_name="Doe",
user_name="temp",
email="temp@temp.com",
password="{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz",
)
user.save()

View file

@ -4,14 +4,14 @@ from canaille.oidc.oauth import get_jwt_config
def test_end_session(testclient, slapd_connection, logged_user, client, id_token):
testclient.get(f"/profile/{logged_user.uid[0]}", status=200)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.uid[0],
"logout_hint": logged_user.user_name[0],
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
@ -24,20 +24,20 @@ def test_end_session(testclient, slapd_connection, logged_user, client, id_token
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
testclient.get(f"/profile/{logged_user.uid[0]}", status=403)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
def test_end_session_no_client_id(
testclient, slapd_connection, logged_user, client, id_token
):
testclient.get(f"/profile/{logged_user.uid[0]}", status=200)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.uid[0],
"logout_hint": logged_user.user_name[0],
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
@ -49,19 +49,19 @@ def test_end_session_no_client_id(
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
testclient.get(f"/profile/{logged_user.uid[0]}", status=403)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
def test_no_redirect_uri_no_redirect(
testclient, slapd_connection, logged_user, client, id_token
):
testclient.get(f"/profile/{logged_user.uid[0]}", status=200)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.uid[0],
"logout_hint": logged_user.user_name[0],
"client_id": client.client_id,
"state": "foobar",
},
@ -73,20 +73,20 @@ def test_no_redirect_uri_no_redirect(
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
testclient.get(f"/profile/{logged_user.uid[0]}", status=403)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
def test_bad_redirect_uri_no_redirect(
testclient, slapd_connection, logged_user, client, id_token
):
testclient.get(f"/profile/{logged_user.uid[0]}", status=200)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
post_logout_redirect_url = "https://mydomain.tld/invalid-uri"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.uid[0],
"logout_hint": logged_user.user_name[0],
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
@ -99,19 +99,19 @@ def test_bad_redirect_uri_no_redirect(
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
testclient.get(f"/profile/{logged_user.uid[0]}", status=403)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
def test_no_client_hint_no_redirect(
testclient, slapd_connection, logged_user, client, id_token
):
testclient.get(f"/profile/{logged_user.uid[0]}", status=200)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"logout_hint": logged_user.uid[0],
"logout_hint": logged_user.user_name[0],
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
@ -125,19 +125,19 @@ def test_no_client_hint_no_redirect(
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
testclient.get(f"/profile/{logged_user.uid[0]}", status=403)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
def test_end_session_invalid_client_id(
testclient, slapd_connection, logged_user, client
):
testclient.get(f"/profile/{logged_user.uid[0]}", status=200)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"logout_hint": logged_user.uid[0],
"logout_hint": logged_user.user_name[0],
"post_logout_redirect_uri": post_logout_redirect_url,
"client_id": "invalid_client_id",
"state": "foobar",
@ -153,7 +153,7 @@ def test_end_session_invalid_client_id(
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
testclient.get(f"/profile/{logged_user.uid[0]}", status=403)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
def test_client_hint_invalid(testclient, slapd_connection, logged_user, client):
@ -164,14 +164,14 @@ def test_client_hint_invalid(testclient, slapd_connection, logged_user, client):
**get_jwt_config(None),
)
testclient.get(f"/profile/{logged_user.uid[0]}", status=200)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.uid[0],
"logout_hint": logged_user.user_name[0],
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
},
@ -183,17 +183,17 @@ def test_client_hint_invalid(testclient, slapd_connection, logged_user, client):
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
testclient.get(f"/profile/{logged_user.uid[0]}", status=403)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
def test_no_jwt_logout(testclient, slapd_connection, logged_user, client):
testclient.get(f"/profile/{logged_user.uid[0]}", status=200)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"logout_hint": logged_user.uid[0],
"logout_hint": logged_user.user_name[0],
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
@ -209,17 +209,17 @@ def test_no_jwt_logout(testclient, slapd_connection, logged_user, client):
assert res.location == f"{post_logout_redirect_url}?state=foobar"
testclient.get(f"/profile/{logged_user.uid[0]}", status=403)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
def test_no_jwt_no_logout(testclient, slapd_connection, logged_user, client):
testclient.get(f"/profile/{logged_user.uid[0]}", status=200)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"logout_hint": logged_user.uid[0],
"logout_hint": logged_user.user_name[0],
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
@ -234,7 +234,7 @@ def test_no_jwt_no_logout(testclient, slapd_connection, logged_user, client):
assert res.location == "/"
testclient.get(f"/profile/{logged_user.uid[0]}", status=200)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
def test_jwt_not_issued_here(
@ -242,14 +242,14 @@ def test_jwt_not_issued_here(
):
testclient.app.config["JWT"]["ISS"] = "https://foo.bar"
testclient.get(f"/profile/{logged_user.uid[0]}", status=200)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.uid[0],
"logout_hint": logged_user.user_name[0],
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
@ -271,14 +271,14 @@ def test_client_hint_mismatch(testclient, slapd_connection, logged_user, client)
**get_jwt_config(None),
)
testclient.get(f"/profile/{logged_user.uid[0]}", status=200)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.uid[0],
"logout_hint": logged_user.user_name[0],
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
@ -295,7 +295,7 @@ def test_client_hint_mismatch(testclient, slapd_connection, logged_user, client)
def test_bad_user_id_token_mismatch(
testclient, slapd_connection, logged_user, client, admin
):
testclient.get(f"/profile/{logged_user.uid[0]}", status=200)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
id_token = generate_id_token(
{},
@ -309,7 +309,7 @@ def test_bad_user_id_token_mismatch(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.uid[0],
"logout_hint": logged_user.user_name[0],
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
@ -325,20 +325,20 @@ def test_bad_user_id_token_mismatch(
assert res.location == f"{post_logout_redirect_url}?state=foobar"
testclient.get(f"/profile/{logged_user.uid[0]}", status=403)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
def test_bad_user_hint(
testclient, slapd_connection, logged_user, client, id_token, admin
):
testclient.get(f"/profile/{logged_user.uid[0]}", status=200)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": admin.uid[0],
"logout_hint": admin.user_name[0],
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
@ -354,17 +354,17 @@ def test_bad_user_hint(
assert res.location == f"{post_logout_redirect_url}?state=foobar"
testclient.get(f"/profile/{logged_user.uid[0]}", status=403)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
def test_no_jwt_bad_csrf(testclient, slapd_connection, logged_user, client):
testclient.get(f"/profile/{logged_user.uid[0]}", status=200)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"logout_hint": logged_user.uid[0],
"logout_hint": logged_user.user_name[0],
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
@ -385,7 +385,7 @@ def test_end_session_already_disconnected(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": user.uid[0],
"logout_hint": user.user_name[0],
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
"state": "foobar",
@ -399,14 +399,14 @@ def test_end_session_already_disconnected(
def test_end_session_no_state(
testclient, slapd_connection, logged_user, client, id_token
):
testclient.get(f"/profile/{logged_user.uid[0]}", status=200)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
post_logout_redirect_url = "https://mydomain.tld/disconnected"
res = testclient.get(
"/oauth/end_session",
params={
"id_token_hint": id_token,
"logout_hint": logged_user.uid[0],
"logout_hint": logged_user.user_name[0],
"client_id": client.client_id,
"post_logout_redirect_uri": post_logout_redirect_url,
},
@ -418,4 +418,4 @@ def test_end_session_no_state(
with testclient.session_transaction() as sess:
assert not sess.get("user_id")
testclient.get(f"/profile/{logged_user.uid[0]}", status=403)
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)

View file

@ -77,8 +77,8 @@ def test_oidc_hybrid(
id_token = params["id_token"][0]
claims = jwt.decode(id_token, keypair[1])
assert logged_user.uid[0] == claims["sub"]
assert logged_user.cn[0] == claims["name"]
assert logged_user.user_name[0] == claims["sub"]
assert logged_user.formatted_name[0] == claims["name"]
assert [client.client_id, other_client.client_id] == claims["aud"]
res = testclient.get(

View file

@ -84,8 +84,8 @@ def test_oidc_implicit(testclient, keypair, user, client, other_client):
id_token = params["id_token"][0]
claims = jwt.decode(id_token, keypair[1])
assert user.uid[0] == claims["sub"]
assert user.cn[0] == claims["name"]
assert user.user_name[0] == claims["sub"]
assert user.formatted_name[0] == claims["name"]
assert [client.client_id, other_client.client_id] == claims["aud"]
res = testclient.get(
@ -138,8 +138,8 @@ def test_oidc_implicit_with_group(
id_token = params["id_token"][0]
claims = jwt.decode(id_token, keypair[1])
assert user.uid[0] == claims["sub"]
assert user.cn[0] == claims["name"]
assert user.user_name[0] == claims["sub"]
assert user.formatted_name[0] == claims["name"]
assert [client.client_id, other_client.client_id] == claims["aud"]
assert ["foo"] == claims["groups"]

View file

@ -20,7 +20,7 @@ def test_access_token_introspection(testclient, user, client, token):
"token_type": token.type,
"username": user.name,
"scope": token.get_scope(),
"sub": user.uid[0],
"sub": user.user_name[0],
"aud": [client.client_id],
"iss": "https://auth.mydomain.tld",
"exp": token.get_expires_at(),
@ -41,7 +41,7 @@ def test_refresh_token_introspection(testclient, user, client, token):
"token_type": token.type,
"username": user.name,
"scope": token.get_scope(),
"sub": user.uid[0],
"sub": user.user_name[0],
"aud": [client.client_id],
"iss": "https://auth.mydomain.tld",
"exp": token.get_expires_at(),
@ -111,7 +111,7 @@ def test_full_flow(testclient, logged_user, client, user, other_client):
"token_type": token.type,
"username": user.name,
"scope": token.get_scope(),
"sub": user.uid[0],
"sub": user.user_name[0],
"iss": "https://auth.mydomain.tld",
"exp": token.get_expires_at(),
"iat": token.get_issued_at(),

View file

@ -264,21 +264,21 @@ STANDARD_CLAIMS = [
"updated_at",
]
DEFAULT_JWT_MAPPING_CONFIG = {
"SUB": "{{ user.uid[0] }}",
"NAME": "{{ user.cn[0] }}",
"PHONE_NUMBER": "{{ user.telephoneNumber[0] }}",
"EMAIL": "{{ user.mail[0] }}",
"GIVEN_NAME": "{{ user.givenName[0] }}",
"FAMILY_NAME": "{{ user.sn[0] }}",
"PREFERRED_USERNAME": "{{ user.displayName }}",
"LOCALE": "{{ user.preferredLanguage }}",
"SUB": "{{ user.user_name[0] }}",
"NAME": "{{ user.formatted_name[0] }}",
"PHONE_NUMBER": "{{ user.phone_number[0] }}",
"EMAIL": "{{ user.email[0] }}",
"GIVEN_NAME": "{{ user.given_name[0] }}",
"FAMILY_NAME": "{{ user.family_name[0] }}",
"PREFERRED_USERNAME": "{{ user.display_name }}",
"LOCALE": "{{ user.preferred_language }}",
}
def test_generate_user_standard_claims_with_default_config(
testclient, slapd_connection, user
):
user.preferredLanguage = ["fr"]
user.preferred_language = ["fr"]
data = generate_user_claims(user, STANDARD_CLAIMS, DEFAULT_JWT_MAPPING_CONFIG)
@ -297,7 +297,7 @@ def test_custom_config_format_claim_is_well_formated(
testclient, slapd_connection, user
):
jwt_mapping_config = DEFAULT_JWT_MAPPING_CONFIG.copy()
jwt_mapping_config["EMAIL"] = "{{ user.uid[0] }}@mydomain.tld"
jwt_mapping_config["EMAIL"] = "{{ user.user_name[0] }}@mydomain.tld"
data = generate_user_claims(user, STANDARD_CLAIMS, jwt_mapping_config)
@ -307,7 +307,7 @@ def test_custom_config_format_claim_is_well_formated(
def test_claim_is_omitted_if_empty(testclient, slapd_connection, user):
# According to https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
# it's better to not insert a null or empty string value
user.mail = ""
user.email = ""
user.save()
data = generate_user_claims(user, STANDARD_CLAIMS, DEFAULT_JWT_MAPPING_CONFIG)

View file

@ -109,10 +109,10 @@ def test_password_page_without_signin_in_redirects_to_login_page(testclient, use
def test_user_without_password_first_login(testclient, slapd_connection, smtpd):
assert len(smtpd.messages) == 0
u = User(
cn="Temp User",
sn="Temp",
uid="temp",
mail="john@doe.com",
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
email="john@doe.com",
)
u.save()
@ -143,10 +143,10 @@ def test_first_login_account_initialization_mail_sending_failed(
assert len(smtpd.messages) == 0
u = User(
cn="Temp User",
sn="Temp",
uid="temp",
mail="john@doe.com",
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
email="john@doe.com",
)
u.save()
@ -165,10 +165,10 @@ def test_first_login_account_initialization_mail_sending_failed(
def test_first_login_form_error(testclient, slapd_connection, smtpd):
assert len(smtpd.messages) == 0
u = User(
cn="Temp User",
sn="Temp",
uid="temp",
mail="john@doe.com",
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
email="john@doe.com",
)
u.save()
@ -187,11 +187,11 @@ def test_first_login_page_unavailable_for_users_with_password(
def test_user_password_deleted_during_login(testclient, slapd_connection):
u = User(
cn="Temp User",
sn="Temp",
uid="temp",
mail="john@doe.com",
userPassword="{SSHA}Yr1ZxSljRsKyaTB30suY2iZ1KRTStF1X",
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
email="john@doe.com",
password="{SSHA}Yr1ZxSljRsKyaTB30suY2iZ1KRTStF1X",
)
u.save()
@ -200,7 +200,7 @@ def test_user_password_deleted_during_login(testclient, slapd_connection):
res = res.form.submit().follow()
res.form["password"] = "correct horse battery staple"
u.userPassword = None
u.password = None
u.save()
res = res.form.submit(status=302)
@ -211,11 +211,11 @@ def test_user_password_deleted_during_login(testclient, slapd_connection):
def test_user_deleted_in_session(testclient, slapd_connection):
u = User(
cn="Jake Doe",
sn="Jake",
uid="jake",
mail="jake@doe.com",
userPassword="{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz",
formatted_name="Jake Doe",
family_name="Jake",
user_name="jake",
email="jake@doe.com",
password="{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz",
)
u.save()
testclient.get("/profile/jake", status=403)
@ -235,7 +235,7 @@ def test_impersonate(testclient, logged_admin, user):
res = (
testclient.get("/", status=302).follow(status=200).click("Account information")
)
assert "admin" == res.form["uid"].value
assert "admin" == res.form["user_name"].value
res = (
testclient.get("/impersonate/user", status=302)
@ -243,14 +243,14 @@ def test_impersonate(testclient, logged_admin, user):
.follow(status=200)
.click("Account information")
)
assert "user" == res.form["uid"].value
assert "user" == res.form["user_name"].value
testclient.get("/logout", status=302).follow(status=302).follow(status=200)
res = (
testclient.get("/", status=302).follow(status=200).click("Account information")
)
assert "admin" == res.form["uid"].value
assert "admin" == res.form["user_name"].value
def test_wrong_login(testclient, user):
@ -275,11 +275,11 @@ def test_wrong_login(testclient, user):
def test_admin_self_deletion(testclient, slapd_connection):
admin = User(
cn="Temp admin",
sn="admin",
uid="temp",
mail="temp@temp.com",
userPassword="{SSHA}Vmgh2jkD0idX3eZHf8RzGos31oerjGiU",
formatted_name="Temp admin",
family_name="admin",
user_name="temp",
email="temp@temp.com",
password="{SSHA}Vmgh2jkD0idX3eZHf8RzGos31oerjGiU",
)
admin.save()
with testclient.session_transaction() as sess:
@ -300,11 +300,11 @@ def test_admin_self_deletion(testclient, slapd_connection):
def test_user_self_deletion(testclient, slapd_connection):
user = User(
cn="Temp user",
sn="user",
uid="temp",
mail="temp@temp.com",
userPassword="{SSHA}Vmgh2jkD0idX3eZHf8RzGos31oerjGiU",
formatted_name="Temp user",
family_name="user",
user_name="temp",
email="temp@temp.com",
password="{SSHA}Vmgh2jkD0idX3eZHf8RzGos31oerjGiU",
)
user.save()
with testclient.session_transaction() as sess:
@ -347,6 +347,6 @@ def test_login_placeholder(testclient):
placeholder = testclient.get("/login").form["login"].attrs["placeholder"]
assert placeholder == "john@doe.com"
testclient.app.config["LDAP"]["USER_FILTER"] = "(|(uid={login})(email={login}))"
testclient.app.config["LDAP"]["USER_FILTER"] = "(|(uid={login})(mail={login}))"
placeholder = testclient.get("/login").form["login"].attrs["placeholder"]
assert placeholder == "jdoe or john@doe.com"

View file

@ -66,7 +66,7 @@ def test_logging_to_file(configuration, tmp_path, smtpd, admin, slapd_server):
sess["user_id"] = [admin.id]
res = testclient.get("/admin/mail")
res.form["mail"] = "test@test.com"
res.form["email"] = "test@test.com"
res = res.form.submit()
g.ldap_connection.unbind_s()

View file

@ -5,7 +5,7 @@ def test_password_forgotten_disabled(smtpd, testclient, user):
testclient.app.config["ENABLE_PASSWORD_RECOVERY"] = False
testclient.get("/reset", status=404)
testclient.get("/reset/uid/hash", status=404)
testclient.get("/reset/user_name/hash", status=404)
res = testclient.get("/login")
res.mustcontain(no="Forgotten password")

View file

@ -85,10 +85,10 @@ def test_set_groups(app, user, foo_group, bar_group):
def test_set_groups_with_leading_space_in_user_id_attribute(app, foo_group):
user = User(
cn=" Doe", # leading space in id attribute
sn="Doe",
uid="user2",
mail="john@doe.com",
formatted_name=" Doe", # leading space in id attribute
family_name="Doe",
user_name="user2",
email="john@doe.com",
)
user.save()
@ -181,7 +181,7 @@ def test_get_members_filters_non_existent_user(
testclient, logged_moderator, foo_group, user
):
# an LDAP group can be inconsistent by containing members which doesn't exist
non_existent_user = User(cn="foo", sn="bar")
non_existent_user = User(formatted_name="foo", family_name="bar")
foo_group.member = foo_group.member + [non_existent_user]
foo_group.save()

View file

@ -3,44 +3,44 @@ from flask_babel import refresh
def test_preferred_language(slapd_server, testclient, logged_user):
logged_user.preferredLanguage = None
logged_user.preferred_language = None
logged_user.save()
res = testclient.get("/profile/user", status=200)
assert res.form["preferredLanguage"].value == "auto"
assert res.form["preferred_language"].value == "auto"
assert res.pyquery("html")[0].attrib["lang"] == "en"
res.mustcontain("My profile")
res.mustcontain(no="Mon profil")
res.form["preferredLanguage"] = "fr"
res.form["preferred_language"] = "fr"
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Le profil a été mis à jour avec succès.")]
res = res.follow()
logged_user = User.get(id=logged_user.id)
assert logged_user.preferredLanguage == "fr"
assert res.form["preferredLanguage"].value == "fr"
assert logged_user.preferred_language == "fr"
assert res.form["preferred_language"].value == "fr"
assert res.pyquery("html")[0].attrib["lang"] == "fr"
res.mustcontain(no="My profile")
res.mustcontain("Mon profil")
res.form["preferredLanguage"] = "en"
res.form["preferred_language"] = "en"
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfuly.")]
res = res.follow()
logged_user = User.get(id=logged_user.id)
assert logged_user.preferredLanguage == "en"
assert res.form["preferredLanguage"].value == "en"
assert logged_user.preferred_language == "en"
assert res.form["preferred_language"].value == "en"
assert res.pyquery("html")[0].attrib["lang"] == "en"
res.mustcontain("My profile")
res.mustcontain(no="Mon profil")
res.form["preferredLanguage"] = "auto"
res.form["preferred_language"] = "auto"
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfuly.")]
res = res.follow()
logged_user = User.get(id=logged_user.id)
assert logged_user.preferredLanguage is None
assert res.form["preferredLanguage"].value == "auto"
assert logged_user.preferred_language is None
assert res.form["preferred_language"].value == "auto"
assert res.pyquery("html")[0].attrib["lang"] == "en"
res.mustcontain("My profile")
res.mustcontain(no="Mon profil")
@ -51,7 +51,7 @@ def test_form_translations(slapd_server, testclient, logged_user):
logged_user.save()
res = testclient.get("/profile/user", status=200)
res.form["mail"] = "invalid"
res.form["email"] = "invalid"
res = res.form.submit(name="action", value="edit")
res.mustcontain(no="Invalid email address.")
@ -59,7 +59,7 @@ def test_form_translations(slapd_server, testclient, logged_user):
def test_language_config(testclient, logged_user):
logged_user.preferredLanguage = None
logged_user.preferred_language = None
logged_user.save()
res = testclient.get("/profile/user", status=200)

View file

@ -9,9 +9,9 @@ def test_invitation(testclient, logged_admin, foo_group, smtpd):
res = testclient.get("/invite", status=200)
res.form["uid"] = "someone"
res.form["uid_editable"] = False
res.form["mail"] = "someone@domain.tld"
res.form["user_name"] = "someone"
res.form["user_name_editable"] = False
res.form["email"] = "someone@domain.tld"
res.form["groups"] = [foo_group.id]
res = res.form.submit(name="action", value="send", status=200)
assert len(smtpd.messages) == 1
@ -24,15 +24,15 @@ def test_invitation(testclient, logged_admin, foo_group, smtpd):
res = testclient.get(url, status=200)
assert res.form["uid"].value == "someone"
assert res.form["uid"].attrs["readonly"]
assert res.form["mail"].value == "someone@domain.tld"
assert res.form["user_name"].value == "someone"
assert res.form["user_name"].attrs["readonly"]
assert res.form["email"].value == "someone@domain.tld"
assert res.form["groups"].value == [foo_group.id]
res.form["password1"] = "whatever"
res.form["password2"] = "whatever"
res.form["givenName"] = "George"
res.form["sn"] = "Abitbol"
res.form["given_name"] = "George"
res.form["family_name"] = "Abitbol"
res = res.form.submit(status=302)
@ -53,15 +53,15 @@ def test_invitation(testclient, logged_admin, foo_group, smtpd):
user.delete()
def test_invitation_editable_uid(testclient, logged_admin, foo_group, smtpd):
def test_invitation_editable_user_name(testclient, logged_admin, foo_group, smtpd):
assert User.get("jackyjack") is None
assert User.get("djorje") is None
res = testclient.get("/invite", status=200)
res.form["uid"] = "jackyjack"
res.form["uid_editable"] = True
res.form["mail"] = "jackyjack@domain.tld"
res.form["user_name"] = "jackyjack"
res.form["user_name_editable"] = True
res.form["email"] = "jackyjack@domain.tld"
res.form["groups"] = [foo_group.id]
res = res.form.submit(name="action", value="send", status=200)
assert len(smtpd.messages) == 1
@ -74,16 +74,16 @@ def test_invitation_editable_uid(testclient, logged_admin, foo_group, smtpd):
res = testclient.get(url, status=200)
assert res.form["uid"].value == "jackyjack"
assert "readonly" not in res.form["uid"].attrs
assert res.form["mail"].value == "jackyjack@domain.tld"
assert res.form["user_name"].value == "jackyjack"
assert "readonly" not in res.form["user_name"].attrs
assert res.form["email"].value == "jackyjack@domain.tld"
assert res.form["groups"].value == [foo_group.id]
res.form["uid"] = "djorje"
res.form["user_name"] = "djorje"
res.form["password1"] = "whatever"
res.form["password2"] = "whatever"
res.form["givenName"] = "George"
res.form["sn"] = "Abitbol"
res.form["given_name"] = "George"
res.form["family_name"] = "Abitbol"
res = res.form.submit(status=302)
@ -107,8 +107,8 @@ def test_generate_link(testclient, logged_admin, foo_group, smtpd):
res = testclient.get("/invite", status=200)
res.form["uid"] = "sometwo"
res.form["mail"] = "sometwo@domain.tld"
res.form["user_name"] = "sometwo"
res.form["email"] = "sometwo@domain.tld"
res.form["groups"] = [foo_group.id]
res = res.form.submit(name="action", value="generate", status=200)
assert len(smtpd.messages) == 0
@ -121,14 +121,14 @@ def test_generate_link(testclient, logged_admin, foo_group, smtpd):
res = testclient.get(url, status=200)
assert res.form["uid"].value == "sometwo"
assert res.form["mail"].value == "sometwo@domain.tld"
assert res.form["user_name"].value == "sometwo"
assert res.form["email"].value == "sometwo@domain.tld"
assert res.form["groups"].value == [foo_group.id]
res.form["password1"] = "whatever"
res.form["password2"] = "whatever"
res.form["givenName"] = "George"
res.form["sn"] = "Abitbol"
res.form["given_name"] = "George"
res.form["family_name"] = "Abitbol"
res = res.form.submit(status=302)
res = res.follow(status=200)
@ -150,8 +150,8 @@ def test_generate_link(testclient, logged_admin, foo_group, smtpd):
def test_invitation_login_already_taken(testclient, logged_admin):
res = testclient.get("/invite", status=200)
res.form["uid"] = logged_admin.uid
res.form["mail"] = logged_admin.mail[0]
res.form["user_name"] = logged_admin.user_name
res.form["email"] = logged_admin.email[0]
res = res.form.submit(name="action", value="send", status=200)
res.mustcontain("The login &#39;admin&#39; already exists")
@ -272,7 +272,7 @@ def test_groups_are_saved_even_when_user_does_not_have_read_permission(
testclient, foo_group
):
testclient.app.config["ACL"]["DEFAULT"]["READ"] = [
"uid"
"user_name"
] # remove groups from default read permissions
invitation = Invitation(
@ -292,8 +292,8 @@ def test_groups_are_saved_even_when_user_does_not_have_read_permission(
res.form["password1"] = "whatever"
res.form["password2"] = "whatever"
res.form["givenName"] = "George"
res.form["sn"] = "Abitbol"
res.form["given_name"] = "George"
res.form["family_name"] = "Abitbol"
res = res.form.submit(status=302)
res = res.follow(status=200)

View file

@ -11,7 +11,7 @@ def test_send_test_email(testclient, logged_admin, smtpd):
assert len(smtpd.messages) == 0
res = testclient.get("/admin/mail")
res.form["mail"] = "test@test.com"
res.form["email"] = "test@test.com"
res = res.form.submit()
assert (
"success",
@ -33,7 +33,7 @@ def test_send_test_email_ssl(testclient, logged_admin, smtpd):
assert len(smtpd.messages) == 0
res = testclient.get("/admin/mail")
res.form["mail"] = "test@test.com"
res.form["email"] = "test@test.com"
res = res.form.submit()
assert (
"success",
@ -50,7 +50,7 @@ def test_send_test_email_without_credentials(testclient, logged_admin, smtpd):
assert len(smtpd.messages) == 0
res = testclient.get("/admin/mail")
res.form["mail"] = "test@test.com"
res.form["email"] = "test@test.com"
res = res.form.submit()
assert (
"success",
@ -68,7 +68,7 @@ def test_send_test_email_recipient_refused(SMTP, testclient, logged_admin, smtpd
assert len(smtpd.messages) == 0
res = testclient.get("/admin/mail")
res.form["mail"] = "test@test.com"
res.form["email"] = "test@test.com"
res = res.form.submit()
assert (
"success",
@ -81,7 +81,7 @@ def test_send_test_email_recipient_refused(SMTP, testclient, logged_admin, smtpd
def test_send_test_email_failed(testclient, logged_admin):
testclient.app.config["SMTP"]["TLS"] = False
res = testclient.get("/admin/mail")
res.form["mail"] = "test@test.com"
res.form["email"] = "test@test.com"
with warnings.catch_warnings(record=True):
res = res.form.submit(expect_errors=True)
assert (
@ -95,7 +95,7 @@ def test_mail_with_default_no_logo(testclient, logged_admin, smtpd):
assert len(smtpd.messages) == 0
res = testclient.get(f"/admin/mail")
res.form["mail"] = "test@test.com"
res.form["email"] = "test@test.com"
res = res.form.submit()
assert (
"success",
@ -118,7 +118,7 @@ def test_mail_with_default_logo(testclient, logged_admin, smtpd, httpserver):
assert len(smtpd.messages) == 0
res = testclient.get(f"http://{httpserver.host}:{httpserver.port}/admin/mail")
res.form["mail"] = "test@test.com"
res.form["email"] = "test@test.com"
res = res.form.submit()
assert (
"success",
@ -146,7 +146,7 @@ def test_mail_with_logo_in_http(testclient, logged_admin, smtpd, httpserver):
assert len(smtpd.messages) == 0
res = testclient.get("/admin/mail")
res.form["mail"] = "test@test.com"
res.form["email"] = "test@test.com"
res = res.form.submit()
assert (
"success",

View file

@ -2,7 +2,7 @@ from canaille.account import profile_hash
def test_password_reset(testclient, user):
hash = profile_hash("user", user.mail[0], user.userPassword[0])
hash = profile_hash("user", user.email[0], user.password[0])
res = testclient.get("/reset/user/" + hash, status=200)
@ -30,7 +30,7 @@ def test_password_reset_bad_link(testclient, user):
def test_password_reset_bad_password(testclient, user):
hash = profile_hash("user", user.mail[0], user.userPassword[0])
hash = profile_hash("user", user.email[0], user.password[0])
res = testclient.get("/reset/user/" + hash, status=200)

View file

@ -11,11 +11,11 @@ def test_user_creation_edition_and_deletion(
# Fill the profile for a new user.
res = testclient.get("/profile", status=200)
res.form["uid"] = "george"
res.form["givenName"] = "George"
res.form["sn"] = "Abitbol"
res.form["mail"] = "george@abitbol.com"
res.form["telephoneNumber"] = "555-666-888"
res.form["user_name"] = "george"
res.form["given_name"] = "George"
res.form["family_name"] = "Abitbol"
res.form["email"] = "george@abitbol.com"
res.form["phone_number"] = "555-666-888"
res.form["groups"] = [foo_group.id]
res.form["password1"] = "totoyolo"
res.form["password2"] = "totoyolo"
@ -27,7 +27,7 @@ def test_user_creation_edition_and_deletion(
george = User.get("george")
george.load_groups()
foo_group.reload()
assert "George" == george.givenName[0]
assert "George" == george.given_name[0]
assert george.groups == [foo_group]
assert george.check_password("totoyolo")
@ -39,7 +39,7 @@ def test_user_creation_edition_and_deletion(
# User have been edited
res = testclient.get("/profile/george", status=200)
res.form["givenName"] = "Georgio"
res.form["given_name"] = "Georgio"
res = res.form.submit(name="action", value="edit").follow()
res = testclient.get("/profile/george/settings", status=200)
@ -48,7 +48,7 @@ def test_user_creation_edition_and_deletion(
george = User.get("george")
george.load_groups()
assert "Georgio" == george.givenName[0]
assert "Georgio" == george.given_name[0]
assert george.check_password("totoyolo")
foo_group.reload()
@ -72,11 +72,11 @@ def test_profile_creation_dynamic_validation(testclient, logged_admin, user):
f"/profile",
{
"csrf_token": res.form["csrf_token"].value,
"mail": "john@doe.com",
"email": "john@doe.com",
},
headers={
"HX-Request": "true",
"HX-Trigger-Name": "mail",
"HX-Trigger-Name": "email",
},
)
res.mustcontain("The email &#39;john@doe.com&#39; is already used")
@ -84,15 +84,15 @@ def test_profile_creation_dynamic_validation(testclient, logged_admin, user):
def test_user_creation_without_password(testclient, logged_moderator):
res = testclient.get("/profile", status=200)
res.form["uid"] = "george"
res.form["sn"] = "Abitbol"
res.form["mail"] = "george@abitbol.com"
res.form["user_name"] = "george"
res.form["family_name"] = "Abitbol"
res.form["email"] = "george@abitbol.com"
res = res.form.submit(name="action", value="edit", status=302)
assert ("success", "User account creation succeed.") in res.flashes
res = res.follow(status=200)
george = User.get("george")
assert george.uid[0] == "george"
assert george.user_name[0] == "george"
assert not george.userPassword
george.delete()
@ -115,9 +115,9 @@ def test_username_already_taken(
testclient, logged_moderator, user, foo_group, bar_group
):
res = testclient.get("/profile", status=200)
res.form["uid"] = "user"
res.form["sn"] = "foo"
res.form["mail"] = "any@thing.com"
res.form["user_name"] = "user"
res.form["family_name"] = "foo"
res.form["email"] = "any@thing.com"
res = res.form.submit(name="action", value="edit")
assert ("error", "User account creation failed.") in res.flashes
res.mustcontain("The login &#39;user&#39; already exists")
@ -125,9 +125,9 @@ def test_username_already_taken(
def test_email_already_taken(testclient, logged_moderator, user, foo_group, bar_group):
res = testclient.get("/profile", status=200)
res.form["uid"] = "user2"
res.form["sn"] = "foo"
res.form["mail"] = "john@doe.com"
res.form["user_name"] = "user2"
res.form["family_name"] = "foo"
res.form["email"] = "john@doe.com"
res = res.form.submit(name="action", value="edit")
assert ("error", "User account creation failed.") in res.flashes
res.mustcontain("The email &#39;john@doe.com&#39; is already used")
@ -135,10 +135,10 @@ def test_email_already_taken(testclient, logged_moderator, user, foo_group, bar_
def test_cn_setting_with_given_name_and_surname(testclient, logged_moderator):
res = testclient.get("/profile", status=200)
res.form["uid"] = "george"
res.form["givenName"] = "George"
res.form["sn"] = "Abitbol"
res.form["mail"] = "george@abitbol.com"
res.form["user_name"] = "george"
res.form["given_name"] = "George"
res.form["family_name"] = "Abitbol"
res.form["email"] = "george@abitbol.com"
res = res.form.submit(name="action", value="edit", status=302).follow(status=200)
@ -149,9 +149,9 @@ def test_cn_setting_with_given_name_and_surname(testclient, logged_moderator):
def test_cn_setting_with_surname_only(testclient, logged_moderator):
res = testclient.get("/profile", status=200)
res.form["uid"] = "george"
res.form["sn"] = "Abitbol"
res.form["mail"] = "george@abitbol.com"
res.form["user_name"] = "george"
res.form["family_name"] = "Abitbol"
res.form["email"] = "george@abitbol.com"
res = res.form.submit(name="action", value="edit", status=302).follow(status=200)

View file

@ -72,7 +72,7 @@ def test_user_list_search_only_allowed_fields(
res.mustcontain(user.name)
res.mustcontain(no=moderator.name)
testclient.app.config["ACL"]["DEFAULT"]["READ"].remove("uid")
testclient.app.config["ACL"]["DEFAULT"]["READ"].remove("user_name")
form = res.forms["search"]
form["query"] = "user"
@ -104,22 +104,22 @@ def test_edition(
jpeg_photo,
):
res = testclient.get("/profile/user", status=200)
res.form["givenName"] = "given_name"
res.form["sn"] = "family_name"
res.form["displayName"] = "display_name"
res.form["mail"] = "email@mydomain.tld"
res.form["telephoneNumber"] = "555-666-777"
res.form["postalAddress"] = "postal_address"
res.form["given_name"] = "given_name"
res.form["family_name"] = "family_name"
res.form["display_name"] = "display_name"
res.form["email"] = "email@mydomain.tld"
res.form["phone_number"] = "555-666-777"
res.form["formatted_address"] = "formatted_address"
res.form["street"] = "street"
res.form["postalCode"] = "postal_code"
res.form["l"] = "locality"
res.form["st"] = "region"
res.form["employeeNumber"] = 666
res.form["departmentNumber"] = 1337
res.form["postal_code"] = "postal_code"
res.form["locality"] = "locality"
res.form["region"] = "region"
res.form["employee_number"] = 666
res.form["department"] = 1337
res.form["title"] = "title"
res.form["o"] = "organization"
res.form["preferredLanguage"] = "fr"
res.form["jpegPhoto"] = Upload("logo.jpg", jpeg_photo)
res.form["organization"] = "organization"
res.form["preferred_language"] = "fr"
res.form["photo"] = Upload("logo.jpg", jpeg_photo)
res = res.form.submit(name="action", value="edit")
assert res.flashes == [
@ -129,28 +129,28 @@ def test_edition(
logged_user = User.get(id=logged_user.id)
assert logged_user.givenName == ["given_name"]
assert logged_user.sn == ["family_name"]
assert logged_user.displayName == "display_name"
assert logged_user.given_name == ["given_name"]
assert logged_user.family_name == ["family_name"]
assert logged_user.display_name == "display_name"
assert logged_user.mail == ["email@mydomain.tld"]
assert logged_user.telephoneNumber == ["555-666-777"]
assert logged_user.postalAddress == ["postal_address"]
assert logged_user.phone_number == ["555-666-777"]
assert logged_user.formatted_address == ["formatted_address"]
assert logged_user.street == ["street"]
assert logged_user.postalCode == ["postal_code"]
assert logged_user.l == ["locality"]
assert logged_user.st == ["region"]
assert logged_user.preferredLanguage == "fr"
assert logged_user.employeeNumber == "666"
assert logged_user.departmentNumber == ["1337"]
assert logged_user.postal_code == ["postal_code"]
assert logged_user.locality == ["locality"]
assert logged_user.region == ["region"]
assert logged_user.preferred_language == "fr"
assert logged_user.employee_number == "666"
assert logged_user.department == ["1337"]
assert logged_user.title == ["title"]
assert logged_user.o == ["organization"]
assert logged_user.organization == ["organization"]
assert logged_user.jpegPhoto == [jpeg_photo]
logged_user.cn = ["John (johnny) Doe"]
logged_user.sn = ["Doe"]
logged_user.family_name = ["Doe"]
logged_user.mail = ["john@doe.com"]
logged_user.givenName = None
logged_user.jpegPhoto = None
logged_user.given_name = None
logged_user.photo = None
logged_user.save()
@ -160,11 +160,11 @@ def test_profile_edition_dynamic_validation(testclient, logged_admin, user):
f"/profile/admin",
{
"csrf_token": res.form["csrf_token"].value,
"mail": "john@doe.com",
"email": "john@doe.com",
},
headers={
"HX-Request": "true",
"HX-Trigger-Name": "mail",
"HX-Trigger-Name": "email",
},
)
res.mustcontain("The email &#39;john@doe.com&#39; is already used")
@ -172,78 +172,78 @@ def test_profile_edition_dynamic_validation(testclient, logged_admin, user):
def test_field_permissions_none(testclient, slapd_server, logged_user):
testclient.get("/profile/user", status=200)
logged_user.telephoneNumber = ["555-666-777"]
logged_user.phone_number = ["555-666-777"]
logged_user.save()
testclient.app.config["ACL"]["DEFAULT"] = {
"READ": ["uid"],
"READ": ["user_name"],
"WRITE": [],
"PERMISSIONS": ["edit_self"],
}
res = testclient.get("/profile/user", status=200)
assert "telephoneNumber" not in res.form.fields
assert "phone_number" not in res.form.fields
testclient.post(
"/profile/user",
{
"action": "edit",
"telephoneNumber": "000-000-000",
"phone_number": "000-000-000",
"csrf_token": res.form["csrf_token"].value,
},
)
user = User.get(id=logged_user.id)
assert user.telephoneNumber == ["555-666-777"]
assert user.phone_number == ["555-666-777"]
def test_field_permissions_read(testclient, slapd_server, logged_user):
testclient.get("/profile/user", status=200)
logged_user.telephoneNumber = ["555-666-777"]
logged_user.phone_number = ["555-666-777"]
logged_user.save()
testclient.app.config["ACL"]["DEFAULT"] = {
"READ": ["uid", "telephoneNumber"],
"READ": ["user_name", "phone_number"],
"WRITE": [],
"PERMISSIONS": ["edit_self"],
}
res = testclient.get("/profile/user", status=200)
assert "telephoneNumber" in res.form.fields
assert "phone_number" in res.form.fields
testclient.post(
"/profile/user",
{
"action": "edit",
"telephoneNumber": "000-000-000",
"phone_number": "000-000-000",
"csrf_token": res.form["csrf_token"].value,
},
)
user = User.get(id=logged_user.id)
assert user.telephoneNumber == ["555-666-777"]
assert user.phone_number == ["555-666-777"]
def test_field_permissions_write(testclient, slapd_server, logged_user):
testclient.get("/profile/user", status=200)
logged_user.telephoneNumber = ["555-666-777"]
logged_user.phone_number = ["555-666-777"]
logged_user.save()
testclient.app.config["ACL"]["DEFAULT"] = {
"READ": ["uid"],
"WRITE": ["telephoneNumber"],
"READ": ["user_name"],
"WRITE": ["phone_number"],
"PERMISSIONS": ["edit_self"],
}
res = testclient.get("/profile/user", status=200)
assert "telephoneNumber" in res.form.fields
assert "phone_number" in res.form.fields
testclient.post(
"/profile/user",
{
"action": "edit",
"telephoneNumber": "000-000-000",
"phone_number": "000-000-000",
"csrf_token": res.form["csrf_token"].value,
},
)
user = User.get(id=logged_user.id)
assert user.telephoneNumber == ["000-000-000"]
assert user.phone_number == ["000-000-000"]
def test_simple_user_cannot_edit_other(testclient, logged_user):
@ -269,7 +269,7 @@ def test_admin_bad_request(testclient, logged_moderator):
def test_bad_email(testclient, logged_user):
res = testclient.get("/profile/user", status=200)
res.form["mail"] = "john@doe.com"
res.form["email"] = "john@doe.com"
res = res.form.submit(name="action", value="edit").follow()
@ -277,7 +277,7 @@ def test_bad_email(testclient, logged_user):
res = testclient.get("/profile/user", status=200)
res.form["mail"] = "yolo"
res.form["email"] = "yolo"
res = res.form.submit(name="action", value="edit", status=200)
@ -288,12 +288,12 @@ def test_bad_email(testclient, logged_user):
def test_surname_is_mandatory(testclient, logged_user):
res = testclient.get("/profile/user", status=200)
logged_user.sn = ["Doe"]
logged_user.family_name = ["Doe"]
res.form["sn"] = ""
res.form["family_name"] = ""
res = res.form.submit(name="action", value="edit", status=200)
logged_user.reload()
assert ["Doe"] == logged_user.sn
assert ["Doe"] == logged_user.family_name

View file

@ -5,18 +5,18 @@ from webtest import Upload
def test_photo(testclient, user, jpeg_photo):
user.jpegPhoto = [jpeg_photo]
user.photo = [jpeg_photo]
user.save()
user = User.get(id=user.id)
res = testclient.get("/profile/user/jpegPhoto")
res = testclient.get("/profile/user/photo")
assert res.body == jpeg_photo
assert res.last_modified == user.modifyTimestamp
assert res.last_modified == user.last_modified
etag = res.etag
assert etag
res = testclient.get(
"/profile/user/jpegPhoto",
"/profile/user/photo",
headers={
"If-Modified-Since": (
res.last_modified + datetime.timedelta(days=1)
@ -27,7 +27,7 @@ def test_photo(testclient, user, jpeg_photo):
assert not res.body
res = testclient.get(
"/profile/user/jpegPhoto",
"/profile/user/photo",
headers={"If-None-Match": etag},
status=304,
)
@ -35,12 +35,12 @@ def test_photo(testclient, user, jpeg_photo):
def test_photo_invalid_user(testclient, user):
res = testclient.get("/profile/invalid/jpegPhoto", status=404)
res = testclient.get("/profile/invalid/photo", status=404)
def test_photo_absent(testclient, user):
assert not user.jpegPhoto
res = testclient.get("/profile/user/jpegPhoto", status=404)
assert not user.photo
res = testclient.get("/profile/user/photo", status=404)
def test_photo_invalid_path(testclient, user):
@ -55,49 +55,49 @@ def test_photo_on_profile_edition(
):
# Add a photo
res = testclient.get("/profile/user", status=200)
res.form["jpegPhoto"] = Upload("logo.jpg", jpeg_photo)
res.form["jpegPhoto_delete"] = False
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
res = res.follow()
logged_user = User.get(id=logged_user.id)
assert [jpeg_photo] == logged_user.jpegPhoto
assert [jpeg_photo] == logged_user.photo
# No change
res = testclient.get("/profile/user", status=200)
res.form["jpegPhoto_delete"] = False
res.form["photo_delete"] = False
res = res.form.submit(name="action", value="edit")
assert ("success", "Profile updated successfuly.") in res.flashes
res = res.follow()
logged_user = User.get(id=logged_user.id)
assert [jpeg_photo] == logged_user.jpegPhoto
assert [jpeg_photo] == logged_user.photo
# Photo deletion
res = testclient.get("/profile/user", status=200)
res.form["jpegPhoto_delete"] = True
res.form["photo_delete"] = True
res = res.form.submit(name="action", value="edit")
assert ("success", "Profile updated successfuly.") in res.flashes
res = res.follow()
logged_user = User.get(id=logged_user.id)
assert [] == logged_user.jpegPhoto
assert [] == logged_user.photo
# 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.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
res = res.follow()
logged_user = User.get(id=logged_user.id)
assert [] == logged_user.jpegPhoto
assert [] == logged_user.photo
def test_photo_on_profile_creation(testclient, slapd_server, jpeg_photo, logged_admin):
@ -106,14 +106,14 @@ def test_photo_on_profile_creation(testclient, slapd_server, jpeg_photo, logged_
res.mustcontain(no="foobar")
res = testclient.get("/profile", status=200)
res.form["jpegPhoto"] = Upload("logo.jpg", jpeg_photo)
res.form["uid"] = "foobar"
res.form["sn"] = "Abitbol"
res.form["mail"] = "george@abitbol.com"
res.form["photo"] = Upload("logo.jpg", jpeg_photo)
res.form["user_name"] = "foobar"
res.form["family_name"] = "Abitbol"
res.form["email"] = "george@abitbol.com"
res = res.form.submit(name="action", value="edit", status=302).follow(status=200)
user = User.get("foobar")
assert user.jpegPhoto == [jpeg_photo]
assert user.photo == [jpeg_photo]
user.delete()
@ -125,13 +125,13 @@ def test_photo_deleted_on_profile_creation(
res.mustcontain(no="foobar")
res = testclient.get("/profile", status=200)
res.form["jpegPhoto"] = Upload("logo.jpg", jpeg_photo)
res.form["jpegPhoto_delete"] = True
res.form["uid"] = "foobar"
res.form["sn"] = "Abitbol"
res.form["mail"] = "george@abitbol.com"
res.form["photo"] = Upload("logo.jpg", jpeg_photo)
res.form["photo_delete"] = True
res.form["user_name"] = "foobar"
res.form["family_name"] = "Abitbol"
res.form["email"] = "george@abitbol.com"
res = res.form.submit(name="action", value="edit", status=302).follow(status=200)
user = User.get("foobar")
assert user.jpegPhoto == []
assert user.photo == []
user.delete()

View file

@ -20,9 +20,9 @@ def test_edition(
assert foo_group.members == [logged_user]
assert bar_group.members == [admin]
assert res.form["groups"].attrs["readonly"]
assert res.form["uid"].attrs["readonly"]
assert res.form["user_name"].attrs["readonly"]
res.form["uid"] = "toto"
res.form["user_name"] = "toto"
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfuly.")]
res = res.follow()
@ -30,7 +30,7 @@ def test_edition(
logged_user = User.get(id=logged_user.id)
logged_user.load_groups()
assert logged_user.uid == ["user"]
assert logged_user.user_name == ["user"]
foo_group.reload()
bar_group.reload()
@ -40,7 +40,7 @@ def test_edition(
assert logged_user.check_password("correct horse battery staple")
logged_user.uid = ["user"]
logged_user.user_name = ["user"]
logged_user.save()
@ -75,10 +75,10 @@ def test_edition_without_groups(
logged_user = User.get(id=logged_user.id)
assert logged_user.uid == ["user"]
assert logged_user.user_name == ["user"]
assert logged_user.check_password("correct horse battery staple")
logged_user.uid = ["user"]
logged_user.user_name = ["user"]
logged_user.save()
@ -128,10 +128,10 @@ def test_password_initialization_mail(
smtpd, testclient, slapd_connection, logged_admin
):
u = User(
cn="Temp User",
sn="Temp",
uid="temp",
mail="john@doe.com",
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
email="john@doe.com",
)
u.save()
@ -148,7 +148,7 @@ def test_password_initialization_mail(
assert len(smtpd.messages) == 1
u.reload()
u.userPassword = ["{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz"]
u.password = ["{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz"]
u.save()
res = testclient.get("/profile/temp/settings", status=200)
@ -163,10 +163,10 @@ def test_password_initialization_mail_send_fail(
):
SMTP.side_effect = mock.Mock(side_effect=OSError("unit test mail error"))
u = User(
cn="Temp User",
sn="Temp",
uid="temp",
mail="john@doe.com",
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
email="john@doe.com",
)
u.save()
@ -235,11 +235,11 @@ def test_invalid_form_request(testclient, logged_admin):
def test_password_reset_email(smtpd, testclient, slapd_connection, logged_admin):
u = User(
cn="Temp User",
sn="Temp",
uid="temp",
mail="john@doe.com",
userPassword=["{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz"],
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
email="john@doe.com",
password="{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz",
)
u.save()
@ -264,11 +264,11 @@ def test_password_reset_email_failed(
):
SMTP.side_effect = mock.Mock(side_effect=OSError("unit test mail error"))
u = User(
cn="Temp User",
sn="Temp",
uid="temp",
mail="john@doe.com",
userPassword=["{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz"],
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
email="john@doe.com",
password=["{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz"],
)
u.save()