feat: send suspension emails and enhanced audit logs for MFA non-compliance
This commit is contained in:
@@ -403,19 +403,19 @@ class MfaPolicyService:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def transition_to_suspended_if_past_due(now: Optional[datetime] = None) -> int:
|
||||
def transition_to_suspended_if_past_due(now: Optional[datetime] = None) -> List[Dict[str, Any]]:
|
||||
"""Scheduled job to transition past-due users to suspended status.
|
||||
|
||||
Args:
|
||||
now: Current time, defaults to now
|
||||
|
||||
Returns:
|
||||
Number of users transitioned to suspended
|
||||
List of dicts with suspended record details (user, compliance, org_policy)
|
||||
"""
|
||||
if now is None:
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
suspended_count = 0
|
||||
suspended_records = []
|
||||
|
||||
# Find all compliance records that are past due
|
||||
past_due_records = MfaPolicyCompliance.query.filter(
|
||||
@@ -437,21 +437,65 @@ class MfaPolicyService:
|
||||
|
||||
# Update user status
|
||||
user = User.query.get(record.user_id)
|
||||
if user and user.status != UserStatus.COMPLIANCE_SUSPENDED:
|
||||
if not user:
|
||||
continue
|
||||
|
||||
if user.status != UserStatus.COMPLIANCE_SUSPENDED:
|
||||
user.status = UserStatus.COMPLIANCE_SUSPENDED
|
||||
db.session.commit()
|
||||
|
||||
# Audit log
|
||||
AuditService.log_action(
|
||||
action=AuditAction.MFA_POLICY_USER_SUSPENDED,
|
||||
user_id=record.user_id,
|
||||
organization_id=record.organization_id,
|
||||
description=f"User suspended due to MFA compliance deadline passed",
|
||||
)
|
||||
# Get org policy for extended details
|
||||
org_policy = OrganizationSecurityPolicy.query.filter_by(
|
||||
organization_id=record.organization_id, deleted_at=None
|
||||
).first()
|
||||
|
||||
suspended_count += 1
|
||||
days_overdue = (now - deadline).days if deadline else 0
|
||||
|
||||
return suspended_count
|
||||
# Audit log for user (with extended details)
|
||||
AuditService.log_action(
|
||||
action=AuditAction.MFA_POLICY_USER_SUSPENDED,
|
||||
user_id=record.user_id,
|
||||
organization_id=record.organization_id,
|
||||
resource_type="user",
|
||||
resource_id=record.user_id,
|
||||
description=f"User suspended due to MFA compliance deadline passed",
|
||||
metadata={
|
||||
"deadline_at": record.deadline_at.isoformat() if record.deadline_at else None,
|
||||
"suspended_at": now.isoformat(),
|
||||
"days_overdue": days_overdue,
|
||||
"user_email": user.email,
|
||||
"policy_mode": org_policy.mfa_policy_mode.value if org_policy else None,
|
||||
"grace_period_days": org_policy.mfa_grace_period_days if org_policy else None,
|
||||
"policy_version": org_policy.policy_version if org_policy else None,
|
||||
"reason": "MFA compliance deadline passed without required enrollment",
|
||||
},
|
||||
)
|
||||
|
||||
# Audit log for org (org-scoped entry for admin visibility)
|
||||
AuditService.log_action(
|
||||
action=AuditAction.MFA_POLICY_USER_SUSPENDED,
|
||||
user_id=None,
|
||||
organization_id=record.organization_id,
|
||||
resource_type="user",
|
||||
resource_id=record.user_id,
|
||||
description=f"Organization member {user.email} suspended due to MFA non-compliance",
|
||||
metadata={
|
||||
"suspended_user_id": record.user_id,
|
||||
"suspended_user_email": user.email,
|
||||
"deadline_at": record.deadline_at.isoformat() if record.deadline_at else None,
|
||||
"suspended_at": now.isoformat(),
|
||||
"days_overdue": days_overdue,
|
||||
"policy_mode": org_policy.mfa_policy_mode.value if org_policy else None,
|
||||
},
|
||||
)
|
||||
|
||||
suspended_records.append({
|
||||
"user": user,
|
||||
"compliance": record,
|
||||
"org_policy": org_policy,
|
||||
})
|
||||
|
||||
return suspended_records
|
||||
|
||||
@staticmethod
|
||||
def create_org_policy(
|
||||
|
||||
Reference in New Issue
Block a user