296 lines
11 KiB
Python
296 lines
11 KiB
Python
|
|
"""Unit tests for MFA policy models."""
|
||
|
|
import pytest
|
||
|
|
from datetime import datetime, timezone, timedelta
|
||
|
|
from gatehouse_app.models import (
|
||
|
|
User,
|
||
|
|
Organization,
|
||
|
|
OrganizationMember,
|
||
|
|
OrganizationSecurityPolicy,
|
||
|
|
UserSecurityPolicy,
|
||
|
|
MfaPolicyCompliance,
|
||
|
|
Session,
|
||
|
|
)
|
||
|
|
from gatehouse_app.utils.constants import (
|
||
|
|
UserStatus,
|
||
|
|
MfaPolicyMode,
|
||
|
|
MfaComplianceStatus,
|
||
|
|
MfaRequirementOverride,
|
||
|
|
SessionStatus,
|
||
|
|
OrganizationRole,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.unit
|
||
|
|
class TestOrganizationSecurityPolicyModel:
|
||
|
|
"""Tests for OrganizationSecurityPolicy model."""
|
||
|
|
|
||
|
|
def test_create_org_security_policy(self, db, test_organization):
|
||
|
|
"""Test creating an organization security policy."""
|
||
|
|
policy = OrganizationSecurityPolicy(
|
||
|
|
organization_id=test_organization.id,
|
||
|
|
mfa_policy_mode=MfaPolicyMode.OPTIONAL,
|
||
|
|
mfa_grace_period_days=14,
|
||
|
|
notify_days_before=7,
|
||
|
|
)
|
||
|
|
policy.save()
|
||
|
|
|
||
|
|
assert policy.id is not None
|
||
|
|
assert policy.organization_id == test_organization.id
|
||
|
|
assert policy.mfa_policy_mode == MfaPolicyMode.OPTIONAL
|
||
|
|
assert policy.mfa_grace_period_days == 14
|
||
|
|
assert policy.notify_days_before == 7
|
||
|
|
assert policy.policy_version == 1
|
||
|
|
assert policy.created_at is not None
|
||
|
|
|
||
|
|
def test_org_security_policy_to_dict(self, db, test_organization):
|
||
|
|
"""Test organization security policy to_dict method."""
|
||
|
|
policy = OrganizationSecurityPolicy(
|
||
|
|
organization_id=test_organization.id,
|
||
|
|
mfa_policy_mode=MfaPolicyMode.REQUIRE_TOTP_OR_WEBAUTHN,
|
||
|
|
mfa_grace_period_days=7,
|
||
|
|
notify_days_before=3,
|
||
|
|
)
|
||
|
|
policy.save()
|
||
|
|
|
||
|
|
policy_dict = policy.to_dict()
|
||
|
|
|
||
|
|
assert "id" in policy_dict
|
||
|
|
assert "organization_id" in policy_dict
|
||
|
|
assert policy_dict["organization_id"] == test_organization.id
|
||
|
|
assert "mfa_policy_mode" in policy_dict
|
||
|
|
assert "mfa_grace_period_days" in policy_dict
|
||
|
|
|
||
|
|
def test_org_security_policy_relationships(self, db, test_organization):
|
||
|
|
"""Test organization security policy relationships."""
|
||
|
|
policy = OrganizationSecurityPolicy(
|
||
|
|
organization_id=test_organization.id,
|
||
|
|
mfa_policy_mode=MfaPolicyMode.REQUIRE_TOTP,
|
||
|
|
)
|
||
|
|
policy.save()
|
||
|
|
|
||
|
|
# Test relationship
|
||
|
|
assert policy.organization is not None
|
||
|
|
assert policy.organization.id == test_organization.id
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.unit
|
||
|
|
class TestUserSecurityPolicyModel:
|
||
|
|
"""Tests for UserSecurityPolicy model."""
|
||
|
|
|
||
|
|
def test_create_user_security_policy(self, db, test_user, test_organization):
|
||
|
|
"""Test creating a user security policy."""
|
||
|
|
policy = UserSecurityPolicy(
|
||
|
|
user_id=test_user.id,
|
||
|
|
organization_id=test_organization.id,
|
||
|
|
mfa_override_mode=MfaRequirementOverride.INHERIT,
|
||
|
|
)
|
||
|
|
policy.save()
|
||
|
|
|
||
|
|
assert policy.id is not None
|
||
|
|
assert policy.user_id == test_user.id
|
||
|
|
assert policy.organization_id == test_organization.id
|
||
|
|
assert policy.mfa_override_mode == MfaRequirementOverride.INHERIT
|
||
|
|
assert policy.force_totp is False
|
||
|
|
assert policy.force_webauthn is False
|
||
|
|
|
||
|
|
def test_user_security_policy_with_overrides(self, db, test_user, test_organization):
|
||
|
|
"""Test user security policy with override settings."""
|
||
|
|
policy = UserSecurityPolicy(
|
||
|
|
user_id=test_user.id,
|
||
|
|
organization_id=test_organization.id,
|
||
|
|
mfa_override_mode=MfaRequirementOverride.REQUIRED,
|
||
|
|
force_totp=True,
|
||
|
|
force_webauthn=False,
|
||
|
|
)
|
||
|
|
policy.save()
|
||
|
|
|
||
|
|
assert policy.mfa_override_mode == MfaRequirementOverride.REQUIRED
|
||
|
|
assert policy.force_totp is True
|
||
|
|
assert policy.force_webauthn is False
|
||
|
|
|
||
|
|
def test_user_security_policy_exempt(self, db, test_user, test_organization):
|
||
|
|
"""Test user security policy with exempt override."""
|
||
|
|
policy = UserSecurityPolicy(
|
||
|
|
user_id=test_user.id,
|
||
|
|
organization_id=test_organization.id,
|
||
|
|
mfa_override_mode=MfaRequirementOverride.EXEMPT,
|
||
|
|
)
|
||
|
|
policy.save()
|
||
|
|
|
||
|
|
assert policy.mfa_override_mode == MfaRequirementOverride.EXEMPT
|
||
|
|
|
||
|
|
def test_user_security_policy_relationships(self, db, test_user, test_organization):
|
||
|
|
"""Test user security policy relationships."""
|
||
|
|
policy = UserSecurityPolicy(
|
||
|
|
user_id=test_user.id,
|
||
|
|
organization_id=test_organization.id,
|
||
|
|
mfa_override_mode=MfaRequirementOverride.INHERIT,
|
||
|
|
)
|
||
|
|
policy.save()
|
||
|
|
|
||
|
|
# Test relationships
|
||
|
|
assert policy.user is not None
|
||
|
|
assert policy.user.id == test_user.id
|
||
|
|
assert policy.organization is not None
|
||
|
|
assert policy.organization.id == test_organization.id
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.unit
|
||
|
|
class TestMfaPolicyComplianceModel:
|
||
|
|
"""Tests for MfaPolicyCompliance model."""
|
||
|
|
|
||
|
|
def test_create_mfa_policy_compliance(self, db, test_user, test_organization):
|
||
|
|
"""Test creating an MFA policy compliance record."""
|
||
|
|
compliance = MfaPolicyCompliance(
|
||
|
|
user_id=test_user.id,
|
||
|
|
organization_id=test_organization.id,
|
||
|
|
status=MfaComplianceStatus.NOT_APPLICABLE,
|
||
|
|
policy_version=1,
|
||
|
|
)
|
||
|
|
compliance.save()
|
||
|
|
|
||
|
|
assert compliance.id is not None
|
||
|
|
assert compliance.user_id == test_user.id
|
||
|
|
assert compliance.organization_id == test_organization.id
|
||
|
|
assert compliance.status == MfaComplianceStatus.NOT_APPLICABLE
|
||
|
|
assert compliance.policy_version == 1
|
||
|
|
assert compliance.notification_count == 0
|
||
|
|
|
||
|
|
def test_mfa_policy_compliance_in_grace(self, db, test_user, test_organization):
|
||
|
|
"""Test MFA compliance record in grace period."""
|
||
|
|
now = datetime.now(timezone.utc)
|
||
|
|
compliance = MfaPolicyCompliance(
|
||
|
|
user_id=test_user.id,
|
||
|
|
organization_id=test_organization.id,
|
||
|
|
status=MfaComplianceStatus.IN_GRACE,
|
||
|
|
policy_version=1,
|
||
|
|
applied_at=now,
|
||
|
|
deadline_at=now + timedelta(days=14),
|
||
|
|
)
|
||
|
|
compliance.save()
|
||
|
|
|
||
|
|
assert compliance.status == MfaComplianceStatus.IN_GRACE
|
||
|
|
assert compliance.applied_at is not None
|
||
|
|
assert compliance.deadline_at is not None
|
||
|
|
assert compliance.deadline_at > now
|
||
|
|
|
||
|
|
def test_mfa_policy_compliance_compliant(self, db, test_user, test_organization):
|
||
|
|
"""Test MFA compliance record when compliant."""
|
||
|
|
now = datetime.now(timezone.utc)
|
||
|
|
compliance = MfaPolicyCompliance(
|
||
|
|
user_id=test_user.id,
|
||
|
|
organization_id=test_organization.id,
|
||
|
|
status=MfaComplianceStatus.COMPLIANT,
|
||
|
|
policy_version=1,
|
||
|
|
applied_at=now - timedelta(days=30),
|
||
|
|
deadline_at=now - timedelta(days=16),
|
||
|
|
compliant_at=now - timedelta(days=16),
|
||
|
|
)
|
||
|
|
compliance.save()
|
||
|
|
|
||
|
|
assert compliance.status == MfaComplianceStatus.COMPLIANT
|
||
|
|
assert compliance.compliant_at is not None
|
||
|
|
|
||
|
|
def test_mfa_policy_compliance_suspended(self, db, test_user, test_organization):
|
||
|
|
"""Test MFA compliance record when suspended."""
|
||
|
|
now = datetime.now(timezone.utc)
|
||
|
|
compliance = MfaPolicyCompliance(
|
||
|
|
user_id=test_user.id,
|
||
|
|
organization_id=test_organization.id,
|
||
|
|
status=MfaComplianceStatus.SUSPENDED,
|
||
|
|
policy_version=1,
|
||
|
|
applied_at=now - timedelta(days=30),
|
||
|
|
deadline_at=now - timedelta(days=16),
|
||
|
|
suspended_at=now - timedelta(days=16),
|
||
|
|
)
|
||
|
|
compliance.save()
|
||
|
|
|
||
|
|
assert compliance.status == MfaComplianceStatus.SUSPENDED
|
||
|
|
assert compliance.suspended_at is not None
|
||
|
|
|
||
|
|
def test_mfa_policy_compliance_relationships(self, db, test_user, test_organization):
|
||
|
|
"""Test MFA compliance relationships."""
|
||
|
|
compliance = MfaPolicyCompliance(
|
||
|
|
user_id=test_user.id,
|
||
|
|
organization_id=test_organization.id,
|
||
|
|
status=MfaComplianceStatus.NOT_APPLICABLE,
|
||
|
|
policy_version=1,
|
||
|
|
)
|
||
|
|
compliance.save()
|
||
|
|
|
||
|
|
# Test relationships
|
||
|
|
assert compliance.user is not None
|
||
|
|
assert compliance.user.id == test_user.id
|
||
|
|
assert compliance.organization is not None
|
||
|
|
assert compliance.organization.id == test_organization.id
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.unit
|
||
|
|
class TestSessionModelComplianceFlag:
|
||
|
|
"""Tests for Session model compliance flag."""
|
||
|
|
|
||
|
|
def test_session_default_not_compliance_only(self, db, test_user):
|
||
|
|
"""Test that sessions are not compliance only by default."""
|
||
|
|
session = Session(
|
||
|
|
user_id=test_user.id,
|
||
|
|
token="test-token-123",
|
||
|
|
status=SessionStatus.ACTIVE,
|
||
|
|
expires_at=datetime.now(timezone.utc) + timedelta(hours=8),
|
||
|
|
last_activity_at=datetime.now(timezone.utc),
|
||
|
|
)
|
||
|
|
session.save()
|
||
|
|
|
||
|
|
assert session.is_compliance_only is False
|
||
|
|
|
||
|
|
def test_session_compliance_only(self, db, test_user):
|
||
|
|
"""Test creating a compliance-only session."""
|
||
|
|
session = Session(
|
||
|
|
user_id=test_user.id,
|
||
|
|
token="compliance-token-123",
|
||
|
|
status=SessionStatus.ACTIVE,
|
||
|
|
expires_at=datetime.now(timezone.utc) + timedelta(hours=8),
|
||
|
|
last_activity_at=datetime.now(timezone.utc),
|
||
|
|
is_compliance_only=True,
|
||
|
|
)
|
||
|
|
session.save()
|
||
|
|
|
||
|
|
assert session.is_compliance_only is True
|
||
|
|
|
||
|
|
def test_session_to_dict_excludes_token(self, db, test_user):
|
||
|
|
"""Test that session to_dict excludes the token."""
|
||
|
|
session = Session(
|
||
|
|
user_id=test_user.id,
|
||
|
|
token="test-token-456",
|
||
|
|
status=SessionStatus.ACTIVE,
|
||
|
|
expires_at=datetime.now(timezone.utc) + timedelta(hours=8),
|
||
|
|
last_activity_at=datetime.now(timezone.utc),
|
||
|
|
)
|
||
|
|
session.save()
|
||
|
|
|
||
|
|
session_dict = session.to_dict()
|
||
|
|
|
||
|
|
assert "id" in session_dict
|
||
|
|
assert "user_id" in session_dict
|
||
|
|
assert "is_compliance_only" in session_dict
|
||
|
|
assert session_dict["is_compliance_only"] is False
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.unit
|
||
|
|
class TestUserStatusComplianceSuspended:
|
||
|
|
"""Tests for UserStatus.COMPLIANCE_SUSPENDED."""
|
||
|
|
|
||
|
|
def test_compliance_suspended_status_exists(self):
|
||
|
|
"""Test that COMPLIANCE_SUSPENDED status exists."""
|
||
|
|
assert UserStatus.COMPLIANCE_SUSPENDED.value == "compliance_suspended"
|
||
|
|
|
||
|
|
def test_create_compliance_suspended_user(self, db):
|
||
|
|
"""Test creating a compliance suspended user."""
|
||
|
|
user = User(
|
||
|
|
email="suspended@example.com",
|
||
|
|
full_name="Suspended User",
|
||
|
|
status=UserStatus.COMPLIANCE_SUSPENDED,
|
||
|
|
)
|
||
|
|
user.save()
|
||
|
|
|
||
|
|
assert user.status == UserStatus.COMPLIANCE_SUSPENDED
|