forked from OCA/server-auth
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[TST] auth_sms[_auth_signup]: make tests great again
- Loading branch information
Showing
12 changed files
with
216 additions
and
172 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,2 @@ | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). | ||
from . import common | ||
from . import test_auth_sms |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,29 @@ | ||
# Copyright 2019 Therp BV <https://therp.nl> | ||
# Copyright 2019-2025 Therp BV <https://therp.nl> | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). | ||
from contextlib import contextmanager | ||
from functools import partial | ||
from odoo.tests import HttpCase, new_test_user | ||
|
||
from werkzeug.test import EnvironBuilder | ||
from werkzeug.wrappers import Request as WerkzeugRequest | ||
|
||
from odoo import http | ||
from odoo.tests.common import TransactionCase | ||
class HttpCaseSMS(HttpCase): | ||
@classmethod | ||
def setUpClass(cls): | ||
super().setUpClass() | ||
cls.admin_user = cls.env.ref("base.user_admin") | ||
cls.username = "dportier" | ||
cls.password = "!asdQWE12345_3" # strong password | ||
cls.demo_user = cls._create_user() | ||
cls.code = None | ||
cls.secret = None | ||
|
||
|
||
class Common(TransactionCase): | ||
def setUp(self): | ||
super(Common, self).setUp() | ||
self.session = http.root.session_store.new() | ||
self.env["res.users"]._register_hook() | ||
self.demo_user = self.env.ref("auth_sms.demo_user") | ||
self.env["auth_sms.code"].search([]).unlink() | ||
|
||
@contextmanager | ||
def _request(self, path, method="POST", data=None): | ||
"""yield request, endpoint for given http request data""" | ||
werkzeug_env = EnvironBuilder( | ||
method=method, | ||
path=path, | ||
data=data, | ||
headers=[("cookie", "session_id=%s" % self.session.sid)], | ||
environ_base={ | ||
"HTTP_HOST": "localhost", | ||
"REMOTE_ADDR": "127.0.0.1", | ||
}, | ||
).get_environ() | ||
werkzeug_request = WerkzeugRequest(werkzeug_env) | ||
http.root.setup_session(werkzeug_request) | ||
werkzeug_request.session.db = self.env.cr.dbname | ||
http.root.setup_db(werkzeug_request) | ||
http.root.setup_lang(werkzeug_request) | ||
|
||
request = http.HttpRequest(werkzeug_request) | ||
request._env = self.env | ||
with request: | ||
routing_map = self.env["ir.http"].routing_map() | ||
endpoint, dummy = routing_map.bind_to_environ(werkzeug_env).match( | ||
return_rule=False, | ||
) | ||
yield request, partial(endpoint, **request.params) | ||
@classmethod | ||
def _create_user(cls): | ||
"""Create auth_sms_enabled user.""" | ||
return new_test_user( | ||
cls.env, | ||
login=cls.username, | ||
context={"no_reset_password": True}, | ||
password=cls.password, | ||
name="Auth SMS test user", | ||
mobile="0123456789", | ||
email="auth_sms_test_user@yourcompany.com", | ||
auth_sms_enabled=True, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,88 +1,107 @@ | ||
# Copyright 2019 Therp BV <https://therp.nl> | ||
# Copyright 2019-2025 Therp BV <https://therp.nl> | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). | ||
from unittest.mock import patch | ||
from unittest import mock | ||
|
||
from lxml.html import document_fromstring | ||
|
||
from odoo import http | ||
from odoo.tests import HOST, Opener, get_db_name, tagged | ||
|
||
from .common import HttpCaseSMS | ||
|
||
from .common import Common | ||
_module_ns = "odoo.addons.auth_sms" | ||
_requests_class = _module_ns + ".models.sms_provider.requests" | ||
_users_class = _module_ns + ".models.res_users.ResUsers" | ||
|
||
|
||
class TestAuthSms(Common): | ||
@tagged("post_install", "-at_install") | ||
class TestAuthSms(HttpCaseSMS): | ||
def test_auth_sms_login_no_2fa(self): | ||
# admin doesn't have sms verification turned on | ||
with self._request( | ||
"/web/login", | ||
method="POST", | ||
data={ | ||
"login": self.env.user.login, | ||
"password": self.env.user.login, | ||
}, | ||
) as (request, endpoint): | ||
response = endpoint() | ||
self.assertFalse(response.template) | ||
response = self._login_user(self.admin_user.login, self.admin_user.login) | ||
self.assertEqual(response.request.path_url, "/web") | ||
self.assertEqual(response.status_code, 200) | ||
|
||
def test_auth_sms_login_no_error(self): | ||
# first request: login | ||
response = self._mock_login_user(self.demo_user.login, self.password) | ||
self.assertEqual(response.request.path_url, "/web/login") | ||
# fill the correct code | ||
response = self._enter_code(self.code) | ||
self.assertEqual(response.request.path_url, "/web") | ||
|
||
def test_auth_sms_login(self): | ||
# first request: login | ||
with self._request( | ||
"/web/login", | ||
data={ | ||
"login": self.demo_user.login, | ||
"password": self.demo_user.login, | ||
}, | ||
) as (request, endpoint), patch( | ||
"odoo.addons.auth_sms.models.sms_provider.requests.post", | ||
) as mock_request_post: | ||
response = self._mock_login_user(self.demo_user.login, self.password) | ||
self.assertEqual(response.request.path_url, "/web/login") | ||
# then fill in a wrong code | ||
response = self._enter_code("wrong code") | ||
self.assertEqual(response.request.path_url, "/auth_sms/code") | ||
# fill the correct code | ||
response = self._enter_code(self.code) | ||
self.assertEqual(response.request.path_url, "/web") | ||
|
||
def test_auth_sms_rate_limit(self): | ||
"""Request codes until we hit the rate limit.""" | ||
# Make sure there are no codes left. | ||
self.env["auth_sms.code"].search([("user_id", "=", self.demo_user.id)]).unlink() | ||
for _i in range(10): | ||
response = self._mock_login_user(self.demo_user.login, self.password) | ||
self.assertEqual(response.request.path_url, "/web/login") | ||
# 10th time should result in error (assuming default limit): | ||
response = self._mock_login_user(self.demo_user.login, self.password) | ||
self.assertEqual(response.request.path_url, "/web/login") | ||
self.assertEqual(response.status_code, 200) | ||
self.assertIn( | ||
"Rate limit for SMS exceeded", | ||
response.text, | ||
) | ||
|
||
def _mock_login_user(self, login, password): | ||
"""Login as a specific user (assume password is same as login).""" | ||
with mock.patch(_requests_class + ".post") as mock_request_post: | ||
mock_request_post.return_value.json.return_value = { | ||
"originator": "originator", | ||
} | ||
response = endpoint() | ||
self.assertEqual(response.template, "auth_sms.template_code") | ||
self.assertTrue(request.session["auth_sms.password"]) | ||
mock_request_post.assert_called_once() | ||
http.root.session_store.save(request.session) | ||
response = self._login_user(login, password) | ||
# retrieve the code to use from the mocked call | ||
self.code = mock_request_post.mock_calls[0][2]["data"]["body"] | ||
# retrieve the secret from the response, if present. | ||
document = document_fromstring(response.content) | ||
secret_inputs = document.xpath("//input[@name='secret']") | ||
self.secret = secret_inputs[0].get("value") if secret_inputs else None | ||
return response | ||
|
||
# then fill in a wrong code | ||
with self._request( | ||
"/auth_sms/code", | ||
data={ | ||
"secret": response.qcontext["secret"], | ||
"user_login": response.qcontext["login"], | ||
"password": "wrong code", | ||
}, | ||
) as (request, endpoint): | ||
response = endpoint() | ||
self.assertEqual(response.template, "auth_sms.template_code") | ||
self.assertTrue(response.qcontext["error"]) | ||
def _login_user(self, login, password): | ||
"""Login as a specific user.""" | ||
# Code largely taken from password_security/tests/test_login.py. | ||
# session must be part of self, because of csrf_token method. | ||
self.session = http.root.session_store.new() | ||
self.opener = Opener(self.env.cr) | ||
self.opener.cookies.set("session_id", self.session.sid, domain=HOST, path="/") | ||
with mock.patch("odoo.http.db_filter") as db_filter: | ||
db_filter.side_effect = lambda dbs, host=None: [get_db_name()] | ||
# The response returned here is not the odoo.http.Response class, | ||
# but the requests.Response. | ||
response = self.url_open( | ||
"/web/login", | ||
data={ | ||
"login": login, | ||
"password": password, | ||
"csrf_token": http.Request.csrf_token(self), | ||
}, | ||
) | ||
response.raise_for_status() | ||
return response | ||
|
||
# fill the correct code | ||
with self._request( | ||
def _enter_code(self, code): | ||
"""Enter code from sms (wrong or correct).""" | ||
return self.url_open( | ||
"/auth_sms/code", | ||
data={ | ||
"secret": response.qcontext["secret"], | ||
"user_login": response.qcontext["login"], | ||
"password": mock_request_post.mock_calls[0][2]["data"]["body"], | ||
"secret": self.secret, | ||
"user_login": self.demo_user.login, | ||
"password": code, | ||
"csrf_token": http.Request.csrf_token(self), | ||
}, | ||
) as (request, endpoint): | ||
response = endpoint() | ||
self.assertFalse(response.is_qweb) | ||
self.assertTrue(response.data) | ||
|
||
def test_auth_sms_rate_limit(self): | ||
# request codes until we hit the rate limit | ||
with self._request( | ||
"/web/login", | ||
data={ | ||
"login": self.demo_user.login, | ||
"password": self.demo_user.login, | ||
}, | ||
) as (request, endpoint), patch( | ||
"odoo.addons.auth_sms.models.sms_provider.requests.post", | ||
) as mock_request_post: | ||
mock_request_post.return_value.json.return_value = { | ||
"originator": "originator", | ||
} | ||
for _i in range(9): | ||
response = endpoint() | ||
self.assertNotIn("error", response.qcontext) | ||
response = endpoint() | ||
self.assertTrue(response.qcontext["error"]) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.