diff --git a/CHANGES.rst b/CHANGES.rst index 77cb187f..8604a2ff 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,11 @@ All notable changes to this project will be documented in this file. The format is based on `Keep a Changelog `_, and this project adheres to `Semantic Versioning `_. +Added +***** + +- User can chose their favourite display name. :pr:`77` + [0.0.14] - 2022-11-29 ===================== diff --git a/canaille/conf/config.sample.toml b/canaille/conf/config.sample.toml index 4146fedd..c97c5e23 100644 --- a/canaille/conf/config.sample.toml +++ b/canaille/conf/config.sample.toml @@ -131,7 +131,17 @@ GROUP_BASE = "ou=groups,dc=mydomain,dc=tld" [ACL.DEFAULT] PERMISSIONS = ["edit_self", "use_oidc"] READ = ["uid", "groups"] -WRITE = ["givenName", "sn", "userPassword", "telephoneNumber", "jpegPhoto", "mail", "labeledURI", "preferredLanguage"] +WRITE = [ + "givenName", + "sn", + "displayName", + "userPassword", + "telephoneNumber", + "jpegPhoto", + "mail", + "labeledURI", + "preferredLanguage", +] [ACL.ADMIN] FILTER = "memberof=cn=moderators,ou=groups,dc=mydomain,dc=tld" @@ -172,7 +182,7 @@ PHONE_NUMBER = "{{ user.telephoneNumber[0] }}" EMAIL = "{{ user.mail[0] }}" GIVEN_NAME = "{{ user.givenName[0] }}" FAMILY_NAME = "{{ user.sn[0] }}" -PREFERRED_USERNAME = "{{ user.displayName[0] }}" +PREFERRED_USERNAME = "{{ user.displayName }}" LOCALE = "{{ user.preferredLanguage[0] }}" ADDRESS = "{{ user.postalAddress[0] }}" PICTURE = "{% if user.jpegPhoto %}{{ url_for('account.photo', uid=user.uid[0], field='jpegPhoto', _external=True) }}{% endif %}" diff --git a/canaille/forms.py b/canaille/forms.py index 2f692e04..6d639e03 100644 --- a/canaille/forms.py +++ b/canaille/forms.py @@ -123,6 +123,15 @@ PROFILE_FORM_FIELDS = dict( "autocorrect": "off", }, ), + displayName=wtforms.StringField( + _("Display Name"), + validators=[wtforms.validators.Optional()], + render_kw={ + "placeholder": _("Johnny"), + "spellcheck": "false", + "autocorrect": "off", + }, + ), mail=wtforms.EmailField( _("Email address"), validators=[wtforms.validators.DataRequired(), wtforms.validators.Email()], diff --git a/canaille/templates/profile.html b/canaille/templates/profile.html index 108e5437..1fc33e72 100644 --- a/canaille/templates/profile.html +++ b/canaille/templates/profile.html @@ -118,9 +118,12 @@ {% if "sn" in form %} {% block sn_field scoped %}{{ render_field(form.sn) }}{% endblock %} {% endif %} - + {% if "displayName" in form %} + {% block display_name_field scoped %}{{ render_field(form.displayName) }}{% endblock %} + {% endif %} + {% if "jpegPhoto" in form %}{% endif %} {% if "mail" in form %} diff --git a/demo/conf-docker/canaille.toml b/demo/conf-docker/canaille.toml index 2f6f7074..a359c62f 100644 --- a/demo/conf-docker/canaille.toml +++ b/demo/conf-docker/canaille.toml @@ -132,7 +132,17 @@ GROUP_BASE = "ou=groups,dc=mydomain,dc=tld" [ACL.DEFAULT] PERMISSIONS = ["use_oidc"] READ = ["uid", "groups"] -WRITE = ["jpegPhoto", "givenName", "sn", "userPassword", "telephoneNumber", "mail", "labeledURI", "preferredLanguage"] +WRITE = [ + "jpegPhoto", + "givenName", + "sn", + "displayName", + "userPassword", + "telephoneNumber", + "mail", + "labeledURI", + "preferredLanguage", +] [ACL.ADMIN] FILTER = "memberof=cn=admins,ou=groups,dc=mydomain,dc=tld" @@ -178,7 +188,7 @@ PHONE_NUMBER = "{{ user.telephoneNumber[0] }}" EMAIL = "{{ user.mail[0] }}" GIVEN_NAME = "{{ user.givenName[0] }}" FAMILY_NAME = "{{ user.sn[0] }}" -PREFERRED_USERNAME = "{{ user.displayName[0] }}" +PREFERRED_USERNAME = "{{ user.displayName }}" LOCALE = "{{ user.preferredLanguage[0] }}" ADDRESS = "{{ user.postalAddress[0] }}" PICTURE = "{% if user.jpegPhoto %}{{ url_for('account.photo', uid=user.uid[0], field='jpegPhoto', _external=True) }}{% endif %}" diff --git a/demo/conf/canaille.toml b/demo/conf/canaille.toml index 2c6f420e..0d18f12a 100644 --- a/demo/conf/canaille.toml +++ b/demo/conf/canaille.toml @@ -132,7 +132,18 @@ GROUP_BASE = "ou=groups,dc=mydomain,dc=tld" [ACL.DEFAULT] PERMISSIONS = ["edit_self", "use_oidc"] READ = ["uid", "groups"] -WRITE = ["jpegPhoto", "givenName", "sn", "userPassword", "telephoneNumber", "mail", "labeledURI", "postalAddress", "preferredLanguage"] +WRITE = [ + "jpegPhoto", + "givenName", + "sn", + "displayName", + "userPassword", + "telephoneNumber", + "mail", + "labeledURI", + "postalAddress", + "preferredLanguage", +] [ACL.ADMIN] FILTER = "memberof=cn=admins,ou=groups,dc=mydomain,dc=tld" @@ -178,7 +189,7 @@ PHONE_NUMBER = "{{ user.telephoneNumber[0] }}" EMAIL = "{{ user.mail[0] }}" GIVEN_NAME = "{{ user.givenName[0] }}" FAMILY_NAME = "{{ user.sn[0] }}" -PREFERRED_USERNAME = "{{ user.displayName[0] }}" +PREFERRED_USERNAME = "{{ user.displayName }}" LOCALE = "{{ user.preferredLanguage[0] }}" ADDRESS = "{{ user.postalAddress[0] }}" PICTURE = "{% if user.jpegPhoto %}{{ url_for('account.photo', uid=user.uid[0], field='jpegPhoto', _external=True) }}{% endif %}" diff --git a/tests/conftest.py b/tests/conftest.py index 5aa115d7..1b101802 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -167,6 +167,7 @@ def configuration(slapd_server, smtpd, keypair_path): "givenName", "jpegPhoto", "sn", + "displayName", "userPassword", "telephoneNumber", "postalAddress", @@ -206,7 +207,7 @@ def configuration(slapd_server, smtpd, keypair_path): "EMAIL": "{{ user.mail[0] }}", "GIVEN_NAME": "{{ user.givenName[0] }}", "FAMILY_NAME": "{{ user.sn[0] }}", - "PREFERRED_USERNAME": "{{ user.displayName[0] }}", + "PREFERRED_USERNAME": "{{ user.displayName }}", "LOCALE": "{{ user.preferredLanguage[0] }}", "PICTURE": "{% if user.jpegPhoto %}{{ url_for('account.photo', uid=user.uid[0], field='jpegPhoto', _external=True) }}{% endif %}", }, @@ -250,6 +251,7 @@ def user(app, slapd_connection): uid="user", mail="john@doe.com", userPassword="{SSHA}fw9DYeF/gHTHuVMepsQzVYAkffGcU8Fz", + displayName="Johnny", ) u.save() yield u diff --git a/tests/oidc/test_authorization_code_flow.py b/tests/oidc/test_authorization_code_flow.py index b29492c3..489cc6a6 100644 --- a/tests/oidc/test_authorization_code_flow.py +++ b/tests/oidc/test_authorization_code_flow.py @@ -93,6 +93,7 @@ def test_authorization_code_flow( assert { "name": "John (johnny) Doe", "family_name": "Doe", + "preferred_username": "Johnny", "email": "john@doe.com", "sub": "user", "groups": [], @@ -161,6 +162,7 @@ def test_authorization_code_flow_preconsented( assert { "name": "John (johnny) Doe", "family_name": "Doe", + "preferred_username": "Johnny", "sub": "user", } == res.json @@ -231,6 +233,7 @@ def test_logout_login(testclient, logged_user, client): assert { "name": "John (johnny) Doe", "family_name": "Doe", + "preferred_username": "Johnny", "sub": "user", } == res.json @@ -312,6 +315,7 @@ def test_refresh_token(testclient, user, client): assert { "name": "John (johnny) Doe", "family_name": "Doe", + "preferred_username": "Johnny", "sub": "user", } == res.json @@ -378,6 +382,7 @@ def test_code_challenge(testclient, logged_user, client): assert { "name": "John (johnny) Doe", "family_name": "Doe", + "preferred_username": "Johnny", "sub": "user", } == res.json @@ -705,6 +710,7 @@ def test_authorization_code_request_scope_too_large( assert { "name": "John (johnny) Doe", "family_name": "Doe", + "preferred_username": "Johnny", "sub": "user", } == res.json diff --git a/tests/oidc/test_hybrid_flow.py b/tests/oidc/test_hybrid_flow.py index d73b10f5..aed9d834 100644 --- a/tests/oidc/test_hybrid_flow.py +++ b/tests/oidc/test_hybrid_flow.py @@ -49,6 +49,7 @@ def test_oauth_hybrid(testclient, slapd_connection, user, client): assert { "name": "John (johnny) Doe", "family_name": "Doe", + "preferred_username": "Johnny", "sub": "user", } == res.json @@ -94,5 +95,6 @@ def test_oidc_hybrid( assert { "name": "John (johnny) Doe", "family_name": "Doe", + "preferred_username": "Johnny", "sub": "user", } == res.json diff --git a/tests/oidc/test_implicit_flow.py b/tests/oidc/test_implicit_flow.py index 0f747e22..3cb4099e 100644 --- a/tests/oidc/test_implicit_flow.py +++ b/tests/oidc/test_implicit_flow.py @@ -46,6 +46,7 @@ def test_oauth_implicit(testclient, user, client): "name": "John (johnny) Doe", "sub": "user", "family_name": "Doe", + "preferred_username": "Johnny", } == res.json client.grant_types = ["code"] @@ -102,6 +103,7 @@ def test_oidc_implicit(testclient, keypair, user, client, other_client): "name": "John (johnny) Doe", "sub": "user", "family_name": "Doe", + "preferred_username": "Johnny", } == res.json client.grant_types = ["code"] @@ -161,6 +163,7 @@ def test_oidc_implicit_with_group( "name": "John (johnny) Doe", "sub": "user", "family_name": "Doe", + "preferred_username": "Johnny", "groups": ["foo"], } == res.json diff --git a/tests/oidc/test_oauth2utils.py b/tests/oidc/test_oauth2utils.py index 4bfb4ce7..3c84e475 100644 --- a/tests/oidc/test_oauth2utils.py +++ b/tests/oidc/test_oauth2utils.py @@ -30,7 +30,7 @@ DEFAULT_JWT_MAPPING_CONFIG = { "EMAIL": "{{ user.mail[0] }}", "GIVEN_NAME": "{{ user.givenName[0] }}", "FAMILY_NAME": "{{ user.sn[0] }}", - "PREFERRED_USERNAME": "{{ user.displayName[0] }}", + "PREFERRED_USERNAME": "{{ user.displayName }}", "LOCALE": "{{ user.preferredLanguage[0] }}", } @@ -49,6 +49,7 @@ def test_generate_user_standard_claims_with_default_config( "email": "john@doe.com", "sub": "user", "locale": "fr", + "preferred_username": "Johnny", } diff --git a/tests/oidc/test_password_flow.py b/tests/oidc/test_password_flow.py index 8a79330d..e62b943a 100644 --- a/tests/oidc/test_password_flow.py +++ b/tests/oidc/test_password_flow.py @@ -32,6 +32,7 @@ def test_password_flow_basic(testclient, user, client): "name": "John (johnny) Doe", "sub": "user", "family_name": "Doe", + "preferred_username": "Johnny", "groups": [], } == res.json @@ -69,5 +70,6 @@ def test_password_flow_post(testclient, user, client): "name": "John (johnny) Doe", "sub": "user", "family_name": "Doe", + "preferred_username": "Johnny", "groups": [], } == res.json diff --git a/tests/test_profile.py b/tests/test_profile.py index 47466887..27bd8557 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -42,6 +42,7 @@ def test_edition( res.form["uid"] = "toto" 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" @@ -61,6 +62,7 @@ def test_edition( assert ["user"] == logged_user.uid assert ["given_name"] == logged_user.givenName assert ["family_name"] == logged_user.sn + assert "display_name" == logged_user.displayName assert ["email@mydomain.tld"] == logged_user.mail assert ["555-666-777"] == logged_user.telephoneNumber assert ["postal_address"] == logged_user.postalAddress