diff --git a/canaille/conf/config.sample.toml b/canaille/conf/config.sample.toml index ca6bc949..d12aa3bc 100644 --- a/canaille/conf/config.sample.toml +++ b/canaille/conf/config.sample.toml @@ -82,7 +82,7 @@ GROUP_USER_FILTER = "member={user.dn}" # You can define access controls that define what users can do on canaille # An access control consists in a FILTER to match users, a list of PERMISSIONS # matched users will be able to perform, and fields users will be able -# to READ and WRITE. +# to READ and WRITE. Users matching several filters will cumulate permissions. # # A 'FILTER' parameter that is a LDAP filter used to determine if a user # belongs to an access control. If absent, all the users will match this @@ -94,17 +94,19 @@ GROUP_USER_FILTER = "member={user.dn}" # # The 'PERMISSIONS' parameter that is an list of items the users in the access # control will be able to manage. 'PERMISSIONS' is optionnal. Values can be: +# - "use_oidc" to allow OpenID Connect authentication +# - "manage_oidc" to allow OpenID Connect client managements # - "manage_users" to allow other users management # - "manage_groups" to allow group edition and creation -# - "manage_oidc" to allow OpenID Connect client managements # - "delete_account" allows a user to delete his own account. If used with -# manage_users, the user can delete any account +# manage_users, the user can delete any account # - "impersonate_users" to allow a user to take the identity of another user # # The 'READ' and 'WRITE' attributes are the LDAP attributes of the user # object that users will be able to read and/or write. [ACL.DEFAULT] READ = ["uid", "groups"] +PERMISSIONS = ["use_oidc"] WRITE = ["givenName", "sn", "userPassword", "telephoneNumber"] [ACL.ADMIN] @@ -116,8 +118,7 @@ PERMISSIONS = [ "delete_account", "impersonate_users", ] -READ = ["uid"] -WRITE = ["groups", "givenName", "sn", "userPassword"] +WRITE = ["groups"] # The jwt configuration. You can generate a RSA keypair with: # openssl genrsa -out private.pem 4096 diff --git a/canaille/models.py b/canaille/models.py index a53661af..19422e84 100644 --- a/canaille/models.py +++ b/canaille/models.py @@ -150,6 +150,10 @@ class User(LDAPObject): self.read |= set(details.get("READ", [])) self.write |= set(details.get("WRITE", [])) + @property + def can_use_oidc(self): + return "use_oidc" in self.permissions + @property def can_manage_users(self): return "manage_users" in self.permissions diff --git a/canaille/oauth.py b/canaille/oauth.py index fa2417ee..28c63399 100644 --- a/canaille/oauth.py +++ b/canaille/oauth.py @@ -76,6 +76,9 @@ def authorize(): return redirect(request.url) + if not user.can_use_oidc: + abort(400) + # CONSENT consents = Consent.filter( diff --git a/canaille/themes/default/base.html b/canaille/themes/default/base.html index 86a897ca..9bff6afa 100644 --- a/canaille/themes/default/base.html +++ b/canaille/themes/default/base.html @@ -38,11 +38,13 @@ {% trans %}My profile{% endtrans %} + {% if user.can_use_oidc %} {% trans %}My consents{% endtrans %} + {% endif %} {% if user.can_manage_users %} diff --git a/demo/conf/canaille.toml b/demo/conf/canaille.toml index 75de7d8c..2c2b6fee 100644 --- a/demo/conf/canaille.toml +++ b/demo/conf/canaille.toml @@ -84,7 +84,7 @@ GROUP_USER_FILTER = "member={user.dn}" # You can define access controls that define what users can do on canaille # An access control consists in a FILTER to match users, a list of PERMISSIONS # matched users will be able to perform, and fields users will be able -# to READ and WRITE. +# to READ and WRITE. Users matching several filters will cumulate permissions. # # A 'FILTER' parameter that is a LDAP filter used to determine if a user # belongs to an access control. If absent, all the users will match this @@ -96,17 +96,19 @@ GROUP_USER_FILTER = "member={user.dn}" # # The 'PERMISSIONS' parameter that is an list of items the users in the access # control will be able to manage. 'PERMISSIONS' is optionnal. Values can be: +# - "use_oidc" to allow OpenID Connect authentication +# - "manage_oidc" to allow OpenID Connect client managements # - "manage_users" to allow other users management # - "manage_groups" to allow group edition and creation -# - "manage_oidc" to allow OpenID Connect client managements # - "delete_account" allows a user to delete his own account. If used with -# manage_users, the user can delete any account +# manage_users, the user can delete any account # - "impersonate_users" to allow a user to take the identity of another user # # The 'READ' and 'WRITE' attributes are the LDAP attributes of the user # object that users will be able to read and/or write. [ACL.DEFAULT] READ = ["uid", "groups"] +PERMISSIONS = ["use_oidc"] WRITE = ["givenName", "sn", "userPassword", "telephoneNumber"] [ACL.ADMIN] @@ -118,14 +120,12 @@ PERMISSIONS = [ "delete_account", "impersonate_users", ] -READ = ["uid"] -WRITE = ["groups", "givenName", "sn", "userPassword"] +WRITE = ["groups"] [ACL.HALF_ADMIN] FILTER = "memberof=cn=moderators,ou=groups,dc=mydomain,dc=tld" PERMISSIONS = ["manage_users", "manage_groups", "delete_account"] -READ = ["uid"] -WRITE = ["groups", "givenName", "sn", "userPassword"] +WRITE = ["groups"] # The jwt configuration. You can generate a RSA keypair with: # openssl genrsa -out private.pem 4096 diff --git a/tests/conftest.py b/tests/conftest.py index 73551b36..ca01d6de 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -147,7 +147,7 @@ def configuration(slapd_server, smtpd, keypair_path): "ACL": { "DEFAULT": { "READ": ["uid", "groups"], - "PERMISSIONS": [], + "PERMISSIONS": ["use_oidc"], "WRITE": [ "mail", "givenName", @@ -166,28 +166,14 @@ def configuration(slapd_server, smtpd, keypair_path): "impersonate_users", "manage_groups", ], - "READ": ["uid"], "WRITE": [ - "mail", - "givenName", - "sn", - "userPassword", - "telephoneNumber", - "employeeNumber", "groups", ], }, "MODERATOR": { "FILTER": "(|(uid=moderator)(sn=moderator))", "PERMISSIONS": ["manage_users", "manage_groups", "delete_account"], - "READ": ["uid"], "WRITE": [ - "mail", - "givenName", - "sn", - "userPassword", - "telephoneNumber", - "employeeNumber", "groups", ], }, diff --git a/tests/test_authorization_code_flow.py b/tests/test_authorization_code_flow.py index 1c4a24bd..3c2b59c3 100644 --- a/tests/test_authorization_code_flow.py +++ b/tests/test_authorization_code_flow.py @@ -438,6 +438,30 @@ def test_authorization_code_flow_when_consent_already_given_but_for_a_smaller_sc assert "groups" in consents[0].oauthScope +def test_authorization_code_flow_but_user_cannot_use_oidc( + testclient, slapd_connection, user, client, keypair, other_client +): + testclient.app.config["ACL"]["DEFAULT"]["PERMISSIONS"] = [] + + res = testclient.get( + "/oauth/authorize", + params=dict( + response_type="code", + client_id=client.oauthClientID, + scope="profile", + nonce="somenonce", + ), + status=200, + ) + + res.form["login"] = "John (johnny) Doe" + res = res.form.submit(status=200) + + res.form["password"] = "correct horse battery staple" + res = res.form.submit(status=302) + res = res.follow(status=400) + + def test_prompt_none(testclient, slapd_connection, logged_user, client): Consent( oauthClient=client.dn,