platform-core/tests/unit/auth/test_mfaService.py
ValueOn AG 08fa70e4e0
All checks were successful
Deploy Plattform-Core (Int) / test (push) Successful in 1m4s
Deploy Plattform-Core (Int) / deploy (push) Successful in 42s
security and mfa
2026-06-03 23:21:29 +02:00

154 lines
5.4 KiB
Python

# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Unit tests for modules.auth.mfaService.
Tests TOTP generation, verification, encryption round-trip, and the
three-rule MFA obligation resolver.
"""
import pytest
from unittest.mock import patch, MagicMock
import pyotp
from modules.auth.mfaService import (
_generateSecret,
_buildTotp,
generateSetup,
confirmSetup,
verifyCode,
isMfaRequired,
_isMfaRequireAdminsEnabled,
)
class TestTotpBasics:
def test_generateSecret_returns_base32(self):
secret = _generateSecret()
assert isinstance(secret, str)
assert len(secret) >= 16
def test_buildTotp_generates_valid_code(self):
secret = _generateSecret()
totp = _buildTotp(secret)
code = totp.now()
assert len(code) == 6
assert code.isdigit()
def test_verifyCode_accepts_current_code(self):
secret = _generateSecret()
totp = _buildTotp(secret)
code = totp.now()
encrypted = f"FAKE_ENC:{secret}"
with patch("modules.auth.mfaService._decryptSecret", return_value=secret):
assert verifyCode(encrypted, code) is True
def test_verifyCode_rejects_wrong_code(self):
secret = _generateSecret()
encrypted = f"FAKE_ENC:{secret}"
with patch("modules.auth.mfaService._decryptSecret", return_value=secret):
assert verifyCode(encrypted, "000000") is False
class TestGenerateSetup:
@patch("modules.auth.mfaService._encryptSecret", return_value="ENC_SECRET")
def test_returns_uri_and_encrypted_secret(self, _mock_enc):
result = generateSetup(userId="u1", username="testuser")
assert "encryptedSecret" in result
assert "provisioningUri" in result
assert result["encryptedSecret"] == "ENC_SECRET"
assert "otpauth://totp/" in result["provisioningUri"]
assert "PowerOn" in result["provisioningUri"]
class TestConfirmSetup:
def test_confirmSetup_with_valid_code(self):
secret = _generateSecret()
totp = _buildTotp(secret)
code = totp.now()
with patch("modules.auth.mfaService._decryptSecret", return_value=secret):
assert confirmSetup("ENC", code) is True
def test_confirmSetup_with_invalid_code(self):
secret = _generateSecret()
with patch("modules.auth.mfaService._decryptSecret", return_value=secret):
assert confirmSetup("ENC", "999999") is False
def test_confirmSetup_handles_decryption_error(self):
with patch("modules.auth.mfaService._decryptSecret", side_effect=Exception("decrypt error")):
assert confirmSetup("BAD_ENC", "123456") is False
class TestIsMfaRequired:
def _makeUser(self, mfaEnabled=False, isSysAdmin=False, isPlatformAdmin=False):
u = MagicMock()
u.mfaEnabled = mfaEnabled
u.isSysAdmin = isSysAdmin
u.isPlatformAdmin = isPlatformAdmin
return u
def _makeMandate(self, mfaRequired=False):
m = MagicMock()
m.mfaRequired = mfaRequired
return m
def test_mfaEnabled_user_always_required(self):
user = self._makeUser(mfaEnabled=True)
assert isMfaRequired(user) is True
@patch("modules.auth.mfaService._isMfaRequireAdminsEnabled", return_value=True)
def test_sysadmin_with_config_key(self, _mock):
user = self._makeUser(isSysAdmin=True)
assert isMfaRequired(user) is True
@patch("modules.auth.mfaService._isMfaRequireAdminsEnabled", return_value=True)
def test_platformadmin_with_config_key(self, _mock):
user = self._makeUser(isPlatformAdmin=True)
assert isMfaRequired(user) is True
@patch("modules.auth.mfaService._isMfaRequireAdminsEnabled", return_value=False)
def test_admin_without_config_key_not_required(self, _mock):
user = self._makeUser(isSysAdmin=True)
assert isMfaRequired(user) is False
@patch("modules.auth.mfaService._isMfaRequireAdminsEnabled", return_value=False)
def test_mandate_with_mfaRequired(self, _mock):
user = self._makeUser()
mandate = self._makeMandate(mfaRequired=True)
assert isMfaRequired(user, mandates=[mandate]) is True
@patch("modules.auth.mfaService._isMfaRequireAdminsEnabled", return_value=False)
def test_mandate_without_mfaRequired(self, _mock):
user = self._makeUser()
mandate = self._makeMandate(mfaRequired=False)
assert isMfaRequired(user, mandates=[mandate]) is False
@patch("modules.auth.mfaService._isMfaRequireAdminsEnabled", return_value=False)
def test_regular_user_no_mandate_not_required(self, _mock):
user = self._makeUser()
assert isMfaRequired(user) is False
class TestConfigKey:
@patch("modules.auth.mfaService.APP_CONFIG")
def test_config_true(self, mock_cfg):
mock_cfg.get.return_value = "true"
assert _isMfaRequireAdminsEnabled() is True
@patch("modules.auth.mfaService.APP_CONFIG")
def test_config_false(self, mock_cfg):
mock_cfg.get.return_value = "false"
assert _isMfaRequireAdminsEnabled() is False
@patch("modules.auth.mfaService.APP_CONFIG")
def test_config_empty(self, mock_cfg):
mock_cfg.get.return_value = ""
assert _isMfaRequireAdminsEnabled() is False
@patch("modules.auth.mfaService.APP_CONFIG")
def test_config_one(self, mock_cfg):
mock_cfg.get.return_value = "1"
assert _isMfaRequireAdminsEnabled() is True