Files
gatehouse-api/gatehouse_app/services/audit_service.py
T
JamesBhattarai a0d4e59c24 Feat(Chore): Verify Flow, Invites, Suspend, Depart Cert Policy
feat: add password reset and email verification flow
feat: add org invite listing, cancellation, and invite link fallback
feat: add user suspend/unsuspend with audit logging
feat: add department certificate policy (expiry, extensions)
feat: enforce dept cert policy on SSH certificate signing
feat: wire up OIDC consent and token flow (replace mocks)
feat: rework CLI auth bridge to use frontend login flow
feat: add admin OAuth provider management (CRUD)
chore: refactor model import paths after module reorganisation
chore: clean up config, decorators, and dev tooling
2026-03-01 20:42:48 +05:45

337 lines
10 KiB
Python

"""Audit service."""
from flask import request, g
from gatehouse_app.models.auth.audit_log import AuditLog
from gatehouse_app.utils.constants import AuditAction
class AuditService:
"""Service for audit logging."""
@staticmethod
def log_action(
action,
user_id=None,
organization_id=None,
resource_type=None,
resource_id=None,
metadata=None,
description=None,
success=True,
error_message=None,
):
"""
Create an audit log entry.
Args:
action: AuditAction enum value
user_id: ID of user performing the action
organization_id: ID of related organization
resource_type: Type of resource being acted upon
resource_id: ID of resource being acted upon
metadata: Additional metadata dictionary
description: Human-readable description
success: Whether the action succeeded
error_message: Error message if action failed
Returns:
AuditLog instance
"""
# Get request details if available
ip_address = None
user_agent = None
request_id = None
try:
if request:
ip_address = request.remote_addr
user_agent = request.headers.get("User-Agent")
request_id = g.get("request_id")
except RuntimeError:
# No request context
pass
log_entry = AuditLog(
action=action,
user_id=user_id,
organization_id=organization_id,
resource_type=resource_type,
resource_id=resource_id,
ip_address=ip_address,
user_agent=user_agent,
request_id=request_id,
metadata=metadata,
description=description,
success=success,
error_message=error_message,
)
log_entry.save()
return log_entry
@staticmethod
def get_user_activity(user_id, limit=50):
"""
Get recent activity for a user.
Args:
user_id: User ID
limit: Maximum number of records to return
Returns:
List of AuditLog instances
"""
return (
AuditLog.query.filter_by(user_id=user_id)
.order_by(AuditLog.created_at.desc())
.limit(limit)
.all()
)
@staticmethod
def get_organization_activity(organization_id, limit=50):
"""
Get recent activity for an organization.
Args:
organization_id: Organization ID
limit: Maximum number of records to return
Returns:
List of AuditLog instances
"""
return (
AuditLog.query.filter_by(organization_id=organization_id)
.order_by(AuditLog.created_at.desc())
.limit(limit)
.all()
)
# External Authentication Provider Audit Methods
@staticmethod
def log_external_auth_link_initiated(
user_id: str,
organization_id: str,
provider_type: str,
state_id: str = None,
):
"""Log external auth account linking initiated event."""
return AuditService.log_action(
action=AuditAction.EXTERNAL_AUTH_LINK_INITIATED,
user_id=user_id,
organization_id=organization_id,
resource_type="oauth_state",
resource_id=state_id,
metadata={
"provider_type": provider_type,
},
description=f"External auth link initiated for {provider_type}",
success=True,
)
@staticmethod
def log_external_auth_link_completed(
user_id: str,
organization_id: str,
provider_type: str,
provider_user_id: str,
auth_method_id: str = None,
):
"""Log external auth account linking completed event."""
return AuditService.log_action(
action=AuditAction.EXTERNAL_AUTH_LINK_COMPLETED,
user_id=user_id,
organization_id=organization_id,
resource_type="authentication_method",
resource_id=auth_method_id,
metadata={
"provider_type": provider_type,
"provider_user_id": provider_user_id,
},
description=f"External auth account linked: {provider_type} ({provider_user_id})",
success=True,
)
@staticmethod
def log_external_auth_link_failed(
user_id: str,
organization_id: str,
provider_type: str,
error_message: str,
failure_reason: str = None,
):
"""Log external auth account linking failed event."""
return AuditService.log_action(
action=AuditAction.EXTERNAL_AUTH_LINK_FAILED,
user_id=user_id,
organization_id=organization_id,
metadata={
"provider_type": provider_type,
"failure_reason": failure_reason,
},
description=f"External auth link failed for {provider_type}: {error_message}",
success=False,
error_message=error_message,
)
@staticmethod
def log_external_auth_unlink(
user_id: str,
organization_id: str,
provider_type: str,
provider_user_id: str,
auth_method_id: str = None,
):
"""Log external auth account unlinking event."""
return AuditService.log_action(
action=AuditAction.EXTERNAL_AUTH_UNLINK,
user_id=user_id,
organization_id=organization_id,
resource_type="authentication_method",
resource_id=auth_method_id,
metadata={
"provider_type": provider_type,
"provider_user_id": provider_user_id,
},
description=f"External auth account unlinked: {provider_type} ({provider_user_id})",
success=True,
)
@staticmethod
def log_external_auth_login(
user_id: str,
organization_id: str,
provider_type: str,
provider_user_id: str,
auth_method_id: str = None,
session_id: str = None,
mfa_used: bool = False,
):
"""Log external auth login event."""
return AuditService.log_action(
action=AuditAction.EXTERNAL_AUTH_LOGIN,
user_id=user_id,
organization_id=organization_id,
resource_type="session",
resource_id=session_id,
metadata={
"provider_type": provider_type,
"provider_user_id": provider_user_id,
"auth_method_id": auth_method_id,
"mfa_used": mfa_used,
},
description=f"User logged in with {provider_type}",
success=True,
)
@staticmethod
def log_external_auth_login_failed(
organization_id: str,
provider_type: str,
provider_user_id: str = None,
email: str = None,
failure_reason: str = None,
error_message: str = None,
):
"""Log external auth login failed event."""
return AuditService.log_action(
action=AuditAction.EXTERNAL_AUTH_LOGIN_FAILED,
user_id=None, # Unknown user
organization_id=organization_id,
metadata={
"provider_type": provider_type,
"provider_user_id": provider_user_id,
"email": email,
"failure_reason": failure_reason,
},
description=f"Failed login attempt with {provider_type}: {failure_reason or error_message}",
success=False,
error_message=error_message or failure_reason,
)
@staticmethod
def log_external_auth_token_refresh(
user_id: str,
organization_id: str,
provider_type: str,
auth_method_id: str = None,
):
"""Log external auth token refresh event."""
return AuditService.log_action(
action=AuditAction.EXTERNAL_AUTH_TOKEN_REFRESH,
user_id=user_id,
organization_id=organization_id,
resource_type="authentication_method",
resource_id=auth_method_id,
metadata={
"provider_type": provider_type,
},
description=f"External auth token refreshed for {provider_type}",
success=True,
)
@staticmethod
def log_external_auth_config_create(
user_id: str,
organization_id: str,
provider_type: str,
config_id: str = None,
):
"""Log external auth provider config creation event."""
return AuditService.log_action(
action=AuditAction.EXTERNAL_AUTH_CONFIG_CREATE,
user_id=user_id,
organization_id=organization_id,
resource_type="external_provider_config",
resource_id=config_id,
metadata={
"provider_type": provider_type,
},
description=f"External auth provider config created: {provider_type}",
success=True,
)
@staticmethod
def log_external_auth_config_update(
user_id: str,
organization_id: str,
provider_type: str,
config_id: str = None,
changes: dict = None,
):
"""Log external auth provider config update event."""
return AuditService.log_action(
action=AuditAction.EXTERNAL_AUTH_CONFIG_UPDATE,
user_id=user_id,
organization_id=organization_id,
resource_type="external_provider_config",
resource_id=config_id,
metadata={
"provider_type": provider_type,
"changes": changes,
},
description=f"External auth provider config updated: {provider_type}",
success=True,
)
@staticmethod
def log_external_auth_config_delete(
user_id: str,
organization_id: str,
provider_type: str,
config_id: str = None,
):
"""Log external auth provider config deletion event."""
return AuditService.log_action(
action=AuditAction.EXTERNAL_AUTH_CONFIG_DELETE,
user_id=user_id,
organization_id=organization_id,
resource_type="external_provider_config",
resource_id=config_id,
metadata={
"provider_type": provider_type,
},
description=f"External auth provider config deleted: {provider_type}",
success=True,
)