From 3fe056c048359e856352d9d293e1f56c4d98823c Mon Sep 17 00:00:00 2001 From: LauraCForgeFlow Date: Tue, 14 Jul 2026 12:30:40 +0200 Subject: [PATCH 1/4] [ADD] auth_totp_enforce --- .github/workflows/test.yml | 11 +- auth_totp_enforce/README.rst | 100 ++++ auth_totp_enforce/__init__.py | 5 + auth_totp_enforce/__manifest__.py | 18 + auth_totp_enforce/controllers/__init__.py | 4 + auth_totp_enforce/controllers/home.py | 71 +++ auth_totp_enforce/models/__init__.py | 4 + auth_totp_enforce/models/res_users.py | 57 +++ auth_totp_enforce/pyproject.toml | 3 + auth_totp_enforce/readme/CONTRIBUTORS.md | 2 + auth_totp_enforce/readme/DESCRIPTION.md | 10 + auth_totp_enforce/readme/USAGE.md | 7 + auth_totp_enforce/security/res_groups.xml | 10 + .../static/description/index.html | 447 ++++++++++++++++++ auth_totp_enforce/tests/__init__.py | 5 + auth_totp_enforce/tests/test_enforce.py | 54 +++ auth_totp_enforce/tests/test_enforce_flow.py | 91 ++++ auth_totp_enforce/views/templates.xml | 89 ++++ 18 files changed, 986 insertions(+), 2 deletions(-) create mode 100644 auth_totp_enforce/README.rst create mode 100644 auth_totp_enforce/__init__.py create mode 100644 auth_totp_enforce/__manifest__.py create mode 100644 auth_totp_enforce/controllers/__init__.py create mode 100644 auth_totp_enforce/controllers/home.py create mode 100644 auth_totp_enforce/models/__init__.py create mode 100644 auth_totp_enforce/models/res_users.py create mode 100644 auth_totp_enforce/pyproject.toml create mode 100644 auth_totp_enforce/readme/CONTRIBUTORS.md create mode 100644 auth_totp_enforce/readme/DESCRIPTION.md create mode 100644 auth_totp_enforce/readme/USAGE.md create mode 100644 auth_totp_enforce/security/res_groups.xml create mode 100644 auth_totp_enforce/static/description/index.html create mode 100644 auth_totp_enforce/tests/__init__.py create mode 100644 auth_totp_enforce/tests/test_enforce.py create mode 100644 auth_totp_enforce/tests/test_enforce_flow.py create mode 100644 auth_totp_enforce/views/templates.xml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a3e2701930..c48e6dc7c6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,10 +43,17 @@ jobs: name: test with OCB makepot: "true" - container: ghcr.io/oca/oca-ci/py3.10-odoo18.0:latest - exclude: "password_security" + include: "auth_totp_enforce" name: test with Odoo - container: ghcr.io/oca/oca-ci/py3.10-ocb18.0:latest - exclude: "password_security" + include: "auth_totp_enforce" + name: test with OCB + makepot: "true" + - container: ghcr.io/oca/oca-ci/py3.10-odoo18.0:latest + exclude: "password_security,auth_totp_enforce" + name: test with Odoo + - container: ghcr.io/oca/oca-ci/py3.10-ocb18.0:latest + exclude: "password_security,auth_totp_enforce" name: test with OCB makepot: "true" services: diff --git a/auth_totp_enforce/README.rst b/auth_totp_enforce/README.rst new file mode 100644 index 0000000000..37b4b0ceb0 --- /dev/null +++ b/auth_totp_enforce/README.rst @@ -0,0 +1,100 @@ +================================= +Enforce Two-Factor Authentication +================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:d7a9647142270eff465347946c269fc042be4388b9f9d22a0d3ff1239f105dae + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/18.0/auth_totp_enforce + :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-18-0/server-auth-18-0-auth_totp_enforce + :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=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module forces internal users to configure two-factor authentication +(a TOTP authenticator app) before they log in. + +Enforcement works like the password expiry flow of +``password_security``: nobody is logged out when the module is +installed, but on their next login users are held in a mandatory step +and cannot reach the backend until they have enabled an authenticator +app. + +Members of the **Exempt from 2FA enforcement** group are never forced. +Portal and public users are out of scope at the moment. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Once installed, any enforced internal user without an authenticator app +configured will, on their next login: + +1. Enter their login and password as usual. +2. Be redirected to a mandatory setup page showing a QR code and secret + key. +3. Scan the code with an authenticator app and enter the generated code. +4. Be handed over to the standard two-factor step to complete the login. + +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 +------- + +* ForgeFlow + +Contributors +------------ + +- ForgeFlow S.L. + + - Laura Cazorla + +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_totp_enforce/__init__.py b/auth_totp_enforce/__init__.py new file mode 100644 index 0000000000..97141152b6 --- /dev/null +++ b/auth_totp_enforce/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2026 ForgeFlow S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models +from . import controllers diff --git a/auth_totp_enforce/__manifest__.py b/auth_totp_enforce/__manifest__.py new file mode 100644 index 0000000000..f28292e851 --- /dev/null +++ b/auth_totp_enforce/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2026 ForgeFlow S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Enforce Two-Factor Authentication", + "summary": "Force users to set up an authenticator app before they can log in.", + "version": "18.0.1.0.0", + "author": "ForgeFlow, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/server-auth", + "category": "Authentication", + "license": "AGPL-3", + "depends": ["auth_totp"], + "data": [ + "security/res_groups.xml", + "views/templates.xml", + ], + "installable": True, +} diff --git a/auth_totp_enforce/controllers/__init__.py b/auth_totp_enforce/controllers/__init__.py new file mode 100644 index 0000000000..fba520a076 --- /dev/null +++ b/auth_totp_enforce/controllers/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2026 ForgeFlow S.L. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import home diff --git a/auth_totp_enforce/controllers/home.py b/auth_totp_enforce/controllers/home.py new file mode 100644 index 0000000000..c4723e964d --- /dev/null +++ b/auth_totp_enforce/controllers/home.py @@ -0,0 +1,71 @@ +# Copyright 2026 ForgeFlow S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, http +from odoo.http import request + +import odoo.addons.auth_totp.controllers.home + +SESSION_KEY = "totp_enforce_setup" + + +class Home(odoo.addons.auth_totp.controllers.home.Home): + @http.route( + "/web/login/totp/setup", + type="http", + auth="public", + methods=["GET", "POST"], + sitemap=False, + website=True, + multilang=False, + ) + def web_totp_setup(self, redirect=None, **kwargs): + if request.session.uid: + return request.redirect( + self._login_redirect(request.session.uid, redirect=redirect) + ) + if not request.session.pre_uid: + return request.redirect("/web/login") + user = request.env["res.users"].sudo().browse(request.session.pre_uid) + if not user._mfa_enforced() or user.totp_enabled: + return request.redirect("/web/login/totp") + stored = request.session.get(SESSION_KEY) + if not stored or stored.get("uid") != user.id: + secret = user._generate_totp_setup_secret() + request.session[SESSION_KEY] = {"uid": user.id, "secret": secret} + else: + secret = stored["secret"] + error = None + if request.httprequest.method == "POST" and kwargs.get("totp_token"): + if user._totp_enforce_setup(secret, kwargs["totp_token"]): + request.session.pop(SESSION_KEY, None) + # Persist secret, session stays partial and delegate to standard MFA + request.env.flush_all() + user.invalidate_recordset(["totp_secret", "totp_enabled"]) + request.session.touch() + return request.redirect( + self._login_redirect(user.id, redirect=redirect) + ) + error = _("Verification failed, please double-check the 6-digit code") + wizard = ( + request.env["auth_totp.wizard"] + .sudo() + .new( + { + "user_id": user.id, + "secret": secret, + } + ) + ) + request.session.touch() + return request.render( + "auth_totp_enforce.auth_totp_setup_form", + { + "user": user, + "error": error, + "redirect": redirect, + "secret": secret, + "qrcode": wizard.qrcode, + "url": wizard.url, + }, + ) diff --git a/auth_totp_enforce/models/__init__.py b/auth_totp_enforce/models/__init__.py new file mode 100644 index 0000000000..56c7b1c2fe --- /dev/null +++ b/auth_totp_enforce/models/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2026 ForgeFlow S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import res_users diff --git a/auth_totp_enforce/models/res_users.py b/auth_totp_enforce/models/res_users.py new file mode 100644 index 0000000000..bdd991f153 --- /dev/null +++ b/auth_totp_enforce/models/res_users.py @@ -0,0 +1,57 @@ +# Copyright 2026 ForgeFlow S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import base64 +import functools +import logging +import os +import re + +from odoo import models +from odoo.http import request + +from odoo.addons.auth_totp.models.totp import TOTP, TOTP_SECRET_SIZE + +_logger = logging.getLogger(__name__) + +compress = functools.partial(re.sub, r"\s", "") + + +class ResUsers(models.Model): + _inherit = "res.users" + + def _mfa_enforced(self): + self.ensure_one() + user = self.sudo() + exempt_group = "auth_totp_enforce.group_mfa_exempt" + return user._is_internal() and not user.has_group(exempt_group) + + def _mfa_url(self): + r = super()._mfa_url() + if r is not None: + return r + if self._mfa_enforced() and not self.totp_enabled: + return "/web/login/totp/setup" + + def _generate_totp_setup_secret(self): + secret = base64.b32encode(os.urandom(TOTP_SECRET_SIZE // 8)).decode() + return " ".join(map("".join, zip(*[iter(secret)] * 4, strict=False))) + + def _totp_enforce_setup(self, secret, code): + self.ensure_one() + assert self.env.su or ( + request + and not request.session.uid + and request.session.get("pre_uid") == self.id + ), "Only callable for the user currently in the pre-authentication phase" + secret = compress(secret).upper() + try: + code = int(compress(str(code))) + except ValueError: + return False + if TOTP(base64.b32decode(secret)).match(code) is None: + _logger.info("2FA enforce setup: REJECT CODE for %r", self.login) + return False + self.sudo().totp_secret = secret + _logger.info("2FA enforce setup: SUCCESS for %r", self.login) + return True diff --git a/auth_totp_enforce/pyproject.toml b/auth_totp_enforce/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/auth_totp_enforce/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/auth_totp_enforce/readme/CONTRIBUTORS.md b/auth_totp_enforce/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..c14e159d67 --- /dev/null +++ b/auth_totp_enforce/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- ForgeFlow S.L. \<\> + - Laura Cazorla \<\> diff --git a/auth_totp_enforce/readme/DESCRIPTION.md b/auth_totp_enforce/readme/DESCRIPTION.md new file mode 100644 index 0000000000..4e4b6f899e --- /dev/null +++ b/auth_totp_enforce/readme/DESCRIPTION.md @@ -0,0 +1,10 @@ +This module forces internal users to configure two-factor authentication +(a TOTP authenticator app) before they log in. + +Enforcement works like the password expiry flow of `password_security`: nobody +is logged out when the module is installed, but on their next login users are +held in a mandatory step and cannot reach the backend until they have enabled +an authenticator app. + +Members of the **Exempt from 2FA enforcement** group are never forced. Portal +and public users are out of scope at the moment. diff --git a/auth_totp_enforce/readme/USAGE.md b/auth_totp_enforce/readme/USAGE.md new file mode 100644 index 0000000000..76d114f76a --- /dev/null +++ b/auth_totp_enforce/readme/USAGE.md @@ -0,0 +1,7 @@ +Once installed, any enforced internal user without an authenticator app +configured will, on their next login: + +1. Enter their login and password as usual. +2. Be redirected to a mandatory setup page showing a QR code and secret key. +3. Scan the code with an authenticator app and enter the generated code. +4. Be handed over to the standard two-factor step to complete the login. diff --git a/auth_totp_enforce/security/res_groups.xml b/auth_totp_enforce/security/res_groups.xml new file mode 100644 index 0000000000..80b17f4f29 --- /dev/null +++ b/auth_totp_enforce/security/res_groups.xml @@ -0,0 +1,10 @@ + + + + Exempt from 2FA enforcement + + Members of this group are NOT forced to configure MFA at login. + + diff --git a/auth_totp_enforce/static/description/index.html b/auth_totp_enforce/static/description/index.html new file mode 100644 index 0000000000..5c807c6e82 --- /dev/null +++ b/auth_totp_enforce/static/description/index.html @@ -0,0 +1,447 @@ + + + + + +Enforce Two-Factor Authentication + + + +
+

Enforce Two-Factor Authentication

+ + +

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

+

This module forces internal users to configure two-factor authentication +(a TOTP authenticator app) before they log in.

+

Enforcement works like the password expiry flow of +password_security: nobody is logged out when the module is +installed, but on their next login users are held in a mandatory step +and cannot reach the backend until they have enabled an authenticator +app.

+

Members of the Exempt from 2FA enforcement group are never forced. +Portal and public users are out of scope at the moment.

+

Table of contents

+ +
+

Usage

+

Once installed, any enforced internal user without an authenticator app +configured will, on their next login:

+
    +
  1. Enter their login and password as usual.
  2. +
  3. Be redirected to a mandatory setup page showing a QR code and secret +key.
  4. +
  5. Scan the code with an authenticator app and enter the generated code.
  6. +
  7. Be handed over to the standard two-factor step to complete the login.
  8. +
+
+
+

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

+
    +
  • ForgeFlow
  • +
+
+
+

Contributors

+ +
+
+

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_totp_enforce/tests/__init__.py b/auth_totp_enforce/tests/__init__.py new file mode 100644 index 0000000000..eb7a2fd2cc --- /dev/null +++ b/auth_totp_enforce/tests/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2026 ForgeFlow S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_enforce +from . import test_enforce_flow diff --git a/auth_totp_enforce/tests/test_enforce.py b/auth_totp_enforce/tests/test_enforce.py new file mode 100644 index 0000000000..fd23e84efd --- /dev/null +++ b/auth_totp_enforce/tests/test_enforce.py @@ -0,0 +1,54 @@ +# Copyright 2026 ForgeFlow S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import base64 +import time + +from odoo.tests import TransactionCase, new_test_user, tagged + +from odoo.addons.auth_totp.models.totp import TIMESTEP, hotp + + +@tagged("post_install", "-at_install") +class TestEnforce(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.internal_user = new_test_user( + cls.env, + login="totp_internal", + password="totp_internal_pwd", + ) + + def _valid_code(self, secret): + key = base64.b32decode(secret.replace(" ", "").upper()) + return hotp(key, int(time.time() / TIMESTEP)) + + def test_setup_with_valid_code_enables_totp(self): + secret = self.internal_user._generate_totp_setup_secret() + self.assertFalse(self.internal_user.totp_enabled) + code = self._valid_code(secret) + enforce = self.internal_user.sudo()._totp_enforce_setup(secret, code) + self.assertTrue(enforce) + self.assertTrue(self.internal_user.totp_enabled) + + def test_setup_with_wrong_code_is_rejected(self): + secret = self.internal_user._generate_totp_setup_secret() + # A code from a far-away time window is outside the match window + key = base64.b32decode(secret.replace(" ", "").upper()) + wrong_code = hotp(key, int(time.time() / TIMESTEP) - 10000) + enforce = self.internal_user.sudo()._totp_enforce_setup(secret, wrong_code) + self.assertFalse(enforce) + self.assertFalse(self.internal_user.totp_enabled) + + def test_setup_with_non_numeric_code_is_rejected(self): + secret = self.internal_user._generate_totp_setup_secret() + enforce = self.internal_user.sudo()._totp_enforce_setup(secret, "not-a-code") + self.assertFalse(enforce) + self.assertFalse(self.internal_user.totp_enabled) + + def test_generated_secret_is_valid_base32(self): + secret = self.internal_user._generate_totp_setup_secret() + # Must decode without error and yield the expected key size + key = base64.b32decode(secret.replace(" ", "").upper()) + self.assertEqual(len(key) * 8, 160) diff --git a/auth_totp_enforce/tests/test_enforce_flow.py b/auth_totp_enforce/tests/test_enforce_flow.py new file mode 100644 index 0000000000..cf042e0ce1 --- /dev/null +++ b/auth_totp_enforce/tests/test_enforce_flow.py @@ -0,0 +1,91 @@ +# Copyright 2026 ForgeFlow S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import base64 +import json +import re +import time + +from odoo.tests import HttpCase, get_db_name, new_test_user, tagged +from odoo.tools import mute_logger + +from odoo.addons.auth_totp.models.totp import TIMESTEP, hotp + + +@tagged("post_install", "-at_install") +class TestEnforceFlow(HttpCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.enforced = new_test_user( + cls.env, + login="flow_enforced", + password="flow_enforced_pwd", + tz="UTC", + ) + cls.exempt = new_test_user( + cls.env, + login="flow_exempt", + password="flow_exempt_pwd", + tz="UTC", + groups="base.group_user,auth_totp_enforce.group_mfa_exempt", + ) + + def _password_login(self, login, password): + # POST to the JSON auth endpoint and return the ``result`` dict + payload = { + "jsonrpc": "2.0", + "method": "call", + "id": 0, + "params": {"db": get_db_name(), "login": login, "password": password}, + } + response = self.url_open( + "/web/session/authenticate", + data=json.dumps(payload), + headers={"Content-Type": "application/json"}, + ) + return response.json()["result"] + + @mute_logger("odoo.http") + def test_enforced_user_is_held_before_backend(self): + # Enforced user must not be fully logged in until 2FA is set up + result = self._password_login("flow_enforced", "flow_enforced_pwd") + self.assertIsNone(result["uid"]) + + def test_exempt_user_logs_in_directly(self): + # Exempt user must reach the backend without any 2FA step + result = self._password_login("flow_exempt", "flow_exempt_pwd") + self.assertEqual(result["uid"], self.exempt.id) + + @mute_logger("odoo.http") + def test_setup_page_activates_and_hands_off_to_standard_step(self): + # Password step -> partial session (blocked) + u_id = self._password_login("flow_enforced", "flow_enforced_pwd")["uid"] + self.assertIsNone(u_id) + # The mandatory setup page is served on the partial session + page = self.url_open("/web/login/totp/setup") + self.assertEqual(page.status_code, 200) + self.assertIn("Set up MFA", page.text) + # Read the generated secret and CSRF token off the page + sec = re.search(r"]*>\s*([A-Z2-7 ]+?)\s*", page.text).group(1) + csrf = re.search(r'name="csrf_token"[^>]*value="([^"]+)"', page.text).group(1) + # Submit a valid code computed from that secret + key = base64.b32decode(sec.replace(" ", "")) + code = hotp(key, int(time.time() / TIMESTEP)) + response_data = {"totp_token": str(code), "csrf_token": csrf} + response = self.url_open("/web/login/totp/setup", data=response_data) + self.assertEqual(response.status_code, 200) + # The secret is now stored on the user + self.enforced.invalidate_recordset(["totp_secret", "totp_enabled"]) + self.assertTrue(self.enforced.totp_enabled) + # We were redirected to standard MFA (still not logged in) + self.assertNotIn("Set up MFA", response.text) + self.assertIn("Authentication Code", response.text) + + def test_setup_page_redirects_when_no_partial_session(self): + # Setup URL without a pre-authenticated session bounce back to login page + response = self.url_open("/web/login/totp/setup", allow_redirects=False) + self.assertIn(response.status_code, (302, 303)) + # Location may be absolute (http://host/web/login) or relative. + location = response.headers.get("Location", "") + self.assertTrue(location.endswith("/web/login")) diff --git a/auth_totp_enforce/views/templates.xml b/auth_totp_enforce/views/templates.xml new file mode 100644 index 0000000000..b3b15d9046 --- /dev/null +++ b/auth_totp_enforce/views/templates.xml @@ -0,0 +1,89 @@ + + + + From 15583efaed4cbb567c13de2aed646258713c1bf2 Mon Sep 17 00:00:00 2001 From: Lois Rilo Date: Wed, 5 Aug 2026 15:59:43 +0200 Subject: [PATCH 2/4] [IMP] auth_totp_enforce: add admin and root by default in demo Otherwise, MFA will be requested in all runboat instances and that can be annoying. --- auth_totp_enforce/__manifest__.py | 3 +++ auth_totp_enforce/demo/res_groups_demo.xml | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 auth_totp_enforce/demo/res_groups_demo.xml diff --git a/auth_totp_enforce/__manifest__.py b/auth_totp_enforce/__manifest__.py index f28292e851..c447decb17 100644 --- a/auth_totp_enforce/__manifest__.py +++ b/auth_totp_enforce/__manifest__.py @@ -14,5 +14,8 @@ "security/res_groups.xml", "views/templates.xml", ], + "demo": [ + "demo/res_groups_demo.xml", + ], "installable": True, } diff --git a/auth_totp_enforce/demo/res_groups_demo.xml b/auth_totp_enforce/demo/res_groups_demo.xml new file mode 100644 index 0000000000..8295542994 --- /dev/null +++ b/auth_totp_enforce/demo/res_groups_demo.xml @@ -0,0 +1,9 @@ + + + + + + From 370a149a2053afd64bb678a162021202d488f355 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Wed, 5 Aug 2026 16:53:13 +0000 Subject: [PATCH 3/4] [UPD] Update auth_totp_enforce.pot --- auth_totp_enforce/i18n/auth_totp_enforce.pot | 92 ++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 auth_totp_enforce/i18n/auth_totp_enforce.pot diff --git a/auth_totp_enforce/i18n/auth_totp_enforce.pot b/auth_totp_enforce/i18n/auth_totp_enforce.pot new file mode 100644 index 0000000000..16c7a183fc --- /dev/null +++ b/auth_totp_enforce/i18n/auth_totp_enforce.pot @@ -0,0 +1,92 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * auth_totp_enforce +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.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_totp_enforce +#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form +msgid "Activate & Log in" +msgstr "" + +#. module: auth_totp_enforce +#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form +msgid "Cancel" +msgstr "" + +#. module: auth_totp_enforce +#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form +msgid "Cannot scan it?" +msgstr "" + +#. module: auth_totp_enforce +#: model:res.groups,name:auth_totp_enforce.group_mfa_exempt +msgid "Exempt from 2FA enforcement" +msgstr "" + +#. module: auth_totp_enforce +#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form +msgid "" +"Install an authenticator app on your device\n" +" (Authy, Google Authenticator, Microsoft Authenticator...)." +msgstr "" + +#. module: auth_totp_enforce +#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form +msgid "Look for an \"Add an account\" button." +msgstr "" + +#. module: auth_totp_enforce +#: model:res.groups,comment:auth_totp_enforce.group_mfa_exempt +msgid "Members of this group are NOT forced to configure MFA at login." +msgstr "" + +#. module: auth_totp_enforce +#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form +msgid "QR Code" +msgstr "" + +#. module: auth_totp_enforce +#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form +msgid "Scan the QR code below, or enter the key manually." +msgstr "" + +#. module: auth_totp_enforce +#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form +msgid "Set up MFA" +msgstr "" + +#. module: auth_totp_enforce +#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form +msgid "Two-factor authentication is required for your account." +msgstr "" + +#. module: auth_totp_enforce +#: model:ir.model,name:auth_totp_enforce.model_res_users +msgid "User" +msgstr "" + +#. module: auth_totp_enforce +#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form +msgid "Verification Code" +msgstr "" + +#. module: auth_totp_enforce +#. odoo-python +#: code:addons/auth_totp_enforce/controllers/home.py:0 +msgid "Verification failed, please double-check the 6-digit code" +msgstr "" + +#. module: auth_totp_enforce +#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form +msgid "e.g. 123456" +msgstr "" From 8eb2e84580493d2b468cbaef492a28c670d6d442 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 5 Aug 2026 16:57:11 +0000 Subject: [PATCH 4/4] [BOT] post-merge updates --- README.md | 1 + auth_totp_enforce/README.rst | 12 +++++--- auth_totp_enforce/static/description/icon.png | Bin 0 -> 10254 bytes .../static/description/index.html | 28 +++++++++++------- setup/_metapackage/pyproject.toml | 3 +- 5 files changed, 28 insertions(+), 16 deletions(-) create mode 100644 auth_totp_enforce/static/description/icon.png diff --git a/README.md b/README.md index e489d9cc2b..5eaf8fe73a 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ addon | version | maintainers | summary [auth_saml](auth_saml/) | 18.0.1.1.3 | vincent-hatakeyama | SAML2 Authentication [auth_session_timeout](auth_session_timeout/) | 18.0.1.0.0 | | This module disable all inactive sessions since a given delay [auth_signup_verify_email](auth_signup_verify_email/) | 18.0.1.0.0 | | Force uninvited users to use a good email for signup +[auth_totp_enforce](auth_totp_enforce/) | 18.0.1.0.0 | | Force users to set up an authenticator app before they can log in. [auth_user_case_insensitive](auth_user_case_insensitive/) | 18.0.1.0.0 | | Makes the user login field case insensitive [base_group_subcontractor](base_group_subcontractor/) | 18.0.1.0.0 | pedrobaeza | Extra user type group for restricted access to backend [base_user_empty_password](base_user_empty_password/) | 18.0.1.0.0 | grindtildeath | Allows to empty password of users diff --git a/auth_totp_enforce/README.rst b/auth_totp_enforce/README.rst index 37b4b0ceb0..dcd3d07f44 100644 --- a/auth_totp_enforce/README.rst +++ b/auth_totp_enforce/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ================================= Enforce Two-Factor Authentication ================================= @@ -7,13 +11,13 @@ Enforce Two-Factor Authentication !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:d7a9647142270eff465347946c269fc042be4388b9f9d22a0d3ff1239f105dae + !! source digest: sha256:559d09d726e6723855e6e9933788c2145fe668a22a6b6f4be309095f69e8e22c !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |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 +.. |badge2| image:: https://img.shields.io/badge/license-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 @@ -78,9 +82,9 @@ Authors Contributors ------------ -- ForgeFlow S.L. +- ForgeFlow S.L. - - Laura Cazorla + - Laura Cazorla Maintainers ----------- diff --git a/auth_totp_enforce/static/description/icon.png b/auth_totp_enforce/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1dcc49c24f364e9adf0afbc6fc0bac6dbecdeb11 GIT binary patch literal 10254 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 -Enforce Two-Factor Authentication +README.rst -
-

Enforce Two-Factor Authentication

+
+ + +Odoo Community Association + +
+

Enforce Two-Factor Authentication

-

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

+

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

This module forces internal users to configure two-factor authentication (a TOTP authenticator app) before they log in.

Enforcement works like the password expiry flow of @@ -393,7 +398,7 @@

Enforce Two-Factor Authentication

-

Usage

+

Usage

Once installed, any enforced internal user without an authenticator app configured will, on their next login:

    @@ -405,7 +410,7 @@

    Usage

-

Bug Tracker

+

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 @@ -413,15 +418,15 @@

Bug Tracker

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

-

Credits

+

Credits

-

Authors

+

Authors

  • ForgeFlow
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -443,5 +448,6 @@

Maintainers

+
diff --git a/setup/_metapackage/pyproject.toml b/setup/_metapackage/pyproject.toml index 7de7d3f024..d23c738a15 100644 --- a/setup/_metapackage/pyproject.toml +++ b/setup/_metapackage/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "odoo-addons-oca-server-auth" -version = "18.0.20260704.0" +version = "18.0.20260805.0" dependencies = [ "odoo-addon-auth_admin_passkey==18.0.*", "odoo-addon-auth_api_key==18.0.*", @@ -16,6 +16,7 @@ dependencies = [ "odoo-addon-auth_saml==18.0.*", "odoo-addon-auth_session_timeout==18.0.*", "odoo-addon-auth_signup_verify_email==18.0.*", + "odoo-addon-auth_totp_enforce==18.0.*", "odoo-addon-auth_user_case_insensitive==18.0.*", "odoo-addon-base_group_subcontractor==18.0.*", "odoo-addon-base_user_empty_password==18.0.*",