diff --git a/auth_keycloak/README.rst b/auth_keycloak/README.rst new file mode 100644 index 0000000000..0a19fb162a --- /dev/null +++ b/auth_keycloak/README.rst @@ -0,0 +1,146 @@ +========================= +Keycloak auth integration +========================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:9cdba0b2846e4d81efc52ed2c8dd4ce399444d8eaf4bb9ced1828fdc3c546a21 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github + :target: https://github.com/OCA/server-auth/tree/16.0/auth_keycloak + :alt: OCA/server-auth +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-auth-16-0/server-auth-16-0-auth_keycloak + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/server-auth&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds support for user creation when using `Keycloak `_ + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +Settings -> Users & Companies -> OAuth Providers + +In the `Users management (Keycloak)` section, fill in your user admin endpoint, this will be in +the form $keycloak_url/admin/realms/$realm/users. Also fill in the name of a keycloak user that +is allowed to create users, and its password. + +This user must be allowed to query and manage users. + +Usage +===== + +**Link existing users from Keycloak** + +If you have existing users in Odoo and they are not linked to Keycloak yet +you can: + +1. get back to Settings -> Users -> OAuth Providers -> Keycloak +2. configure "Users management" box +3. click on "Sync users" button +4. select the matching key +5. submit + +Once it's done all matching and updated users will be listed in a list view. +Now your users will be able to log in on Keycloak + + +**Push new users to Keycloak** + +Usually Keycloak is already populated w/ your users base. +Many times this will come via LDAP, AD, pick yours. + +Still, you might need to push some users to Keycloak on demand, +maybe just for testing. + +If you need this, either you + +1. go to a single user form +2. hit the button "Push to Keycloak" (in the header) +3. use the wizard to push it + +or + +1. go to the users list view +2. select some users +3. click on Actions -> Push to Keycloak +4. select "Keycloak" provider +5. push them all + +Changelog +========= + +16.0.1.0.0 2026-02-20 +~~~~~~~~~~~~~~~~~~~~~ + +* v16 migration only implementing user creation, everything else is done by auth_oidc + +10.0.1.0.0 2018-10-17 +~~~~~~~~~~~~~~~~~~~~~ + +* Initial implementation + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Camptocamp + +Contributors +~~~~~~~~~~~~ + +* Simone Orsi +* Dennis Sluijk + +Other credits +~~~~~~~~~~~~~ + +Development sponsored by `Sensefly `_ and `UTB `_. + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/server-auth `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/auth_keycloak/__init__.py b/auth_keycloak/__init__.py new file mode 100644 index 0000000000..9b4296142f --- /dev/null +++ b/auth_keycloak/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/auth_keycloak/__manifest__.py b/auth_keycloak/__manifest__.py new file mode 100644 index 0000000000..79a5308881 --- /dev/null +++ b/auth_keycloak/__manifest__.py @@ -0,0 +1,22 @@ +# Copyright 2018 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +{ + "name": "Keycloak auth integration", + "summary": "Integrate Keycloak into your SSO", + "version": "16.0.1.0.0", + "category": "Tools", + "website": "https://github.com/OCA/server-auth", + "author": "Camptocamp, Odoo Community Association (OCA)", + "license": "AGPL-3", + "depends": [ + "auth_oidc", + ], + "data": [ + "security/ir.model.access.csv", + "wizard/auth_keycloak_sync_wiz.xml", + "wizard/auth_keycloak_create_wiz.xml", + "views/auth_oauth_views.xml", + "views/res_users_views.xml", + ], +} diff --git a/auth_keycloak/i18n/auth_keycloak.pot b/auth_keycloak/i18n/auth_keycloak.pot new file mode 100644 index 0000000000..ead7b56743 --- /dev/null +++ b/auth_keycloak/i18n/auth_keycloak.pot @@ -0,0 +1,289 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * auth_keycloak +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: auth_keycloak +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_create_wiz_pwd +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_sync_mixin_pwd +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_sync_wiz_pwd +#: model:ir.model.fields,help:auth_keycloak.field_auth_oauth_provider_superuser_pwd +msgid "\"Superuser\" user password" +msgstr "" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_create_wiz +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_sync_wiz +msgid "Users managerment not enabled!\n" +" You must configure \"Users management\" parameters on selected provider." +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_create_wiz_user +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_sync_mixin_user +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_sync_wiz_user +#: model:ir.model.fields,help:auth_keycloak.field_auth_oauth_provider_superuser +msgid "A super power user that is able to CRUD users on KC." +msgstr "" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_create_wiz +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_sync_wiz +msgid "Cancel" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_oauth_provider_client_secret +msgid "Client Secret" +msgstr "" + +#. module: auth_keycloak +#: code:addons/auth_keycloak/wizard/keycloak_sync_wiz.py:208 +#, python-format +msgid "Conflict on user values. Please verify that all values supposed to be unique are really unique. %(detail)s" +msgstr "" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_create_wiz +msgid "Create" +msgstr "" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.view_users_form +msgid "Create this user on Keycloak too." +msgstr "" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_create_wiz +msgid "Create user on Keycloak" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_create_uid +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_create_uid +msgid "Created by" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_create_date +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_create_date +msgid "Created on" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_display_name +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin_display_name +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_display_name +msgid "Display Name" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_id +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin_id +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_id +msgid "ID" +msgstr "" + +#. module: auth_keycloak +#: code:addons/auth_keycloak/models/res_users.py:59 +#, python-format +msgid "Keycloak provider not found or not configured properly." +msgstr "" + +#. module: auth_keycloak +#: model:ir.actions.act_window,name:auth_keycloak.keycloak_sync_users +msgid "Keycloak sync users" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_create_wiz_login_match_key +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_sync_mixin_login_match_key +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_sync_wiz_login_match_key +msgid "Keycloak user field to match users' login." +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz___last_update +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin___last_update +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz___last_update +msgid "Last Modified on" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_write_uid +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_write_uid +msgid "Last Updated by" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_write_date +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_write_date +msgid "Last Updated on" +msgstr "" + +#. module: auth_keycloak +#: model:auth.oauth.provider,body:auth_keycloak.default_keycloak_provider +msgid "Log in with Keycloak" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_login_match_key +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin_login_match_key +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_login_match_key +msgid "Login Match Key" +msgstr "" + +#. module: auth_keycloak +#: code:addons/auth_keycloak/wizard/keycloak_sync_wiz.py:195 +#, python-format +msgid "No user selected" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model,name:auth_keycloak.model_auth_oauth_provider +msgid "OAuth2 provider" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_provider_id +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin_provider_id +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_provider_id +msgid "Provider" +msgstr "" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.view_users_form +msgid "Push to Keycloak" +msgstr "" + +#. module: auth_keycloak +#: model:ir.actions.act_window,name:auth_keycloak.keycloak_create_users +msgid "Push users to Keycloak" +msgstr "" + +#. module: auth_keycloak +#: code:addons/auth_keycloak/wizard/keycloak_sync_wiz.py:71 +#, python-format +msgid "Something went wrong. Please check logs." +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_user +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin_user +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_user +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_oauth_provider_superuser +msgid "Superuser" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_pwd +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin_pwd +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_pwd +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_oauth_provider_superuser_pwd +msgid "Superuser Pwd" +msgstr "" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_sync_wiz +msgid "Sychronize existing users" +msgstr "" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_sync_wiz +msgid "Sync" +msgstr "" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.view_oauth_provider_form +msgid "Sync users" +msgstr "" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_sync_wiz +msgid "Synchronize users" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_user_ids +msgid "User" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_create_wiz_endpoint +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_sync_mixin_endpoint +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_sync_wiz_endpoint +#: model:ir.model.fields,help:auth_keycloak.field_auth_oauth_provider_users_endpoint +msgid "User endpoint" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model,name:auth_keycloak.model_res_users +msgid "Users" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_endpoint +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin_endpoint +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_endpoint +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_oauth_provider_users_endpoint +msgid "Users Endpoint" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_management_enabled +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin_management_enabled +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_management_enabled +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_oauth_provider_users_management_enabled +msgid "Users Management Enabled" +msgstr "" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.view_oauth_provider_form +msgid "Users management (Keycloak)" +msgstr "" + +#. module: auth_keycloak +#: code:addons/auth_keycloak/wizard/keycloak_sync_wiz.py:57 +#, python-format +msgid "Users management must be enabled on selected provider" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model,name:auth_keycloak.model_auth_keycloak_create_wiz +msgid "auth.keycloak.create.wiz" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model,name:auth_keycloak.model_auth_keycloak_sync_mixin +msgid "auth.keycloak.sync.mixin" +msgstr "" + +#. module: auth_keycloak +#: model:ir.model,name:auth_keycloak.model_auth_keycloak_sync_wiz +msgid "auth.keycloak.sync.wiz" +msgstr "" + +#. module: auth_keycloak +#: selection:auth.keycloak.create.wiz,login_match_key:0 +#: selection:auth.keycloak.sync.mixin,login_match_key:0 +#: selection:auth.keycloak.sync.wiz,login_match_key:0 +msgid "email" +msgstr "" + +#. module: auth_keycloak +#: selection:auth.keycloak.create.wiz,login_match_key:0 +#: selection:auth.keycloak.sync.mixin,login_match_key:0 +#: selection:auth.keycloak.sync.wiz,login_match_key:0 +msgid "username" +msgstr "" + diff --git a/auth_keycloak/i18n/fr.po b/auth_keycloak/i18n/fr.po new file mode 100644 index 0000000000..9564ac115f --- /dev/null +++ b/auth_keycloak/i18n/fr.po @@ -0,0 +1,318 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * auth_keycloak +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2018-12-12 19:58+0000\n" +"Last-Translator: Alexandre Fayolle \n" +"Language-Team: none\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 3.3\n" + +#. module: auth_keycloak +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_create_wiz_pwd +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_sync_mixin_pwd +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_sync_wiz_pwd +#: model:ir.model.fields,help:auth_keycloak.field_auth_oauth_provider_superuser_pwd +msgid "\"Superuser\" user password" +msgstr "Mot de passe du \"super utilisateur\"" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_create_wiz +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_sync_wiz +msgid "" +"Users managerment not enabled!\n" +" You must configure \"Users management\" parameters on " +"selected provider." +msgstr "" +"La gestion des utilisateurs n'est pas activée !\n" +" Vous devez paramétrer la « gestion des utilisateurs » sur le " +"fournisseur sélectionné." + +#. module: auth_keycloak +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_create_wiz_user +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_sync_mixin_user +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_sync_wiz_user +#: model:ir.model.fields,help:auth_keycloak.field_auth_oauth_provider_superuser +msgid "A super power user that is able to CRUD users on KC." +msgstr "" +"Un utilisateur avec des droits avancés qui a la possibilité de Créer / " +"Modifier / Supprimer des utilisateurs sur Keycloak." + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_create_wiz +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_sync_wiz +msgid "Cancel" +msgstr "Annuler" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_oauth_provider_client_secret +#, fuzzy +msgid "Client Secret" +msgstr "Secret du client" + +#. module: auth_keycloak +#: code:addons/auth_keycloak/wizard/keycloak_sync_wiz.py:208 +#, python-format +msgid "" +"Conflict on user values. Please verify that all values supposed to be unique " +"are really unique. %(detail)s" +msgstr "" +"Conflit sur les valeurs de l'utilisateur. Vérifiez que toutes les valeurs " +"qui sont sensées être uniques le sont réellement. %(detail)s" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_create_wiz +msgid "Create" +msgstr "Créer" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.view_users_form +msgid "Create this user on Keycloak too." +msgstr "Créer cet utilisateur également sur Keycloak." + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_create_wiz +msgid "Create user on Keycloak" +msgstr "Créer l'utilisateur sur Keycloak" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_create_uid +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_create_date +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_create_date +msgid "Created on" +msgstr "Créé le" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_display_name +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin_display_name +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_display_name +msgid "Display Name" +msgstr "Nom affiché" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_id +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin_id +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_id +msgid "ID" +msgstr "ID" + +#. module: auth_keycloak +#: code:addons/auth_keycloak/models/res_users.py:59 +#, python-format +msgid "Keycloak provider not found or not configured properly." +msgstr "Fournisseur Keycloak non trouvé ou mal configuré." + +#. module: auth_keycloak +#: model:ir.actions.act_window,name:auth_keycloak.keycloak_sync_users +msgid "Keycloak sync users" +msgstr "Keycloak synchronise des utilisateurs" + +#. module: auth_keycloak +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_create_wiz_login_match_key +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_sync_mixin_login_match_key +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_sync_wiz_login_match_key +msgid "Keycloak user field to match users' login." +msgstr "" +"Champ utilisateur dans Keycloak correspondant au login de l'utilisateur." + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz___last_update +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin___last_update +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz___last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_write_uid +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_write_uid +msgid "Last Updated by" +msgstr "Dernière mise à jour par" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_write_date +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_write_date +msgid "Last Updated on" +msgstr "Dernière mise à jour le" + +#. module: auth_keycloak +#: model:auth.oauth.provider,body:auth_keycloak.default_keycloak_provider +#, fuzzy +msgid "Log in with Keycloak" +msgstr "Clé correspondant au login" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_login_match_key +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin_login_match_key +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_login_match_key +#, fuzzy +msgid "Login Match Key" +msgstr "Clé correspondant au login" + +#. module: auth_keycloak +#: code:addons/auth_keycloak/wizard/keycloak_sync_wiz.py:195 +#, python-format +msgid "No user selected" +msgstr "Aucun utilisateur sélectionné" + +#. module: auth_keycloak +#: model:ir.model,name:auth_keycloak.model_auth_oauth_provider +msgid "OAuth2 provider" +msgstr "Fournisseur OAuth2" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_provider_id +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin_provider_id +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_provider_id +msgid "Provider" +msgstr "Fournisseur" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.view_users_form +msgid "Push to Keycloak" +msgstr "Pousser sur Keycloak" + +#. module: auth_keycloak +#: model:ir.actions.act_window,name:auth_keycloak.keycloak_create_users +#, fuzzy +msgid "Push users to Keycloak" +msgstr "Pousser sur Keycloak" + +#. module: auth_keycloak +#: code:addons/auth_keycloak/wizard/keycloak_sync_wiz.py:71 +#, python-format +msgid "Something went wrong. Please check logs." +msgstr "Quelque chose s'est mal passé. Veuillez vérifier les logs." + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_user +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin_user +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_user +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_oauth_provider_superuser +msgid "Superuser" +msgstr "Super utilisateur" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_pwd +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin_pwd +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_pwd +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_oauth_provider_superuser_pwd +#, fuzzy +msgid "Superuser Pwd" +msgstr "Mot de passe du super utilisateur" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_sync_wiz +msgid "Sychronize existing users" +msgstr "Synchroniser les utilisateurs existants" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_sync_wiz +msgid "Sync" +msgstr "Synchroniser" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.view_oauth_provider_form +msgid "Sync users" +msgstr "Synchroniser les utilisateurs" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.auth_keycloak_sync_wiz +msgid "Synchronize users" +msgstr "Synchroniser les utilisateurs" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_user_ids +#, fuzzy +msgid "User" +msgstr "Utilisateurs" + +#. module: auth_keycloak +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_create_wiz_endpoint +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_sync_mixin_endpoint +#: model:ir.model.fields,help:auth_keycloak.field_auth_keycloak_sync_wiz_endpoint +#: model:ir.model.fields,help:auth_keycloak.field_auth_oauth_provider_users_endpoint +msgid "User endpoint" +msgstr "Endpoint utilisateur" + +#. module: auth_keycloak +#: model:ir.model,name:auth_keycloak.model_res_users +msgid "Users" +msgstr "Utilisateurs" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_endpoint +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin_endpoint +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_endpoint +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_oauth_provider_users_endpoint +#, fuzzy +msgid "Users Endpoint" +msgstr "Endpoint utilisateurs" + +#. module: auth_keycloak +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_create_wiz_management_enabled +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_mixin_management_enabled +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_keycloak_sync_wiz_management_enabled +#: model:ir.model.fields,field_description:auth_keycloak.field_auth_oauth_provider_users_management_enabled +#, fuzzy +msgid "Users Management Enabled" +msgstr "Gestion des utilisateurs activée" + +#. module: auth_keycloak +#: model:ir.ui.view,arch_db:auth_keycloak.view_oauth_provider_form +msgid "Users management (Keycloak)" +msgstr "Gestion des utilisateurs (Keycloak)" + +#. module: auth_keycloak +#: code:addons/auth_keycloak/wizard/keycloak_sync_wiz.py:57 +#, python-format +msgid "Users management must be enabled on selected provider" +msgstr "" +"La gestion des utilisateurs doit être activée sur le fournisseur sélectionné" + +#. module: auth_keycloak +#: model:ir.model,name:auth_keycloak.model_auth_keycloak_create_wiz +msgid "auth.keycloak.create.wiz" +msgstr "auth.keycloak.create.wiz" + +#. module: auth_keycloak +#: model:ir.model,name:auth_keycloak.model_auth_keycloak_sync_mixin +msgid "auth.keycloak.sync.mixin" +msgstr "auth.keycloak.sync.mixin" + +#. module: auth_keycloak +#: model:ir.model,name:auth_keycloak.model_auth_keycloak_sync_wiz +msgid "auth.keycloak.sync.wiz" +msgstr "auth.keycloak.sync.wiz" + +#. module: auth_keycloak +#: selection:auth.keycloak.create.wiz,login_match_key:0 +#: selection:auth.keycloak.sync.mixin,login_match_key:0 +#: selection:auth.keycloak.sync.wiz,login_match_key:0 +msgid "email" +msgstr "email" + +#. module: auth_keycloak +#: selection:auth.keycloak.create.wiz,login_match_key:0 +#: selection:auth.keycloak.sync.mixin,login_match_key:0 +#: selection:auth.keycloak.sync.wiz,login_match_key:0 +msgid "username" +msgstr "nom d'utilisateur" + +#~ msgid "Keycloak create users" +#~ msgstr "Keycloak crée des utilisateurs" + +#~ msgid "User ids" +#~ msgstr "Ids utilisateur" diff --git a/auth_keycloak/models/__init__.py b/auth_keycloak/models/__init__.py new file mode 100644 index 0000000000..a13cf366cd --- /dev/null +++ b/auth_keycloak/models/__init__.py @@ -0,0 +1,2 @@ +from . import auth_oauth_provider +from . import res_users diff --git a/auth_keycloak/models/auth_oauth_provider.py b/auth_keycloak/models/auth_oauth_provider.py new file mode 100644 index 0000000000..356757d10b --- /dev/null +++ b/auth_keycloak/models/auth_oauth_provider.py @@ -0,0 +1,45 @@ +# Copyright 2018 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from odoo import api, fields, models + + +class OAuthProvider(models.Model): + _inherit = "auth.oauth.provider" + + users_endpoint = fields.Char( + help="User endpoint, ie " + "http://keycloak.mycompany.com/auth/admin/realms/{realm}/users", + required=False, + ) + superuser = fields.Char( + help="A super power user that is able to CRUD users on KC, ie admin", + required=False, + ) + superuser_pwd = fields.Char( + help='"Superuser" user password', + required=False, + ) + users_management_enabled = fields.Boolean( + compute="_compute_users_management_enabled" + ) + send_password_email = fields.Boolean( + help="Send email with password reset link through keycloak", + default=False, + ) + + @api.depends( + "enabled", + "users_endpoint", + "superuser", + "superuser_pwd", + ) + def _compute_users_management_enabled(self): + for item in self: + item.users_management_enabled = all( + [ + item.enabled, + item.users_endpoint, + item.superuser, + item.superuser_pwd, + ] + ) diff --git a/auth_keycloak/models/res_users.py b/auth_keycloak/models/res_users.py new file mode 100644 index 0000000000..d30f73413e --- /dev/null +++ b/auth_keycloak/models/res_users.py @@ -0,0 +1,37 @@ +# Copyright 2018 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +import logging + +from odoo import _, exceptions, models + +logger = logging.getLogger(__name__) + + +class ResUsers(models.Model): + _inherit = "res.users" + + def button_push_to_keycloak(self): + """Quick action to push current users to Keycloak.""" + provider = ( + self.env["auth.oauth.provider"] + .search( + [ + ("enabled", "=", True), + ] + ) + .filtered("users_management_enabled") + ) + enabled = len(provider) == 1 + if not enabled: + raise exceptions.UserError( + _("Keycloak provider not found or not configured properly.") + ) + wiz = self.env["auth.keycloak.create.wiz"].create( + { + "provider_id": provider.id, + "user_ids": [(6, 0, self.ids)], + } + ) + action = self.env.ref("auth_keycloak.keycloak_create_users").read()[0] + action["res_id"] = wiz.id + return action diff --git a/auth_keycloak/readme/CONFIGURE.rst b/auth_keycloak/readme/CONFIGURE.rst new file mode 100644 index 0000000000..9832c2ef4b --- /dev/null +++ b/auth_keycloak/readme/CONFIGURE.rst @@ -0,0 +1,7 @@ +Settings -> Users & Companies -> OAuth Providers + +In the `Users management (Keycloak)` section, fill in your user admin endpoint, this will be in +the form $keycloak_url/admin/realms/$realm/users. Also fill in the name of a keycloak user that +is allowed to create users, and its password. + +This user must be allowed to query and manage users. diff --git a/auth_keycloak/readme/CONTRIBUTORS.rst b/auth_keycloak/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..fbe05aff03 --- /dev/null +++ b/auth_keycloak/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Simone Orsi +* Dennis Sluijk diff --git a/auth_keycloak/readme/CREDITS.rst b/auth_keycloak/readme/CREDITS.rst new file mode 100644 index 0000000000..00c3e56539 --- /dev/null +++ b/auth_keycloak/readme/CREDITS.rst @@ -0,0 +1 @@ +Development sponsored by `Sensefly `_ and `UTB `_. diff --git a/auth_keycloak/readme/DESCRIPTION.rst b/auth_keycloak/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..fb9b226a6c --- /dev/null +++ b/auth_keycloak/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module adds support for user creation when using `Keycloak `_ diff --git a/auth_keycloak/readme/HISTORY.rst b/auth_keycloak/readme/HISTORY.rst new file mode 100644 index 0000000000..904a982423 --- /dev/null +++ b/auth_keycloak/readme/HISTORY.rst @@ -0,0 +1,9 @@ +16.0.1.0.0 2026-02-20 +~~~~~~~~~~~~~~~~~~~~~ + +* v16 migration only implementing user creation, everything else is done by auth_oidc + +10.0.1.0.0 2018-10-17 +~~~~~~~~~~~~~~~~~~~~~ + +* Initial implementation diff --git a/auth_keycloak/readme/USAGE.rst b/auth_keycloak/readme/USAGE.rst new file mode 100644 index 0000000000..2ed5e63451 --- /dev/null +++ b/auth_keycloak/readme/USAGE.rst @@ -0,0 +1,36 @@ +**Link existing users from Keycloak** + +If you have existing users in Odoo and they are not linked to Keycloak yet +you can: + +1. get back to Settings -> Users -> OAuth Providers -> Keycloak +2. configure "Users management" box +3. click on "Sync users" button +4. select the matching key +5. submit + +Once it's done all matching and updated users will be listed in a list view. +Now your users will be able to log in on Keycloak + + +**Push new users to Keycloak** + +Usually Keycloak is already populated w/ your users base. +Many times this will come via LDAP, AD, pick yours. + +Still, you might need to push some users to Keycloak on demand, +maybe just for testing. + +If you need this, either you + +1. go to a single user form +2. hit the button "Push to Keycloak" (in the header) +3. use the wizard to push it + +or + +1. go to the users list view +2. select some users +3. click on Actions -> Push to Keycloak +4. select "Keycloak" provider +5. push them all diff --git a/auth_keycloak/security/ir.model.access.csv b/auth_keycloak/security/ir.model.access.csv new file mode 100644 index 0000000000..a0a808b136 --- /dev/null +++ b/auth_keycloak/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +auth_keycloak.access_auth_keycloak_sync_wiz,access_auth_keycloak_sync_wiz,model_auth_keycloak_sync_wiz,base.group_system,1,1,1,1 +auth_keycloak.access_auth_keycloak_create_wiz,access_auth_keycloak_create_wiz,model_auth_keycloak_create_wiz,base.group_system,1,1,1,1 diff --git a/auth_keycloak/static/description/icon.png b/auth_keycloak/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/auth_keycloak/static/description/icon.png differ diff --git a/auth_keycloak/static/description/index.html b/auth_keycloak/static/description/index.html new file mode 100644 index 0000000000..bb161e5104 --- /dev/null +++ b/auth_keycloak/static/description/index.html @@ -0,0 +1,493 @@ + + + + + +Keycloak auth integration + + + +
+

Keycloak auth integration

+ + +

Beta License: AGPL-3 OCA/server-auth Translate me on Weblate Try me on Runboat

+

This module adds support for user creation when using Keycloak

+

Table of contents

+ +
+

Configuration

+

Settings -> Users & Companies -> OAuth Providers

+

In the Users management (Keycloak) section, fill in your user admin endpoint, this will be in +the form $keycloak_url/admin/realms/$realm/users. Also fill in the name of a keycloak user that +is allowed to create users, and its password.

+

This user must be allowed to query and manage users.

+
+
+

Usage

+

Link existing users from Keycloak

+

If you have existing users in Odoo and they are not linked to Keycloak yet +you can:

+
    +
  1. get back to Settings -> Users -> OAuth Providers -> Keycloak
  2. +
  3. configure “Users management” box
  4. +
  5. click on “Sync users” button
  6. +
  7. select the matching key
  8. +
  9. submit
  10. +
+

Once it’s done all matching and updated users will be listed in a list view. +Now your users will be able to log in on Keycloak

+

Push new users to Keycloak

+

Usually Keycloak is already populated w/ your users base. +Many times this will come via LDAP, AD, pick yours.

+

Still, you might need to push some users to Keycloak on demand, +maybe just for testing.

+

If you need this, either you

+
    +
  1. go to a single user form
  2. +
  3. hit the button “Push to Keycloak” (in the header)
  4. +
  5. use the wizard to push it
  6. +
+

or

+
    +
  1. go to the users list view
  2. +
  3. select some users
  4. +
  5. click on Actions -> Push to Keycloak
  6. +
  7. select “Keycloak” provider
  8. +
  9. push them all
  10. +
+
+
+

Changelog

+
+

16.0.1.0.0 2026-02-20

+
    +
  • v16 migration only implementing user creation, everything else is done by auth_oidc
  • +
+
+
+

10.0.1.0.0 2018-10-17

+
    +
  • Initial implementation
  • +
+
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Camptocamp
  • +
+
+
+

Contributors

+ +
+
+

Other credits

+

Development sponsored by Sensefly and UTB.

+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/server-auth project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/auth_keycloak/tests/__init__.py b/auth_keycloak/tests/__init__.py new file mode 100644 index 0000000000..a677f803f0 --- /dev/null +++ b/auth_keycloak/tests/__init__.py @@ -0,0 +1,2 @@ +from . import test_wizard_sync +from . import test_wizard_create diff --git a/auth_keycloak/tests/common.py b/auth_keycloak/tests/common.py new file mode 100644 index 0000000000..96c5181414 --- /dev/null +++ b/auth_keycloak/tests/common.py @@ -0,0 +1,133 @@ +# Copyright 2018 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +import base64 + +import responses + +import odoo.tests.common as common + + +class TestKeycloakBase(common.TransactionCase): + + base_auth_url = "https://keycloak/auth" + base_openid_url = base_auth_url + "/realms/Odoo/protocol/openid-connect" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env( + context=dict(cls.env.context, tracking_disable=True, no_reset_password=True) + ) + cls.provider = cls.env["auth.oauth.provider"].create( + { + "name": "Keycloak", + "client_id": "odoo", + "client_secret": "c35a795e-65ef-432d-97fb-6ef4bea84bb8", + "auth_endpoint": cls.base_openid_url + "/auth", + "token_endpoint": cls.base_openid_url + "/token", + "body": "foo", + "enabled": True, + } + ) + + +FAKE_TOKEN_RESPONSE = { + "session_state": "623c9060-fd20-40e1-ad31-090bd77d521e", + "not-before-policy": 0, + "expires_in": 60, + "token_type": "bearer", + "refresh_expires_in": 1800, + "scope": "profile email", + "access_token": base64.encodebytes(b"my nice token").decode("utf-8"), + "refresh_token": base64.encodebytes(b"my nice refresh token").decode("utf-8"), +} +FAKE_USERS_RESPONSE = [ + { + "username": "jdoe", + "access": { + "manage": True, + "manageGroupMembership": True, + "impersonate": True, + "mapRoles": True, + "view": True, + }, + "notBefore": 0, + "email": "john@doe.com", + "emailVerified": False, + "enabled": True, + "createdTimestamp": 1539857662328, + "totp": False, + "disableableCredentialTypes": ["password"], + "requiredActions": [], + "id": "ef1d2e5d-1aad-4daf-858e-f246168a10ef", + }, + { + "username": "dduck", + "access": { + "manage": True, + "manageGroupMembership": True, + "impersonate": True, + "mapRoles": True, + "view": True, + }, + "firstName": "Donald", + "lastName": "Duck", + "notBefore": 0, + "emailVerified": False, + "requiredActions": [], + "enabled": True, + "email": "donald@duck.com", + "createdTimestamp": 1539871348882, + "totp": False, + "disableableCredentialTypes": [], + "id": "1feb89e6-76bd-44a1-ab5d-df28b6477e19", + }, +] + + +class TestKeycloakWizBase(TestKeycloakBase): + + wiz_model = "" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.users_endpoint = cls.base_auth_url + "/admin/realms/Odoo/users" + cls.provider.update( + { + "users_endpoint": cls.users_endpoint, + "superuser": "admin", + "superuser_pwd": 'well, yes, is "admin"', + } + ) + cls.wiz = cls.env[cls.wiz_model].create( + { + "provider_id": cls.provider.id, + } + ) + # create users matching keycloak response + cls.user_john = cls.env["res.users"].create( + { + "name": "John Doe", + "login": "jdoe", + "email": "john@doe.com", + } + ) + cls.user_donald = cls.env["res.users"].create( + { + "name": "Donald Duck", + "login": "dduck", + "email": "donald@duck.com", + } + ) + + def setUp(self): + super().setUp() + responses.add( + responses.POST, + self.provider.token_endpoint, + json=FAKE_TOKEN_RESPONSE, + status=200, + content_type="application/json", + ) diff --git a/auth_keycloak/tests/test_wizard_create.py b/auth_keycloak/tests/test_wizard_create.py new file mode 100644 index 0000000000..d854f893a5 --- /dev/null +++ b/auth_keycloak/tests/test_wizard_create.py @@ -0,0 +1,193 @@ +# Copyright 2018 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +from unittest import mock + +import responses + +from odoo import exceptions + +from .common import FAKE_USERS_RESPONSE, TestKeycloakWizBase + +FAKE_NEW_USER = { + "username": "mmouse", + "access": { + "manage": True, + "manageGroupMembership": True, + "impersonate": True, + "mapRoles": True, + "view": True, + }, + "firstName": "Micky", + "lastName": "Mouse", + "notBefore": 0, + "emailVerified": False, + "requiredActions": [], + "enabled": True, + "email": "mickey@mouse.com", + "createdTimestamp": 1539871348883, + "totp": False, + "disableableCredentialTypes": [], + "id": "1feb89e6-76bd-44a1-ab5d-df28b2477e19", +} + + +class TestWizard(TestKeycloakWizBase): + + wiz_model = "auth.keycloak.create.wiz" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.user_mickey = cls.env["res.users"].create( + { + "name": "Mickey Mouse", + "login": "mmouse", + "email": "mickey@mouse.com", + } + ) + + def test_create_user_values(self): + values = self.wiz._create_user_values(self.user_donald) + self.assertEqual(values["username"], "dduck") + self.assertEqual(values["email"], "donald@duck.com") + # you could have partner_firstname installed + # or an override in fullname handling, + # hence we might have different results here. + # We just make sure that we have both names... + self.assertTrue(values["firstName"]) + self.assertTrue(values["lastName"]) + + values = self.wiz._create_user_values(self.user_john) + self.assertEqual(values["username"], "jdoe") + self.assertEqual(values["email"], "john@doe.com") + self.assertTrue(values["firstName"]) + self.assertTrue(values["lastName"]) + + @responses.activate + def test_get_or_create_user_exists(self): + # make users endpoint return one user less + responses.add( + responses.GET, + self.wiz.endpoint, + # return only one + json=FAKE_USERS_RESPONSE[1:2], + status=200, + content_type="application/json", + ) + with mock.patch.object(type(self.wiz), "_create_user") as mock_create_user: + kk_user = self.wiz._get_or_create_user("TOKEN", self.user_donald) + + self.assertEqual(kk_user["id"], FAKE_USERS_RESPONSE[1]["id"]) + # user exists, no call to create user issued + self.assertFalse(mock_create_user.called) + # indeed we find only 1 calls + self.assertEqual(len(responses.calls), 1) + request = responses.calls[0].request + self.assertEqual( + request.url, self.wiz.endpoint + "?search=%s" % self.user_donald.login + ) + auth = request.headers["Authorization"].replace("Bearer ", "") + self.assertEqual(auth, "TOKEN") + + @responses.activate + def test_get_or_create_user_not_exists(self): + # mock 1st call to get all users: no users + responses.add( + responses.GET, + self.wiz.endpoint, + json=[], + status=200, + content_type="application/json", + ) + # mock 2nd call to create a new user: all good, nothing back + responses.add( + responses.POST, + self.wiz.endpoint, + body="", + status=200, + content_type="application/json", + ) + # mock 3rd call to retrieve new user's data + # yes, Keycloak sends back NOTHING :( + responses.add( + responses.GET, + self.wiz.endpoint, + json=[ + FAKE_NEW_USER, + ], + status=200, + content_type="application/json", + ) + kk_user = self.wiz._get_or_create_user("TOKEN", self.user_mickey) + self.assertDictEqual(kk_user, FAKE_NEW_USER) + self.assertEqual(len(responses.calls), 3) + request = responses.calls[0].request + self.assertEqual( + request.url, self.wiz.endpoint + "?search=%s" % self.user_mickey.login + ) + auth = request.headers["Authorization"].replace("Bearer ", "") + self.assertEqual(auth, "TOKEN") + + @responses.activate + def test_create_user_conflict(self): + # simulate again we found no user + responses.add( + responses.GET, + self.wiz.endpoint, + json=[], + status=200, + content_type="application/json", + ) + # and we try to create a new one, but + # simulate HTTPError: 409 Client Error: + # Conflict for url: https://keycloak/auth/admin/realms/Odoo/users + responses.add( + responses.POST, + self.wiz.endpoint, + body="", + status=409, + content_type="application/json", + ) + with self.assertRaises(exceptions.UserError) as err: + self.wiz._get_or_create_user("TOKEN", self.user_mickey) + self.assertEqual(len(responses.calls), 2) + self.assertIn("Conflict on user values.", str(err.exception)) + + @responses.activate + def test_wizard_action(self): + responses.add( + responses.GET, + self.wiz.endpoint, + json=[], + status=200, + content_type="application/json", + ) + responses.add( + responses.POST, + self.wiz.endpoint, + body="", + status=200, + content_type="application/json", + ) + responses.add( + responses.GET, + self.wiz.endpoint, + json=[ + FAKE_NEW_USER, + ], + status=200, + content_type="application/json", + ) + responses.add( + responses.PUT, + self.wiz.endpoint + "/mmouse/execute-actions-email", + body="", + status=200, + content_type="application/json", + ) + self.provider.send_password_email = True + wizard_action = self.user_mickey.button_push_to_keycloak() + wizard = self.env[wizard_action["res_model"]].browse(wizard_action["res_id"]) + wizard.button_create_user() + self.assertEqual(self.user_mickey.oauth_provider_id, self.provider) diff --git a/auth_keycloak/tests/test_wizard_sync.py b/auth_keycloak/tests/test_wizard_sync.py new file mode 100644 index 0000000000..ae23ba60dc --- /dev/null +++ b/auth_keycloak/tests/test_wizard_sync.py @@ -0,0 +1,91 @@ +# Copyright 2018 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +import base64 +from urllib import parse + +import responses + +from .common import FAKE_TOKEN_RESPONSE, FAKE_USERS_RESPONSE, TestKeycloakWizBase + + +class TestWizard(TestKeycloakWizBase): + + wiz_model = "auth.keycloak.sync.wiz" + + def setUp(self): + super().setUp() + responses.add( + responses.GET, + self.wiz.endpoint, + json=FAKE_USERS_RESPONSE, + status=200, + content_type="application/json", + ) + + @responses.activate + def test_get_token(self): + token = self.wiz._get_token() + self.assertEqual(len(responses.calls), 1) + self.assertDictEqual(responses.calls[0].response.json(), FAKE_TOKEN_RESPONSE) + self.assertEqual(token, FAKE_TOKEN_RESPONSE["access_token"]) + request = responses.calls[0].request + self.assertEqual(request.url, self.provider.token_endpoint) + expected = [ + ("grant_type", "password"), + ("username", "admin"), + ("password", 'well, yes, is "admin"'), + ("client_id", "odoo"), + ("client_secret", "c35a795e-65ef-432d-97fb-6ef4bea84bb8"), + ] + self.assertListEqual(sorted(parse.parse_qsl(request.body)), sorted(expected)) + + @responses.activate + def test_get_users(self): + token = self.wiz._get_token() + users = self.wiz._get_users(token) + self.assertEqual(len(responses.calls), 2) + self.assertListEqual(responses.calls[1].response.json(), FAKE_USERS_RESPONSE) + self.assertEqual(len(users), 2) + request = responses.calls[1].request + self.assertEqual(request.url, self.wiz.endpoint) + auth = request.headers["Authorization"].replace("Bearer ", "") + self.assertEqual(base64.decodebytes(auth.encode()), b"my nice token") + + @responses.activate + def test_sync_by_username(self): + self.assertFalse(self.user_donald.oauth_uid) + self.assertFalse(self.user_john.oauth_uid) + self.assertEqual(self.wiz.login_match_key, "username:login") + action = self.wiz.button_sync() + self.assertEqual(len(responses.calls), 2) + self.assertEqual( + action["domain"], [("id", "in", (self.user_donald + self.user_john).ids)] + ) + self.assertEqual( + self.user_donald.oauth_uid, "1feb89e6-76bd-44a1-ab5d-df28b6477e19" + ) + self.assertEqual(self.user_donald.oauth_provider_id, self.provider) + self.assertEqual( + self.user_john.oauth_uid, "ef1d2e5d-1aad-4daf-858e-f246168a10ef" + ) + self.assertEqual(self.user_john.oauth_provider_id, self.provider) + + @responses.activate + def test_sync_by_email(self): + self.assertFalse(self.user_donald.oauth_uid) + self.assertFalse(self.user_john.oauth_uid) + self.wiz.login_match_key = "email:partner_id.email" + action = self.wiz.button_sync() + self.assertEqual(len(responses.calls), 2) + self.assertEqual( + action["domain"], [("id", "in", (self.user_donald + self.user_john).ids)] + ) + self.assertEqual( + self.user_donald.oauth_uid, "1feb89e6-76bd-44a1-ab5d-df28b6477e19" + ) + self.assertEqual(self.user_donald.oauth_provider_id, self.provider) + self.assertEqual( + self.user_john.oauth_uid, "ef1d2e5d-1aad-4daf-858e-f246168a10ef" + ) + self.assertEqual(self.user_john.oauth_provider_id, self.provider) diff --git a/auth_keycloak/views/auth_oauth_views.xml b/auth_keycloak/views/auth_oauth_views.xml new file mode 100644 index 0000000000..5f09c0bf25 --- /dev/null +++ b/auth_keycloak/views/auth_oauth_views.xml @@ -0,0 +1,33 @@ + + + + auth.oauth.provider.form + auth.oauth.provider + + + + + + + + + + + +