Skip to content

Commit

Permalink
Start impl security tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nlachat-compassion committed Oct 18, 2024
1 parent 35809e6 commit a4c5324
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 3 deletions.
9 changes: 6 additions & 3 deletions auth_external/controllers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,28 @@
_logger = logging.getLogger(__name__)



AUTH_LOGIN_ROUTE = "/auth/login"
class AuthController(Controller):

@route(
route="/auth/login",
route=AUTH_LOGIN_ROUTE,
auth="none",
type="json",
methods=["POST"],
csrf=False,
cors="*",
)
def login(self):
username = request.jsonrequest["username"]
login = request.jsonrequest["login"]
password = request.jsonrequest["password"]
totp = request.jsonrequest["totp"]

db = request.env.cr.dbname
res_users = registry(db)["res.users"]

user_id = res_users.authenticate(
db, username, password, {"totp": totp, "interactive": False}
db, login, password, {"totp": totp, "interactive": False}
)

user = request.env["res.users"].browse(int(user_id))
Expand Down
4 changes: 4 additions & 0 deletions auth_external/tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Running the tests
```sh
odoo/odoo-bin -c etc/dev_t1486.conf -u auth_external -i auth_external --test-tags=auth_external --stop-after-init
```
1 change: 1 addition & 0 deletions auth_external/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_auth_controller
109 changes: 109 additions & 0 deletions auth_external/tests/test_auth_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import json
from odoo.tests.common import HttpCase
from ..controllers.auth import AUTH_LOGIN_ROUTE
from http import HTTPStatus
from requests import Response


class TestAuthController(HttpCase):
TEST_USER_NORMAL = {
"login": "user_normal",
"password": "password_normal",
}

TEST_USER_2FA_CREATE = {
"login": "user_2fa",
"password": "password_2fa",
"totp_secret": "totp_secret",
"totp_enabled": True
}

TEST_USER_2FA = {
"login": TEST_USER_2FA_CREATE["login"],
"password": TEST_USER_2FA_CREATE["password"],
"totp": "123456"
}

def setUp(self, *args, **kwargs):
super(TestAuthController, self).setUp(*args, **kwargs)
res_users = self.env["res.users"]
self.test_user_normal = res_users.create(
TestAuthController.TEST_USER_NORMAL
)
self.test_user_2fa = res_users.create(
TestAuthController.TEST_USER_2FA_CREATE
)

def json_post(self, route: str, data: dict) -> Response:
JSON_HEADERS = {"Content-Type": "application/json"}
return self.url_open(route, data=json.dumps(data), headers=JSON_HEADERS)

def assert_access_denied(self, response: Response) -> None:
self.assertEqual(response.status_code, HTTPStatus.OK)
response_data = json.loads(response.text)
self.assertEqual(
response_data["error"]["data"]["name"], "odoo.exceptions.AccessDenied"
)

def should_deny_access(self, login_data: dict) -> None:
response = self.json_post(AUTH_LOGIN_ROUTE, login_data)
self.assert_access_denied(response)

def test_login_should_fail_with_invalid_user(self):
data = {
"login": "This username should not exist",
# and if it does, it's a really weird edge case
"password": "password",
"totp": "123456",
}
self.should_deny_access(data)

def test_login_should_succeed_for_normal_user(self):
response = self.json_post(AUTH_LOGIN_ROUTE, TestAuthController.TEST_USER_NORMAL)
print(response.text)
self.assertTrue(False)

def test_access_denied_incorrect_password(self):
"""
An attacker is denied access to a normal user account when providing an incorrect password
"""
data_incorrect_password = TestAuthController.TEST_USER_NORMAL.copy()
data_incorrect_password["password"] = "incorrect_password"
self.should_deny_access(data_incorrect_password)

def test_access_denied_2fa_correct_password_absent_totp(self):
"""
An attacker is denied access to a 2fa user account when not providing any totp
"""
data = TestAuthController.TEST_USER_2FA.copy()
del data["totp"]
self.should_deny_access(data)

def test_access_denied_2fa_correct_password_incorrect_totp(self):
"""
An attacker is denied access to a 2fa user account when providing a correct password but incorrect totp
"""
data_incorrect_totp = TestAuthController.TEST_USER_2FA.copy()
data_incorrect_totp["totp"] = "123456" # 1/1'000'000 chance that this is the correct totp and that the test fails
self.should_deny_access(data_incorrect_totp)

def test_accses_denied_2fa_incorrect_password_correct_totp(self):
"""
An attacker is denied access to a 2fa user account when providing an incorrect password but correct totp
"""
data = TestAuthController.TEST_USER_2FA.copy()
data["password"] = "incorrect_password"
data["totp"] = None # TODO

"""
We assume the attacker knows the username of the victim
TO TEST:
-
-
-
- An attacker is denied access to a normal user account when providing a totp
- An attacker cannot successfully submit a forged access token
- An attacker cannot successfully submit a forged refresh token
- An attacker cannot successfully reuse an expired access token
- An attacker cannot successfully reuse an expired refresh token
"""

0 comments on commit a4c5324

Please sign in to comment.