forked from Github-Mirrors/canaille
Use a unique identifier to indentify users in URLS
Previously we used the uid since we supposed this value was always valid, but some users user the mail attribute as the User RDN in their OpenLDAP installation, and do not have a uuid.
This commit is contained in:
parent
4551dc3f60
commit
57af18d557
22 changed files with 143 additions and 125 deletions
|
@ -85,6 +85,10 @@ class User(canaille.core.models.User, LDAPObject):
|
|||
|
||||
return user
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
return self.rdn_value
|
||||
|
||||
def has_password(self):
|
||||
return bool(self.password)
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ def index():
|
|||
|
||||
if user.can_edit_self or user.can_manage_users:
|
||||
return redirect(
|
||||
url_for("account.profile_edition", username=current_user().user_name[0])
|
||||
url_for("account.profile_edition", identifier=current_user().identifier)
|
||||
)
|
||||
|
||||
if user.can_use_oidc:
|
||||
|
@ -82,7 +82,7 @@ def about():
|
|||
def login():
|
||||
if current_user():
|
||||
return redirect(
|
||||
url_for("account.profile_edition", username=current_user().user_name[0])
|
||||
url_for("account.profile_edition", identifier=current_user().identifier)
|
||||
)
|
||||
|
||||
form = LoginForm(request.form or None)
|
||||
|
@ -93,7 +93,7 @@ def login():
|
|||
|
||||
user = models.User.get_from_login(form.login.data)
|
||||
if user and not user.has_password():
|
||||
return redirect(url_for("account.firstlogin", user_name=user.user_name[0]))
|
||||
return redirect(url_for("account.firstlogin", identifier=user.identifier))
|
||||
|
||||
if not form.validate():
|
||||
models.User.logout()
|
||||
|
@ -119,7 +119,7 @@ def password():
|
|||
|
||||
user = models.User.get_from_login(session["attempt_login"])
|
||||
if user and not user.has_password():
|
||||
return redirect(url_for("account.firstlogin", user_name=user.user_name[0]))
|
||||
return redirect(url_for("account.firstlogin", identifier=user.identifier))
|
||||
|
||||
if not form.validate() or not user:
|
||||
models.User.logout()
|
||||
|
@ -160,15 +160,15 @@ def logout():
|
|||
return redirect("/")
|
||||
|
||||
|
||||
@bp.route("/firstlogin/<user_name>", methods=("GET", "POST"))
|
||||
def firstlogin(user_name):
|
||||
user = models.User.get_from_login(user_name)
|
||||
@bp.route("/firstlogin/<identifier>", methods=("GET", "POST"))
|
||||
def firstlogin(identifier):
|
||||
user = models.User.get_from_login(identifier)
|
||||
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, user_name=user_name)
|
||||
return render_template("firstlogin.html", form=form, identifier=identifier)
|
||||
|
||||
form.validate()
|
||||
|
||||
|
@ -182,7 +182,7 @@ def firstlogin(user_name):
|
|||
else:
|
||||
flash(_("Could not send the password initialization email"), "error")
|
||||
|
||||
return render_template("firstlogin.html", form=form, user_name=user_name)
|
||||
return render_template("firstlogin.html", form=form, identifier=identifier)
|
||||
|
||||
|
||||
@bp.route("/users", methods=["GET", "POST"])
|
||||
|
@ -360,7 +360,7 @@ def registration(data, hash):
|
|||
user = profile_create(current_app, form)
|
||||
user.login()
|
||||
flash(_("Your account has been created successfully."), "success")
|
||||
return redirect(url_for("account.profile_edition", username=user.user_name[0]))
|
||||
return redirect(url_for("account.profile_edition", identifier=user.identifier))
|
||||
|
||||
|
||||
@bp.route("/profile", methods=("GET", "POST"))
|
||||
|
@ -393,7 +393,7 @@ def profile_creation(user):
|
|||
)
|
||||
|
||||
user = profile_create(current_app, form)
|
||||
return redirect(url_for("account.profile_edition", username=user.user_name[0]))
|
||||
return redirect(url_for("account.profile_edition", identifier=user.identifier))
|
||||
|
||||
|
||||
def profile_create(current_app, form):
|
||||
|
@ -424,19 +424,19 @@ def profile_create(current_app, form):
|
|||
return user
|
||||
|
||||
|
||||
@bp.route("/profile/<username>", methods=("GET", "POST"))
|
||||
@bp.route("/profile/<identifier>", methods=("GET", "POST"))
|
||||
@user_needed()
|
||||
def profile_edition(user, username):
|
||||
def profile_edition(user, identifier):
|
||||
editor = user
|
||||
if not user.can_manage_users and not (
|
||||
user.can_edit_self and username == user.user_name[0]
|
||||
user.can_edit_self and identifier == user.identifier
|
||||
):
|
||||
abort(403)
|
||||
|
||||
menuitem = "profile" if username == editor.user_name[0] else "users"
|
||||
menuitem = "profile" if identifier == editor.identifier else "users"
|
||||
fields = editor.read | editor.write
|
||||
if username != editor.user_name[0]:
|
||||
user = models.User.get_from_login(username)
|
||||
if identifier != editor.identifier:
|
||||
user = models.User.get_from_login(identifier)
|
||||
else:
|
||||
user = editor
|
||||
|
||||
|
@ -524,18 +524,18 @@ def profile_edition(user, username):
|
|||
|
||||
user.save()
|
||||
flash(_("Profile updated successfully."), "success")
|
||||
return redirect(url_for("account.profile_edition", username=username))
|
||||
return redirect(url_for("account.profile_edition", identifier=identifier))
|
||||
|
||||
|
||||
@bp.route("/profile/<username>/settings", methods=("GET", "POST"))
|
||||
@bp.route("/profile/<identifier>/settings", methods=("GET", "POST"))
|
||||
@user_needed()
|
||||
def profile_settings(user, username):
|
||||
def profile_settings(user, identifier):
|
||||
if not user.can_manage_users and not (
|
||||
user.can_edit_self and username == user.user_name[0]
|
||||
user.can_edit_self and identifier == user.identifier
|
||||
):
|
||||
abort(403)
|
||||
|
||||
edited_user = models.User.get_from_login(username)
|
||||
edited_user = models.User.get_from_login(identifier)
|
||||
if not edited_user:
|
||||
abort(404)
|
||||
|
||||
|
@ -640,7 +640,7 @@ def profile_settings_edit(editor, edited_user):
|
|||
edited_user.save()
|
||||
flash(_("Profile updated successfully."), "success")
|
||||
return redirect(
|
||||
url_for("account.profile_settings", username=edited_user.user_name[0])
|
||||
url_for("account.profile_settings", identifier=edited_user.identifier)
|
||||
)
|
||||
|
||||
return render_template(
|
||||
|
@ -671,10 +671,10 @@ def profile_delete(user, edited_user):
|
|||
return redirect(url_for("account.users"))
|
||||
|
||||
|
||||
@bp.route("/impersonate/<username>")
|
||||
@bp.route("/impersonate/<identifier>")
|
||||
@permissions_needed("impersonate_users")
|
||||
def impersonate(user, username):
|
||||
puppet = models.User.get_from_login(username)
|
||||
def impersonate(user, identifier):
|
||||
puppet = models.User.get_from_login(identifier)
|
||||
if not puppet:
|
||||
abort(404)
|
||||
|
||||
|
@ -735,16 +735,16 @@ def forgotten():
|
|||
return render_template("forgotten-password.html", form=form)
|
||||
|
||||
|
||||
@bp.route("/reset/<user_name>/<hash>", methods=["GET", "POST"])
|
||||
def reset(user_name, hash):
|
||||
@bp.route("/reset/<identifier>/<hash>", methods=["GET", "POST"])
|
||||
def reset(identifier, hash):
|
||||
if not current_app.config.get("ENABLE_PASSWORD_RECOVERY", True):
|
||||
abort(404)
|
||||
|
||||
form = PasswordResetForm(request.form)
|
||||
user = models.User.get_from_login(user_name)
|
||||
user = models.User.get_from_login(identifier)
|
||||
|
||||
if not user or hash != profile_hash(
|
||||
user.user_name[0],
|
||||
user.identifier,
|
||||
user.preferred_email,
|
||||
user.password[0] if user.has_password() else "",
|
||||
):
|
||||
|
@ -759,19 +759,19 @@ def reset(user_name, hash):
|
|||
user.login()
|
||||
|
||||
flash(_("Your password has been updated successfully"), "success")
|
||||
return redirect(url_for("account.profile_edition", username=user_name))
|
||||
return redirect(url_for("account.profile_edition", identifier=identifier))
|
||||
|
||||
return render_template(
|
||||
"reset-password.html", form=form, user_name=user_name, hash=hash
|
||||
"reset-password.html", form=form, identifier=identifier, hash=hash
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/profile/<user_name>/<field>")
|
||||
def photo(user_name, field):
|
||||
@bp.route("/profile/<identifier>/<field>")
|
||||
def photo(identifier, field):
|
||||
if field.lower() != "photo":
|
||||
abort(404)
|
||||
|
||||
user = models.User.get_from_login(user_name)
|
||||
user = models.User.get_from_login(identifier)
|
||||
if not user:
|
||||
abort(404)
|
||||
|
||||
|
@ -779,7 +779,7 @@ def photo(user_name, field):
|
|||
if request.if_modified_since and request.if_modified_since >= user.last_modified:
|
||||
return "", 304
|
||||
|
||||
etag = profile_hash(user_name, user.last_modified.isoformat())
|
||||
etag = profile_hash(identifier, user.last_modified.isoformat())
|
||||
if request.if_none_match and etag in request.if_none_match:
|
||||
return "", 304
|
||||
|
||||
|
|
|
@ -75,8 +75,8 @@ def password_init_html(user):
|
|||
base_url = url_for("account.index", _external=True)
|
||||
reset_url = url_for(
|
||||
"account.reset",
|
||||
user_name=user.user_name[0],
|
||||
hash=profile_hash(user.user_name[0], user.preferred_email, user.password[0]),
|
||||
identifier=user.identifier,
|
||||
hash=profile_hash(user.identifier, user.preferred_email, 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",
|
||||
user_name=user.user_name[0],
|
||||
hash=profile_hash(user.user_name[0], user.preferred_email, user.password[0]),
|
||||
identifier=user.identifier,
|
||||
hash=profile_hash(user.identifier, user.preferred_email, 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",
|
||||
user_name=user.user_name[0],
|
||||
hash=profile_hash(user.user_name[0], user.preferred_email, user.password[0]),
|
||||
identifier=user.identifier,
|
||||
hash=profile_hash(user.identifier, user.preferred_email, 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",
|
||||
user_name=user.user_name[0],
|
||||
hash=profile_hash(user.user_name[0], user.preferred_email, user.password[0]),
|
||||
identifier=user.identifier,
|
||||
hash=profile_hash(user.identifier, user.preferred_email, user.password[0]),
|
||||
_external=True,
|
||||
)
|
||||
|
||||
|
@ -153,14 +153,14 @@ def password_reset_txt(user):
|
|||
)
|
||||
|
||||
|
||||
@bp.route("/mail/<user_name>/<email>/invitation.html")
|
||||
@bp.route("/mail/<identifier>/<email>/invitation.html")
|
||||
@permissions_needed("manage_oidc")
|
||||
def invitation_html(user, user_name, email):
|
||||
def invitation_html(user, identifier, email):
|
||||
base_url = url_for("account.index", _external=True)
|
||||
registration_url = url_for(
|
||||
"account.registration",
|
||||
data=obj_to_b64([user_name, email]),
|
||||
hash=profile_hash(user_name, email),
|
||||
data=obj_to_b64([identifier, email]),
|
||||
hash=profile_hash(identifier, email),
|
||||
_external=True,
|
||||
)
|
||||
|
||||
|
@ -176,14 +176,14 @@ def invitation_html(user, user_name, email):
|
|||
)
|
||||
|
||||
|
||||
@bp.route("/mail/<user_name>/<email>/invitation.txt")
|
||||
@bp.route("/mail/<identifier>/<email>/invitation.txt")
|
||||
@permissions_needed("manage_oidc")
|
||||
def invitation_txt(user, user_name, email):
|
||||
def invitation_txt(user, identifier, email):
|
||||
base_url = url_for("account.index", _external=True)
|
||||
registration_url = url_for(
|
||||
"account.registration",
|
||||
data=obj_to_b64([user_name, email]),
|
||||
hash=profile_hash(user_name, email),
|
||||
data=obj_to_b64([identifier, email]),
|
||||
hash=profile_hash(identifier, email),
|
||||
_external=True,
|
||||
)
|
||||
|
||||
|
|
|
@ -39,9 +39,9 @@ def send_password_reset_mail(user):
|
|||
base_url = url_for("account.index", _external=True)
|
||||
reset_url = url_for(
|
||||
"account.reset",
|
||||
user_name=user.user_name[0],
|
||||
identifier=user.identifier,
|
||||
hash=profile_hash(
|
||||
user.user_name[0],
|
||||
user.identifier,
|
||||
user.preferred_email,
|
||||
user.password[0] if user.has_password() else "",
|
||||
),
|
||||
|
@ -79,9 +79,9 @@ def send_password_initialization_mail(user):
|
|||
base_url = url_for("account.index", _external=True)
|
||||
reset_url = url_for(
|
||||
"account.reset",
|
||||
user_name=user.user_name[0],
|
||||
identifier=user.identifier,
|
||||
hash=profile_hash(
|
||||
user.user_name[0],
|
||||
user.identifier,
|
||||
user.preferred_email,
|
||||
user.password[0] if user.has_password() else "",
|
||||
),
|
||||
|
|
|
@ -34,6 +34,14 @@ class User:
|
|||
except (IndexError, KeyError):
|
||||
pass
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
"""
|
||||
Returns a unique value that will be used to identify the user.
|
||||
This value will be used in URLs in canaille, so it should be unique and short.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def has_password(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
|
@ -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", user_name=user.user_name, email=user.preferred_email) }}">TXT</a>
|
||||
<a class="ui button primary" href="{{ url_for("admin.invitation_html", user_name=user.user_name, email=user.preferred_email) }}">HTML</a>
|
||||
<a class="ui button primary" href="{{ url_for("admin.invitation_txt", identifier=user.identifier, email=user.preferred_email) }}">TXT</a>
|
||||
<a class="ui button primary" href="{{ url_for("admin.invitation_html", identifier=user.identifier, email=user.preferred_email) }}">HTML</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="middle aligned content">
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
<tr>
|
||||
<td>{{ _("Subject") }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for("account.profile_edition", username=token.subject.user_name[0]) }}">
|
||||
<a href="{{ url_for("account.profile_edition", identifier=token.subject.identifier) }}">
|
||||
{{ token.subject.name }} - {{ token.subject.user_name[0] }}
|
||||
</a>
|
||||
</td>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</h2>
|
||||
<div class="ui message">
|
||||
<p>
|
||||
{{ _("You are currently logged in as %(username)s.", username=user.formatted_name[0]) }}
|
||||
{{ _("You are currently logged in as %(username)s.", username=user.identifier) }}
|
||||
{% if client %}
|
||||
{{ _("The application %(client_name)s wants to disconnect your account.", client_name=client.client_name) }}
|
||||
{% endif %}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<td><a href="{{ url_for('oidc.authorizations.view', authorization_id=authorization.authorization_code_id) }}">{{ authorization.authorization_code_id }}</a></td>
|
||||
<td><a href="{{ url_for('oidc.clients.edit', client_id=authorization.client.client_id) }}">{{ authorization.client.client_id }}</a></td>
|
||||
<td>
|
||||
<a href="{{ url_for("account.profile_edition", username=authorization.subject.user_name[0]) }}">
|
||||
<a href="{{ url_for("account.profile_edition", identifier=authorization.subject.identifier) }}">
|
||||
{{ authorization.subject.user_name[0] }}
|
||||
</a>
|
||||
</td>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ url_for("account.profile_edition", username=token.subject.user_name[0]) }}">
|
||||
<a href="{{ url_for("account.profile_edition", identifier=token.subject.identifier) }}">
|
||||
{{ token.subject.user_name[0] }}
|
||||
</a>
|
||||
</td>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<tr>
|
||||
{% if user.can_read("photo") %}
|
||||
<td>
|
||||
<a href="{{ url_for('account.profile_edition', username=watched_user.user_name[0]) }}">
|
||||
<a href="{{ url_for('account.profile_edition', identifier=watched_user.identifier) }}">
|
||||
{% if user.can_manage_users and watched_user.locked %}
|
||||
<i class="lock circle big black icon" title="{% trans %}This account is locked{% endtrans %}"></i>
|
||||
{% elif watched_user.photo and watched_user.photo[0] %}
|
||||
|
@ -36,7 +36,7 @@
|
|||
</td>
|
||||
{% endif %}
|
||||
{% 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>
|
||||
<td><a href="{{ url_for('account.profile_edition', identifier=watched_user.identifier) }}">{{ watched_user.user_name[0] }}</a></td>
|
||||
{% endif %}
|
||||
{% if user.can_read("family_name") or user.can_read("given_name") %}
|
||||
<td>{{ watched_user.formatted_name[0] }}</td>
|
||||
|
|
|
@ -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.user_name[0]) }}">
|
||||
<a class="active item" href="{{ url_for('account.profile_edition', identifier=edited_user.identifier) }}">
|
||||
<i class="id card icon"></i>
|
||||
{% trans %}Personal information{% endtrans %}
|
||||
</a>
|
||||
<a class="item" href="{{ url_for('account.profile_settings', username=edited_user.user_name[0]) }}">
|
||||
<a class="item" href="{{ url_for('account.profile_settings', identifier=edited_user.identifier) }}">
|
||||
<i class="tools icon"></i>
|
||||
{% trans %}Account information{% endtrans %}
|
||||
</a>
|
||||
|
@ -66,7 +66,7 @@
|
|||
<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", user_name=edited_user.user_name[0], field="photo") }}{% endif %}" alt="User photo">
|
||||
<img src="{% if photo %}{{ url_for("account.photo", identifier=edited_user.identifier, field="photo") }}{% endif %}" alt="User photo">
|
||||
</label>
|
||||
<label
|
||||
class="ui centered photo-placeholder"
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
|
||||
{% block submenu %}
|
||||
<nav class="ui bottom attached two item borderless menu">
|
||||
<a class="item" href="{{ url_for('account.profile_edition', username=edited_user.user_name[0]) }}">
|
||||
<a class="item" href="{{ url_for('account.profile_edition', identifier=edited_user.identifier) }}">
|
||||
<i class="id card icon"></i>
|
||||
{% trans %}Personal information{% endtrans %}
|
||||
</a>
|
||||
<a class="active item" href="{{ url_for('account.profile_settings', username=edited_user.user_name[0]) }}">
|
||||
<a class="active item" href="{{ url_for('account.profile_settings', identifier=edited_user.identifier) }}">
|
||||
<i class="tools icon"></i>
|
||||
{% trans %}Account information{% endtrans %}
|
||||
</a>
|
||||
|
@ -188,8 +188,8 @@
|
|||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% 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" hx-boost="false">
|
||||
{% if user.can_impersonate_users and user.identifier != edited_user.identifier %}
|
||||
<a href="{{ url_for('account.impersonate', identifier=edited_user.identifier) }}" class="ui right floated basic button" name="action" value="impersonate" id="impersonate" hx-boost="false">
|
||||
{{ _("Impersonate") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
</h3>
|
||||
|
||||
<div class="ui attached clearing segment">
|
||||
{{ fui.render_form(form, _("Password reset"), action=url_for("account.reset", user_name=user_name, hash=hash)) }}
|
||||
{{ fui.render_form(form, _("Password reset"), action=url_for("account.reset", identifier=identifier, hash=hash)) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
{% if user.can_edit_self %}
|
||||
<a class="item {% if menuitem == "profile" %}active{% endif %}"
|
||||
href="{{ url_for('account.profile_edition', username=user.user_name[0]) }}">
|
||||
href="{{ url_for('account.profile_edition', identifier=user.identifier) }}">
|
||||
<i class="id card icon"></i>
|
||||
{% trans %}Profile{% endtrans %}
|
||||
</a>
|
||||
|
|
|
@ -77,7 +77,7 @@ USER_BASE = "ou=users,dc=mydomain,dc=tld"
|
|||
# USER_CLASS = "inetOrgPerson"
|
||||
|
||||
# The attribute to identify an object in the User dn.
|
||||
USER_ID_ATTRIBUTE = "uid"
|
||||
# USER_ID_ATTRIBUTE = "uid"
|
||||
|
||||
# Filter to match users on sign in. Supports a variable
|
||||
# {login} that can be used to compare against several fields:
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
<a class="item {% if menuitem == "profile" %}active{% endif %}"
|
||||
href="{{ url_for('account.profile_edition', username=user.user_name[0]) }}">
|
||||
href="{{ url_for('account.profile_edition', identifier=user.identifier) }}">
|
||||
<i class="id card icon"></i>
|
||||
{% trans %}My profile{% endtrans %}
|
||||
</a>
|
||||
|
|
|
@ -34,6 +34,7 @@ def ldap_configuration(configuration, slapd_server):
|
|||
"BIND_DN": slapd_server.root_dn,
|
||||
"BIND_PW": slapd_server.root_pw,
|
||||
"USER_BASE": "ou=users",
|
||||
"USER_ID_ATTRIBUTE": "uid",
|
||||
"USER_FILTER": "(uid={login})",
|
||||
"GROUP_BASE": "ou=groups",
|
||||
"TIMEOUT": 0.1,
|
||||
|
|
|
@ -33,7 +33,7 @@ def test_object_creation(app, backend):
|
|||
|
||||
def test_repr(backend, foo_group, user):
|
||||
assert repr(foo_group) == "<Group display_name=foo>"
|
||||
assert repr(user) == "<User formatted_name=John (johnny) Doe>"
|
||||
assert repr(user) == "<User user_name=user>"
|
||||
|
||||
|
||||
def test_dn_when_leading_space_in_id_attribute(backend):
|
||||
|
@ -47,7 +47,7 @@ def test_dn_when_leading_space_in_id_attribute(backend):
|
|||
|
||||
assert ldap.dn.is_dn(user.dn)
|
||||
assert ldap.dn.dn2str(ldap.dn.str2dn(user.dn)) == user.dn
|
||||
assert user.dn == "cn=Doe,ou=users,dc=mydomain,dc=tld"
|
||||
assert user.dn == "uid=user,ou=users,dc=mydomain,dc=tld"
|
||||
|
||||
user.delete()
|
||||
|
||||
|
@ -56,14 +56,14 @@ def test_dn_when_ldap_special_char_in_id_attribute(backend):
|
|||
user = models.User(
|
||||
formatted_name="#Doe", # special char
|
||||
family_name="Doe",
|
||||
user_name="user",
|
||||
user_name="#user",
|
||||
emails="john@doe.com",
|
||||
)
|
||||
user.save()
|
||||
|
||||
assert ldap.dn.is_dn(user.dn)
|
||||
assert ldap.dn.dn2str(ldap.dn.str2dn(user.dn)) == user.dn
|
||||
assert user.dn == "cn=\\#Doe,ou=users,dc=mydomain,dc=tld"
|
||||
assert user.dn == "uid=\\#user,ou=users,dc=mydomain,dc=tld"
|
||||
|
||||
user.delete()
|
||||
|
||||
|
@ -182,7 +182,7 @@ def test_object_class_update(backend, testclient):
|
|||
testclient.app.config["BACKENDS"]["LDAP"]["USER_CLASS"] = ["inetOrgPerson"]
|
||||
setup_ldap_models(testclient.app.config)
|
||||
|
||||
user1 = models.User(cn="foo1", sn="bar1")
|
||||
user1 = models.User(cn="foo1", sn="bar1", user_name="baz1")
|
||||
user1.save()
|
||||
|
||||
assert user1.objectClass == ["inetOrgPerson"]
|
||||
|
@ -194,7 +194,7 @@ def test_object_class_update(backend, testclient):
|
|||
]
|
||||
setup_ldap_models(testclient.app.config)
|
||||
|
||||
user2 = models.User(cn="foo2", sn="bar2")
|
||||
user2 = models.User(cn="foo2", sn="bar2", user_name="baz2")
|
||||
user2.save()
|
||||
|
||||
assert user2.objectClass == ["inetOrgPerson", "extensibleObject"]
|
||||
|
|
|
@ -180,7 +180,9 @@ def test_model_references_set_unsaved_object(
|
|||
group.save()
|
||||
user.reload() # an LDAP group can be inconsistent by containing members which doesn't exist
|
||||
|
||||
non_existent_user = models.User(formatted_name="foo", family_name="bar")
|
||||
non_existent_user = models.User(
|
||||
formatted_name="foo", family_name="bar", user_name="baz"
|
||||
)
|
||||
group.members = group.members + [non_existent_user]
|
||||
assert group.members == [user, non_existent_user]
|
||||
|
||||
|
|
|
@ -18,6 +18,9 @@ def test_required_methods(testclient):
|
|||
with pytest.raises(NotImplementedError):
|
||||
user.set_password("password")
|
||||
|
||||
with pytest.raises(NotImplementedError):
|
||||
user.identifier
|
||||
|
||||
|
||||
def test_user_get_from_login(testclient, user, backend):
|
||||
assert models.User.get_from_login(login="invalid") is None
|
||||
|
|
|
@ -4,14 +4,14 @@ from canaille.oidc.oauth import get_jwt_config
|
|||
|
||||
|
||||
def test_end_session(testclient, backend, logged_user, client, id_token):
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", 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.user_name[0],
|
||||
"logout_hint": logged_user.identifier,
|
||||
"client_id": client.client_id,
|
||||
"post_logout_redirect_uri": post_logout_redirect_url,
|
||||
"state": "foobar",
|
||||
|
@ -24,18 +24,18 @@ def test_end_session(testclient, backend, logged_user, client, id_token):
|
|||
with testclient.session_transaction() as sess:
|
||||
assert not sess.get("user_id")
|
||||
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=403)
|
||||
|
||||
|
||||
def test_end_session_no_client_id(testclient, backend, logged_user, client, id_token):
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", 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.user_name[0],
|
||||
"logout_hint": logged_user.identifier,
|
||||
"post_logout_redirect_uri": post_logout_redirect_url,
|
||||
"state": "foobar",
|
||||
},
|
||||
|
@ -47,19 +47,19 @@ def test_end_session_no_client_id(testclient, backend, logged_user, client, id_t
|
|||
with testclient.session_transaction() as sess:
|
||||
assert not sess.get("user_id")
|
||||
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=403)
|
||||
|
||||
|
||||
def test_no_redirect_uri_no_redirect(
|
||||
testclient, backend, logged_user, client, id_token
|
||||
):
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=200)
|
||||
|
||||
res = testclient.get(
|
||||
"/oauth/end_session",
|
||||
params={
|
||||
"id_token_hint": id_token,
|
||||
"logout_hint": logged_user.user_name[0],
|
||||
"logout_hint": logged_user.identifier,
|
||||
"client_id": client.client_id,
|
||||
"state": "foobar",
|
||||
},
|
||||
|
@ -71,20 +71,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.user_name[0]}", status=403)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=403)
|
||||
|
||||
|
||||
def test_bad_redirect_uri_no_redirect(
|
||||
testclient, backend, logged_user, client, id_token
|
||||
):
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", 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.user_name[0],
|
||||
"logout_hint": logged_user.identifier,
|
||||
"client_id": client.client_id,
|
||||
"post_logout_redirect_uri": post_logout_redirect_url,
|
||||
"state": "foobar",
|
||||
|
@ -97,17 +97,17 @@ 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.user_name[0]}", status=403)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=403)
|
||||
|
||||
|
||||
def test_no_client_hint_no_redirect(testclient, backend, logged_user, client, id_token):
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=200)
|
||||
|
||||
post_logout_redirect_url = "https://mydomain.tld/disconnected"
|
||||
res = testclient.get(
|
||||
"/oauth/end_session",
|
||||
params={
|
||||
"logout_hint": logged_user.user_name[0],
|
||||
"logout_hint": logged_user.identifier,
|
||||
"post_logout_redirect_uri": post_logout_redirect_url,
|
||||
"state": "foobar",
|
||||
},
|
||||
|
@ -121,17 +121,17 @@ def test_no_client_hint_no_redirect(testclient, backend, logged_user, client, id
|
|||
with testclient.session_transaction() as sess:
|
||||
assert not sess.get("user_id")
|
||||
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=403)
|
||||
|
||||
|
||||
def test_end_session_invalid_client_id(testclient, backend, logged_user, client):
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=200)
|
||||
|
||||
post_logout_redirect_url = "https://mydomain.tld/disconnected"
|
||||
res = testclient.get(
|
||||
"/oauth/end_session",
|
||||
params={
|
||||
"logout_hint": logged_user.user_name[0],
|
||||
"logout_hint": logged_user.identifier,
|
||||
"post_logout_redirect_uri": post_logout_redirect_url,
|
||||
"client_id": "invalid_client_id",
|
||||
"state": "foobar",
|
||||
|
@ -147,7 +147,7 @@ def test_end_session_invalid_client_id(testclient, backend, logged_user, client)
|
|||
with testclient.session_transaction() as sess:
|
||||
assert not sess.get("user_id")
|
||||
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=403)
|
||||
|
||||
|
||||
def test_client_hint_invalid(testclient, backend, logged_user, client):
|
||||
|
@ -158,14 +158,14 @@ def test_client_hint_invalid(testclient, backend, logged_user, client):
|
|||
**get_jwt_config(None),
|
||||
)
|
||||
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", 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.user_name[0],
|
||||
"logout_hint": logged_user.identifier,
|
||||
"post_logout_redirect_uri": post_logout_redirect_url,
|
||||
"state": "foobar",
|
||||
},
|
||||
|
@ -177,17 +177,17 @@ def test_client_hint_invalid(testclient, backend, logged_user, client):
|
|||
with testclient.session_transaction() as sess:
|
||||
assert not sess.get("user_id")
|
||||
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=403)
|
||||
|
||||
|
||||
def test_no_jwt_logout(testclient, backend, logged_user, client):
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=200)
|
||||
|
||||
post_logout_redirect_url = "https://mydomain.tld/disconnected"
|
||||
res = testclient.get(
|
||||
"/oauth/end_session",
|
||||
params={
|
||||
"logout_hint": logged_user.user_name[0],
|
||||
"logout_hint": logged_user.identifier,
|
||||
"client_id": client.client_id,
|
||||
"post_logout_redirect_uri": post_logout_redirect_url,
|
||||
"state": "foobar",
|
||||
|
@ -203,17 +203,17 @@ def test_no_jwt_logout(testclient, backend, logged_user, client):
|
|||
|
||||
assert res.location == f"{post_logout_redirect_url}?state=foobar"
|
||||
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=403)
|
||||
|
||||
|
||||
def test_no_jwt_no_logout(testclient, backend, logged_user, client):
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=200)
|
||||
|
||||
post_logout_redirect_url = "https://mydomain.tld/disconnected"
|
||||
res = testclient.get(
|
||||
"/oauth/end_session",
|
||||
params={
|
||||
"logout_hint": logged_user.user_name[0],
|
||||
"logout_hint": logged_user.identifier,
|
||||
"client_id": client.client_id,
|
||||
"post_logout_redirect_uri": post_logout_redirect_url,
|
||||
"state": "foobar",
|
||||
|
@ -228,20 +228,20 @@ def test_no_jwt_no_logout(testclient, backend, logged_user, client):
|
|||
|
||||
assert res.location == "/"
|
||||
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=200)
|
||||
|
||||
|
||||
def test_jwt_not_issued_here(testclient, backend, logged_user, client, id_token):
|
||||
testclient.app.config["OIDC"]["JWT"]["ISS"] = "https://foo.bar"
|
||||
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", 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.user_name[0],
|
||||
"logout_hint": logged_user.identifier,
|
||||
"client_id": client.client_id,
|
||||
"post_logout_redirect_uri": post_logout_redirect_url,
|
||||
"state": "foobar",
|
||||
|
@ -263,14 +263,14 @@ def test_client_hint_mismatch(testclient, backend, logged_user, client):
|
|||
**get_jwt_config(None),
|
||||
)
|
||||
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", 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.user_name[0],
|
||||
"logout_hint": logged_user.identifier,
|
||||
"client_id": client.client_id,
|
||||
"post_logout_redirect_uri": post_logout_redirect_url,
|
||||
"state": "foobar",
|
||||
|
@ -285,7 +285,7 @@ def test_client_hint_mismatch(testclient, backend, logged_user, client):
|
|||
|
||||
|
||||
def test_bad_user_id_token_mismatch(testclient, backend, logged_user, client, admin):
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=200)
|
||||
|
||||
id_token = generate_id_token(
|
||||
{},
|
||||
|
@ -299,7 +299,7 @@ def test_bad_user_id_token_mismatch(testclient, backend, logged_user, client, ad
|
|||
"/oauth/end_session",
|
||||
params={
|
||||
"id_token_hint": id_token,
|
||||
"logout_hint": logged_user.user_name[0],
|
||||
"logout_hint": logged_user.identifier,
|
||||
"client_id": client.client_id,
|
||||
"post_logout_redirect_uri": post_logout_redirect_url,
|
||||
"state": "foobar",
|
||||
|
@ -315,18 +315,18 @@ def test_bad_user_id_token_mismatch(testclient, backend, logged_user, client, ad
|
|||
|
||||
assert res.location == f"{post_logout_redirect_url}?state=foobar"
|
||||
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=403)
|
||||
|
||||
|
||||
def test_bad_user_hint(testclient, backend, logged_user, client, id_token, admin):
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", 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.user_name[0],
|
||||
"logout_hint": admin.identifier,
|
||||
"client_id": client.client_id,
|
||||
"post_logout_redirect_uri": post_logout_redirect_url,
|
||||
"state": "foobar",
|
||||
|
@ -342,17 +342,17 @@ def test_bad_user_hint(testclient, backend, logged_user, client, id_token, admin
|
|||
|
||||
assert res.location == f"{post_logout_redirect_url}?state=foobar"
|
||||
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=403)
|
||||
|
||||
|
||||
def test_no_jwt_bad_csrf(testclient, backend, logged_user, client):
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=200)
|
||||
|
||||
post_logout_redirect_url = "https://mydomain.tld/disconnected"
|
||||
res = testclient.get(
|
||||
"/oauth/end_session",
|
||||
params={
|
||||
"logout_hint": logged_user.user_name[0],
|
||||
"logout_hint": logged_user.identifier,
|
||||
"client_id": client.client_id,
|
||||
"post_logout_redirect_uri": post_logout_redirect_url,
|
||||
"state": "foobar",
|
||||
|
@ -371,7 +371,7 @@ def test_end_session_already_disconnected(testclient, backend, user, client, id_
|
|||
"/oauth/end_session",
|
||||
params={
|
||||
"id_token_hint": id_token,
|
||||
"logout_hint": user.user_name[0],
|
||||
"logout_hint": user.identifier,
|
||||
"client_id": client.client_id,
|
||||
"post_logout_redirect_uri": post_logout_redirect_url,
|
||||
"state": "foobar",
|
||||
|
@ -383,14 +383,14 @@ def test_end_session_already_disconnected(testclient, backend, user, client, id_
|
|||
|
||||
|
||||
def test_end_session_no_state(testclient, backend, logged_user, client, id_token):
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=200)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", 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.user_name[0],
|
||||
"logout_hint": logged_user.identifier,
|
||||
"client_id": client.client_id,
|
||||
"post_logout_redirect_uri": post_logout_redirect_url,
|
||||
},
|
||||
|
@ -402,4 +402,4 @@ def test_end_session_no_state(testclient, backend, logged_user, client, id_token
|
|||
with testclient.session_transaction() as sess:
|
||||
assert not sess.get("user_id")
|
||||
|
||||
testclient.get(f"/profile/{logged_user.user_name[0]}", status=403)
|
||||
testclient.get(f"/profile/{logged_user.identifier}", status=403)
|
||||
|
|
Loading…
Reference in a new issue