-
-
Notifications
You must be signed in to change notification settings - Fork 492
[ADD] auth_saml: Support for unsolicited SAML requests #836
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 18.0
Are you sure you want to change the base?
Changes from all commits
294bb9f
f8b7d50
ce7c82e
dabefe5
6524c63
68901f5
a625ed8
1918d26
225c43a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -137,6 +137,12 @@ class AuthSamlProvider(models.Model): | |
| help="Whether metadata should be signed or not", | ||
| ) | ||
|
|
||
| allow_saml_unsolicited_req = fields.Boolean( | ||
| compute="_compute_allow_saml_unsolicited", | ||
| string="Allow Unsolicited Requests", | ||
| help="Allow IdP-initiated authentication requests", | ||
| ) | ||
|
|
||
| @api.model | ||
| def _sig_alg_selection(self): | ||
| return [(sig[0], sig[0]) for sig in ds.SIG_ALLOWED_ALG] | ||
|
|
@@ -219,7 +225,7 @@ def _get_config_for_provider(self, base_url: str = None) -> Saml2Config: | |
| (acs_url, saml2.BINDING_HTTP_POST), | ||
| ], | ||
| }, | ||
| "allow_unsolicited": False, | ||
| "allow_unsolicited": self.allow_saml_unsolicited_req, | ||
|
Comment on lines
-222
to
+228
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’m not confident the company is correct at this place, I believe it might be using the superuser company instead of any company. |
||
| "authn_requests_signed": self.authn_requests_signed, | ||
| "logout_requests_signed": self.logout_requests_signed, | ||
| "want_assertions_signed": self.want_assertions_signed, | ||
|
|
@@ -370,3 +376,9 @@ def _hook_validate_auth_response(self, response, matching_value): | |
| vals[attribute.field_name] = attribute_value | ||
|
|
||
| return {"mapped_attrs": vals} | ||
|
|
||
| def _compute_allow_saml_unsolicited(self): | ||
| for record in self: | ||
| record.allow_saml_unsolicited_req = ( | ||
| self.env.company.allow_saml_unsolicited_req | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # Copyright (C) 2010-2016, 2022 XCG Consulting <http://odoo.consulting> | ||
| # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
|
||
| from odoo import fields, models | ||
|
|
||
|
|
||
| class ResCompany(models.Model): | ||
| _inherit = "res.company" | ||
|
|
||
| allow_saml_unsolicited_req = fields.Boolean( | ||
| string="Allow SAML Unsolicited Requests", | ||
| help="Allow IdP-initiated authentication requests without", | ||
| ) |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,82 @@ | ||||||
| # Copyright (C) 2010-2016, 2022 XCG Consulting <http://odoo.consulting> | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
No need to keep the copyright unless you copied something from another test. |
||||||
| # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||||||
|
|
||||||
| import base64 | ||||||
| import os | ||||||
|
|
||||||
| from odoo.tests import TransactionCase | ||||||
|
|
||||||
| from .fake_idp import FakeIDP | ||||||
|
|
||||||
|
|
||||||
| class TestUnsolicitedRequests(TransactionCase): | ||||||
| def setUp(self): | ||||||
| super().setUp() | ||||||
|
|
||||||
| with open( | ||||||
| os.path.join(os.path.dirname(__file__), "data", "sp.pem"), | ||||||
| encoding="UTF-8", | ||||||
| ) as file: | ||||||
| sp_pem_public = file.read() | ||||||
|
|
||||||
| with open( | ||||||
| os.path.join(os.path.dirname(__file__), "data", "sp.key"), | ||||||
| encoding="UTF-8", | ||||||
| ) as file: | ||||||
| sp_pem_private = file.read() | ||||||
|
|
||||||
| self.saml_provider = self.env["auth.saml.provider"].create( | ||||||
| { | ||||||
| "name": "SAML Provider Demo", | ||||||
| "idp_metadata": FakeIDP().get_metadata(), | ||||||
| "sp_pem_public": base64.b64encode(sp_pem_public.encode()), | ||||||
| "sp_pem_private": base64.b64encode(sp_pem_private.encode()), | ||||||
| "body": "Login with Provider", | ||||||
| "active": True, | ||||||
| "sig_alg": "SIG_RSA_SHA1", | ||||||
| "matching_attribute": "mail", | ||||||
| } | ||||||
| ) | ||||||
|
|
||||||
| def test_unsolicited_request_setting_default_false(self): | ||||||
| """Test that unsolicited requests are disabled by default""" | ||||||
| # Default company setting should be False | ||||||
| self.assertFalse(self.env.company.allow_saml_unsolicited_req) | ||||||
|
|
||||||
| # Provider computed field should reflect company setting | ||||||
| self.assertFalse(self.saml_provider.allow_saml_unsolicited_req) | ||||||
|
|
||||||
| def test_unsolicited_request_setting_enabled(self): | ||||||
| """Test enabling unsolicited requests""" | ||||||
| # Enable unsolicited requests for company | ||||||
| self.env.company.allow_saml_unsolicited_req = True | ||||||
|
|
||||||
| # Provider computed field should reflect the change | ||||||
| self.saml_provider._compute_allow_saml_unsolicited() | ||||||
| self.assertTrue(self.saml_provider.allow_saml_unsolicited_req) | ||||||
|
|
||||||
| def test_saml_config_with_unsolicited_enabled(self): | ||||||
| """Test that SAML configuration includes unsolicited setting""" | ||||||
| # Enable unsolicited requests | ||||||
| self.env.company.allow_saml_unsolicited_req = True | ||||||
| self.saml_provider._compute_allow_saml_unsolicited() | ||||||
|
|
||||||
| # Get SAML config | ||||||
| config = self.saml_provider._get_config_for_provider() | ||||||
|
|
||||||
| # Check that the config includes the allow_unsolicited setting | ||||||
| sp_config = config.getattr("service", "sp") | ||||||
| self.assertTrue(sp_config.get("allow_unsolicited")) | ||||||
|
|
||||||
| def test_saml_config_with_unsolicited_disabled(self): | ||||||
| """Test that SAML configuration respects disabled unsolicited setting""" | ||||||
| # Ensure unsolicited requests are disabled | ||||||
| self.env.company.allow_saml_unsolicited_req = False | ||||||
| self.saml_provider._compute_allow_saml_unsolicited() | ||||||
|
|
||||||
| # Get SAML config | ||||||
| config = self.saml_provider._get_config_for_provider() | ||||||
|
|
||||||
| # Check that the config does not allow unsolicited requests | ||||||
| sp_config = config.getattr("service", "sp") | ||||||
| self.assertFalse(sp_config.get("allow_unsolicited")) | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,13 @@ | |
| id="module_auth_saml" | ||
| > | ||
| <field name="allow_saml_uid_and_internal_password" /> | ||
| <!-- Allow IdP-initiated authentication without prior AuthnRequest from SP. | ||
| Security Note: Only enable if your IdP requires unsolicited requests | ||
| and you trust the IdP to properly validate user sessions. --> | ||
|
Comment on lines
+16
to
+18
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not be a comment, it should be displayed in the configuration page. |
||
| <field | ||
| name="allow_saml_unsolicited_req" | ||
| string="Allow SAML Unsolicited Requests" | ||
| /> | ||
| </setting> | ||
| </xpath> | ||
| </field> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why make this a company dependent value and not a provider dependant one?
If it is company dependent, it should only be in the configuration panel.