From 2db2173fb2e1a379a5dee75d7556befde88704d4 Mon Sep 17 00:00:00 2001 From: wluyima Date: Thu, 10 Oct 2024 10:50:36 +0300 Subject: [PATCH] Add support to skip the logout confirmation in the auth provider Add field to oauth provider to toggle skip logout confirmation Added tests for the skip confirmation feature Applied auto format changes Removed unused line Reduced long line --- auth_oidc/controllers/main.py | 2 + auth_oidc/models/auth_oauth_provider.py | 6 +++ auth_oidc/models/res_users.py | 9 +++- auth_oidc/tests/test_auth_oidc_logout.py | 55 ++++++++++++++++++++++++ auth_oidc/views/auth_oauth_provider.xml | 1 + 5 files changed, 72 insertions(+), 1 deletion(-) diff --git a/auth_oidc/controllers/main.py b/auth_oidc/controllers/main.py index f7d17dd790..9ef0ca9020 100644 --- a/auth_oidc/controllers/main.py +++ b/auth_oidc/controllers/main.py @@ -72,6 +72,8 @@ def logout(self, redirect="/web/login"): params = parse_qs(components.query) params["client_id"] = provider.client_id params["post_logout_redirect_uri"] = redirect_url + if provider.skip_logout_confirmation and user.oauth_id_token: + params["id_token_hint"] = user.oauth_id_token logout_url = components._replace(query=url_encode(params)).geturl() return super().logout(redirect=logout_url) # User has no account with any provider or no logout URL is configured for the provider diff --git a/auth_oidc/models/auth_oauth_provider.py b/auth_oidc/models/auth_oauth_provider.py index 2599eb3ece..2d326ec2b0 100644 --- a/auth_oidc/models/auth_oauth_provider.py +++ b/auth_oidc/models/auth_oauth_provider.py @@ -52,6 +52,12 @@ class AuthOauthProvider(models.Model): "in the client, should be the value of end_session_endpoint specified by " "the authorization provider.", ) + skip_logout_confirmation = fields.Boolean( + default=False, + string="Skip Logout Confirmation", + help="If set to true, the logout confirmation is skipped in the " + "authorization provider.", + ) @tools.ormcache("self.jwks_uri", "kid") def _get_keys(self, kid): diff --git a/auth_oidc/models/res_users.py b/auth_oidc/models/res_users.py index 4743619e00..50d07d4eef 100644 --- a/auth_oidc/models/res_users.py +++ b/auth_oidc/models/res_users.py @@ -6,7 +6,7 @@ import requests -from odoo import api, models +from odoo import api, fields, models from odoo.exceptions import AccessDenied from odoo.http import request @@ -16,6 +16,8 @@ class ResUsers(models.Model): _inherit = "res.users" + oauth_id_token = fields.Char(string="OAuth Id Token", readonly=True, copy=False) + def _auth_oauth_get_tokens_implicit_flow(self, oauth_provider, params): # https://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse return params.get("access_token"), params.get("id_token") @@ -74,7 +76,12 @@ def auth_oauth(self, provider, params): raise AccessDenied() # retrieve and sign in user params["access_token"] = access_token + params["id_token"] = id_token login = self._auth_oauth_signin(provider, validation, params) + oauth_user = self.search( + [("login", "=", login), ("oauth_access_token", "=", access_token)] + ) + oauth_user.write({"oauth_id_token": params["id_token"]}) if not login: raise AccessDenied() # return user credentials diff --git a/auth_oidc/tests/test_auth_oidc_logout.py b/auth_oidc/tests/test_auth_oidc_logout.py index 5280ddcf54..4d4003f908 100644 --- a/auth_oidc/tests/test_auth_oidc_logout.py +++ b/auth_oidc/tests/test_auth_oidc_logout.py @@ -128,3 +128,58 @@ def test_oidc_logout_with_absolute_redirect_url(self): actual_params = dict(parse_qsl(actual_components.query)) self.assertEqual(CLIENT_ID, actual_params["client_id"]) self.assertEqual(BASE_URL, actual_params["post_logout_redirect_uri"]) + + def test_oidc_logout_skip_confirmation(self): + """Test that oidc logout skips confirmation""" + id_token = "test-id-token" + self._set_test_oidc_logout_url(urljoin(OIDC_BASE_LOGOUT_URL, OIDC_LOGOUT_PATH)) + self.provider.write({"skip_logout_confirmation": True}) + user = self._prepare_login_test_user(self.provider) + user.write({"oauth_id_token": id_token}) + with create_request(self.env, user.id, self.mock_logout_user): + resp = OpenIDLogout().logout() + self.assertTrue(resp.location.startswith(OIDC_BASE_LOGOUT_URL)) + actual_components = urlparse(resp.location) + self.assertEqual(OIDC_LOGOUT_PATH, actual_components.path) + actual_params = dict(parse_qsl(actual_components.query)) + self.assertEqual(CLIENT_ID, actual_params["client_id"]) + self.assertEqual(id_token, actual_params["id_token_hint"]) + self.assertEqual( + urljoin(BASE_URL, LOGIN_PATH), actual_params["post_logout_redirect_uri"] + ) + + def test_oidc_logout_not_skip_confirmation_if_no_id_token(self): + """Test that oidc logout does not skip confirmation if user has no oauth_id_token""" + self._set_test_oidc_logout_url(urljoin(OIDC_BASE_LOGOUT_URL, OIDC_LOGOUT_PATH)) + self.provider.write({"skip_logout_confirmation": True}) + user = self._prepare_login_test_user(self.provider) + with create_request(self.env, user.id, self.mock_logout_user): + resp = OpenIDLogout().logout() + self.assertTrue(resp.location.startswith(OIDC_BASE_LOGOUT_URL)) + actual_components = urlparse(resp.location) + self.assertEqual(OIDC_LOGOUT_PATH, actual_components.path) + actual_params = dict(parse_qsl(actual_components.query)) + self.assertEqual(CLIENT_ID, actual_params["client_id"]) + self.assertIsNone(actual_params.get("id_token_hint")) + self.assertEqual( + urljoin(BASE_URL, LOGIN_PATH), actual_params["post_logout_redirect_uri"] + ) + + def test_oidc_logout_not_skip_confirmation_if_not_enabled(self): + """Test that oidc logout skips confirmation""" + id_token = "test-id-token" + self._set_test_oidc_logout_url(urljoin(OIDC_BASE_LOGOUT_URL, OIDC_LOGOUT_PATH)) + self.provider.write({"skip_logout_confirmation": False}) + user = self._prepare_login_test_user(self.provider) + user.write({"oauth_id_token": id_token}) + with create_request(self.env, user.id, self.mock_logout_user): + resp = OpenIDLogout().logout() + self.assertTrue(resp.location.startswith(OIDC_BASE_LOGOUT_URL)) + actual_components = urlparse(resp.location) + self.assertEqual(OIDC_LOGOUT_PATH, actual_components.path) + actual_params = dict(parse_qsl(actual_components.query)) + self.assertEqual(CLIENT_ID, actual_params["client_id"]) + self.assertIsNone(actual_params.get("id_token_hint")) + self.assertEqual( + urljoin(BASE_URL, LOGIN_PATH), actual_params["post_logout_redirect_uri"] + ) diff --git a/auth_oidc/views/auth_oauth_provider.xml b/auth_oidc/views/auth_oauth_provider.xml index c890fb55a8..1cf85680b1 100644 --- a/auth_oidc/views/auth_oauth_provider.xml +++ b/auth_oidc/views/auth_oauth_provider.xml @@ -19,6 +19,7 @@ +