feat: send suspension emails and enhanced audit logs for MFA non-compliance
This commit is contained in:
@@ -23,9 +23,10 @@ from gatehouse_app.extensions import db
|
||||
from gatehouse_app.models.security.mfa_policy_compliance import MfaPolicyCompliance
|
||||
from gatehouse_app.models.security.organization_security_policy import OrganizationSecurityPolicy
|
||||
from gatehouse_app.models.user.user import User
|
||||
from gatehouse_app.models.organization.organization_member import OrganizationMember
|
||||
from gatehouse_app.services.mfa_policy_service import MfaPolicyService
|
||||
from gatehouse_app.services.notification_service import NotificationService
|
||||
from gatehouse_app.utils.constants import MfaComplianceStatus
|
||||
from gatehouse_app.utils.constants import MfaComplianceStatus, OrganizationRole
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -35,9 +36,11 @@ def process_mfa_compliance(now: Optional[datetime] = None) -> Dict[str, Any]:
|
||||
|
||||
This scheduled job performs the following operations:
|
||||
1. Transitions users from PAST_DUE to SUSPENDED status
|
||||
2. Identifies users approaching deadline (within notify_days_before)
|
||||
3. Sends deadline reminder notifications
|
||||
4. Updates notification tracking metadata
|
||||
2. Sends suspension notification to suspended users
|
||||
3. Sends suspension notification to org admins
|
||||
4. Identifies users approaching deadline (within notify_days_before)
|
||||
5. Sends deadline reminder notifications
|
||||
6. Updates notification tracking metadata
|
||||
|
||||
Args:
|
||||
now: Current time, defaults to now (UTC)
|
||||
@@ -45,7 +48,9 @@ def process_mfa_compliance(now: Optional[datetime] = None) -> Dict[str, Any]:
|
||||
Returns:
|
||||
Dictionary with job execution statistics:
|
||||
- suspended_count: Number of users transitioned to suspended
|
||||
- notified_count: Number of notifications sent
|
||||
- user_notified_count: Number of suspension emails sent to users
|
||||
- admin_notified_count: Number of suspension emails sent to admins
|
||||
- notified_count: Number of deadline reminder notifications sent
|
||||
- processed_count: Total compliance records processed
|
||||
"""
|
||||
if now is None:
|
||||
@@ -55,6 +60,8 @@ def process_mfa_compliance(now: Optional[datetime] = None) -> Dict[str, Any]:
|
||||
|
||||
stats = {
|
||||
"suspended_count": 0,
|
||||
"user_notified_count": 0,
|
||||
"admin_notified_count": 0,
|
||||
"notified_count": 0,
|
||||
"processed_count": 0,
|
||||
"errors": [],
|
||||
@@ -62,16 +69,67 @@ def process_mfa_compliance(now: Optional[datetime] = None) -> Dict[str, Any]:
|
||||
|
||||
try:
|
||||
# Step 1: Transition past-due users to suspended
|
||||
suspended_count = MfaPolicyService.transition_to_suspended_if_past_due(now)
|
||||
stats["suspended_count"] = suspended_count
|
||||
logger.info(f"Transitioned {suspended_count} users to suspended status")
|
||||
suspended_records = MfaPolicyService.transition_to_suspended_if_past_due(now)
|
||||
stats["suspended_count"] = len(suspended_records)
|
||||
logger.info(f"Transitioned {len(suspended_records)} users to suspended status")
|
||||
|
||||
# Step 2: Send notifications to users approaching deadline
|
||||
# Step 2: Send notifications for each suspended user
|
||||
for entry in suspended_records:
|
||||
try:
|
||||
user = entry["user"]
|
||||
compliance = entry["compliance"]
|
||||
org_policy = entry["org_policy"]
|
||||
|
||||
# 2a: Send suspension notification to the user
|
||||
user_notified = NotificationService.send_mfa_suspended_notification(
|
||||
user=user,
|
||||
compliance=compliance,
|
||||
org_policy=org_policy,
|
||||
)
|
||||
if user_notified:
|
||||
stats["user_notified_count"] += 1
|
||||
logger.info(f"Sent suspension notice to user {user.email}")
|
||||
|
||||
# 2b: Send suspension notification to org admins
|
||||
if org_policy:
|
||||
admin_members = OrganizationMember.query.filter(
|
||||
OrganizationMember.organization_id == compliance.organization_id,
|
||||
OrganizationMember.role.in_([OrganizationRole.OWNER, OrganizationRole.ADMIN]),
|
||||
OrganizationMember.deleted_at == None,
|
||||
).all()
|
||||
|
||||
for member in admin_members:
|
||||
admin_user = User.query.get(member.user_id)
|
||||
if not admin_user or not admin_user.email:
|
||||
continue
|
||||
|
||||
# Skip notifying the suspended user themselves
|
||||
if admin_user.id == user.id:
|
||||
continue
|
||||
|
||||
admin_notified = NotificationService.send_mfa_suspended_admin_notification(
|
||||
admin_user=admin_user,
|
||||
suspended_user=user,
|
||||
compliance=compliance,
|
||||
org_policy=org_policy,
|
||||
)
|
||||
if admin_notified:
|
||||
stats["admin_notified_count"] += 1
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Error sending suspension notifications for compliance record "
|
||||
f"{entry.get('compliance', {}).id if entry.get('compliance') else 'unknown'}: {e}"
|
||||
)
|
||||
stats["errors"].append(str(e))
|
||||
continue
|
||||
|
||||
# Step 3: Send notifications to users approaching deadline
|
||||
notified_count = _send_deadline_reminders(now)
|
||||
stats["notified_count"] = notified_count
|
||||
logger.info(f"Sent {notified_count} deadline reminder notifications")
|
||||
|
||||
# Step 3: Process any pending compliance evaluations
|
||||
# Step 4: Process any pending compliance evaluations
|
||||
processed_count = _evaluate_pending_compliance(now)
|
||||
stats["processed_count"] = processed_count
|
||||
logger.info(f"Processed {processed_count} compliance records")
|
||||
@@ -82,7 +140,10 @@ def process_mfa_compliance(now: Optional[datetime] = None) -> Dict[str, Any]:
|
||||
|
||||
logger.info(
|
||||
f"MFA compliance job completed: suspended={stats['suspended_count']}, "
|
||||
f"notified={stats['notified_count']}, processed={stats['processed_count']}"
|
||||
f"user_notified={stats['user_notified_count']}, "
|
||||
f"admin_notified={stats['admin_notified_count']}, "
|
||||
f"deadline_reminders={stats['notified_count']}, "
|
||||
f"processed={stats['processed_count']}"
|
||||
)
|
||||
|
||||
return stats
|
||||
|
||||
Reference in New Issue
Block a user