Improve auditing
This commit is contained in:
@@ -10,6 +10,8 @@ from gatehouse_app.models import Department, DepartmentMembership
|
|||||||
from gatehouse_app.services.organization_service import OrganizationService
|
from gatehouse_app.services.organization_service import OrganizationService
|
||||||
from gatehouse_app.services.user_service import UserService
|
from gatehouse_app.services.user_service import UserService
|
||||||
from gatehouse_app.extensions import db
|
from gatehouse_app.extensions import db
|
||||||
|
from gatehouse_app.utils.constants import AuditAction
|
||||||
|
from gatehouse_app.services.audit_service import AuditService
|
||||||
|
|
||||||
|
|
||||||
class DepartmentCreateSchema(Schema):
|
class DepartmentCreateSchema(Schema):
|
||||||
@@ -127,6 +129,15 @@ def create_department(org_id):
|
|||||||
db.session.add(dept)
|
db.session.add(dept)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.DEPARTMENT_CREATED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="department",
|
||||||
|
resource_id=str(dept.id),
|
||||||
|
description=f"Department '{dept.name}' created",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(
|
return api_response(
|
||||||
data={"department": dept.to_dict()},
|
data={"department": dept.to_dict()},
|
||||||
message="Department created successfully",
|
message="Department created successfully",
|
||||||
@@ -255,6 +266,15 @@ def update_department(org_id, dept_id):
|
|||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.DEPARTMENT_UPDATED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="department",
|
||||||
|
resource_id=str(dept.id),
|
||||||
|
description=f"Department '{dept.name}' updated",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(
|
return api_response(
|
||||||
data={"department": dept.to_dict()},
|
data={"department": dept.to_dict()},
|
||||||
message="Department updated successfully",
|
message="Department updated successfully",
|
||||||
@@ -308,6 +328,15 @@ def delete_department(org_id, dept_id):
|
|||||||
dept.deleted_at = db.func.now()
|
dept.deleted_at = db.func.now()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.DEPARTMENT_DELETED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="department",
|
||||||
|
resource_id=str(dept.id),
|
||||||
|
description=f"Department '{dept.name}' deleted",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(
|
return api_response(
|
||||||
message="Department deleted successfully",
|
message="Department deleted successfully",
|
||||||
)
|
)
|
||||||
@@ -461,6 +490,15 @@ def add_department_member(org_id, dept_id):
|
|||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.DEPARTMENT_MEMBER_ADDED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="user",
|
||||||
|
resource_id=str(user.id),
|
||||||
|
description=f"Added user {user.email} to department '{dept.name}'",
|
||||||
|
)
|
||||||
|
|
||||||
member_dict = membership.to_dict()
|
member_dict = membership.to_dict()
|
||||||
member_dict["user"] = user.to_dict()
|
member_dict["user"] = user.to_dict()
|
||||||
|
|
||||||
@@ -533,6 +571,15 @@ def remove_department_member(org_id, dept_id, user_id):
|
|||||||
membership.deleted_at = db.func.now()
|
membership.deleted_at = db.func.now()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.DEPARTMENT_MEMBER_REMOVED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="user",
|
||||||
|
resource_id=str(user_id),
|
||||||
|
description=f"Removed user from department '{dept.name}'",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(
|
return api_response(
|
||||||
message="Member removed successfully",
|
message="Member removed successfully",
|
||||||
)
|
)
|
||||||
@@ -699,5 +746,14 @@ def set_dept_cert_policy(org_id, dept_id):
|
|||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.DEPARTMENT_CERT_POLICY_UPDATED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="department",
|
||||||
|
resource_id=str(dept_id),
|
||||||
|
description=f"Certificate policy updated for department '{dept.name}'",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(data={"cert_policy": policy.to_dict()}, message="Certificate policy saved")
|
return api_response(data={"cert_policy": policy.to_dict()}, message="Certificate policy saved")
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ from flask import g, request
|
|||||||
from gatehouse_app.api.v1 import api_v1_bp
|
from gatehouse_app.api.v1 import api_v1_bp
|
||||||
from gatehouse_app.utils.response import api_response
|
from gatehouse_app.utils.response import api_response
|
||||||
from gatehouse_app.utils.decorators import login_required
|
from gatehouse_app.utils.decorators import login_required
|
||||||
|
from gatehouse_app.utils.constants import AuditAction
|
||||||
|
from gatehouse_app.services.audit_service import AuditService
|
||||||
|
|
||||||
|
|
||||||
@api_v1_bp.route("/admin/oauth/providers", methods=["GET"])
|
@api_v1_bp.route("/admin/oauth/providers", methods=["GET"])
|
||||||
@@ -78,6 +80,14 @@ def admin_configure_app_provider(provider: str):
|
|||||||
db.session.add(cfg)
|
db.session.add(cfg)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.EXTERNAL_AUTH_CONFIG_UPDATE if cfg else AuditAction.EXTERNAL_AUTH_CONFIG_CREATE,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
resource_type="oauth_provider",
|
||||||
|
resource_id=provider,
|
||||||
|
description=f"OAuth provider '{provider}' configured (enabled={cfg.is_enabled})",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(
|
return api_response(
|
||||||
data={"provider": {"id": provider, "client_id": cfg.client_id, "is_enabled": cfg.is_enabled}},
|
data={"provider": {"id": provider, "client_id": cfg.client_id, "is_enabled": cfg.is_enabled}},
|
||||||
message=f"{provider.capitalize()} OAuth provider configured successfully",
|
message=f"{provider.capitalize()} OAuth provider configured successfully",
|
||||||
@@ -104,4 +114,13 @@ def admin_delete_app_provider(provider: str):
|
|||||||
return api_response(success=False, message=f"Provider '{provider}' is not configured", status=404, error_type="NOT_FOUND")
|
return api_response(success=False, message=f"Provider '{provider}' is not configured", status=404, error_type="NOT_FOUND")
|
||||||
|
|
||||||
cfg.delete()
|
cfg.delete()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.EXTERNAL_AUTH_CONFIG_DELETE,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
resource_type="oauth_provider",
|
||||||
|
resource_id=provider,
|
||||||
|
description=f"OAuth provider '{provider}' configuration removed",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(message=f"{provider.capitalize()} OAuth provider configuration removed")
|
return api_response(message=f"{provider.capitalize()} OAuth provider configuration removed")
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ from gatehouse_app.exceptions.auth_exceptions import (
|
|||||||
AccountSuspendedError,
|
AccountSuspendedError,
|
||||||
AccountInactiveError,
|
AccountInactiveError,
|
||||||
)
|
)
|
||||||
|
from gatehouse_app.utils.constants import AuditAction
|
||||||
|
from gatehouse_app.services.audit_service import AuditService
|
||||||
|
from gatehouse_app.services.oidc_audit_service import OIDCAuditService
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -849,6 +852,18 @@ def oidc_register():
|
|||||||
)
|
)
|
||||||
client.save()
|
client.save()
|
||||||
|
|
||||||
|
OIDCAuditService.log_event(
|
||||||
|
event_type="client_registration",
|
||||||
|
client_id=client_id,
|
||||||
|
user_id=g.current_user.id if hasattr(g, "current_user") else None,
|
||||||
|
success=True,
|
||||||
|
metadata={
|
||||||
|
"client_name": client_name,
|
||||||
|
"redirect_uris": redirect_uris,
|
||||||
|
"organization_id": str(organization.id),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
response = jsonify({
|
response = jsonify({
|
||||||
"client_id": client_id,
|
"client_id": client_id,
|
||||||
"client_secret": client_secret,
|
"client_secret": client_secret,
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ from gatehouse_app.utils.decorators import login_required, require_admin, full_a
|
|||||||
from gatehouse_app.models.organization import OrganizationApiKey
|
from gatehouse_app.models.organization import OrganizationApiKey
|
||||||
from gatehouse_app.services.organization_service import OrganizationService
|
from gatehouse_app.services.organization_service import OrganizationService
|
||||||
from gatehouse_app.extensions import db
|
from gatehouse_app.extensions import db
|
||||||
|
from gatehouse_app.utils.constants import AuditAction
|
||||||
|
from gatehouse_app.services.audit_service import AuditService
|
||||||
|
|
||||||
|
|
||||||
class ApiKeyCreateSchema(Schema):
|
class ApiKeyCreateSchema(Schema):
|
||||||
@@ -131,6 +133,15 @@ def create_api_key(org_id):
|
|||||||
description=data.get("description"),
|
description=data.get("description"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.ORG_API_KEY_CREATED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="api_key",
|
||||||
|
resource_id=str(api_key.id),
|
||||||
|
description=f"API key '{api_key.name}' created",
|
||||||
|
)
|
||||||
|
|
||||||
# Return the key data with the plain text key (only on creation)
|
# Return the key data with the plain text key (only on creation)
|
||||||
key_dict = api_key.to_dict()
|
key_dict = api_key.to_dict()
|
||||||
key_dict["key"] = plain_key # Include plain text only on creation
|
key_dict["key"] = plain_key # Include plain text only on creation
|
||||||
@@ -222,6 +233,15 @@ def update_api_key(org_id, key_id):
|
|||||||
|
|
||||||
api_key.save()
|
api_key.save()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.ORG_API_KEY_UPDATED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="api_key",
|
||||||
|
resource_id=str(api_key.id),
|
||||||
|
description=f"API key '{api_key.name}' updated",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(
|
return api_response(
|
||||||
data={"api_key": api_key.to_dict()},
|
data={"api_key": api_key.to_dict()},
|
||||||
message="API key updated successfully",
|
message="API key updated successfully",
|
||||||
@@ -294,6 +314,15 @@ def delete_api_key(org_id, key_id):
|
|||||||
# Soft delete the API key
|
# Soft delete the API key
|
||||||
api_key.delete(soft=True)
|
api_key.delete(soft=True)
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.ORG_API_KEY_DELETED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="api_key",
|
||||||
|
resource_id=str(api_key.id),
|
||||||
|
description=f"API key '{api_key.name}' deleted",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(
|
return api_response(
|
||||||
message="API key deleted successfully",
|
message="API key deleted successfully",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ from gatehouse_app.utils.response import api_response
|
|||||||
from gatehouse_app.utils.decorators import login_required, require_admin
|
from gatehouse_app.utils.decorators import login_required, require_admin
|
||||||
from gatehouse_app.extensions import db
|
from gatehouse_app.extensions import db
|
||||||
from gatehouse_app.api.v1.organizations._helpers import _get_system_ca_dict
|
from gatehouse_app.api.v1.organizations._helpers import _get_system_ca_dict
|
||||||
|
from gatehouse_app.utils.constants import AuditAction
|
||||||
|
from gatehouse_app.services.audit_service import AuditService
|
||||||
|
|
||||||
|
|
||||||
@api_v1_bp.route("/organizations/<org_id>/cas", methods=["GET"])
|
@api_v1_bp.route("/organizations/<org_id>/cas", methods=["GET"])
|
||||||
@@ -66,6 +68,16 @@ def update_org_ca(org_id, ca_id):
|
|||||||
ca.max_cert_validity_hours = data["max_cert_validity_hours"]
|
ca.max_cert_validity_hours = data["max_cert_validity_hours"]
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.CA_UPDATED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="CA",
|
||||||
|
resource_id=ca_id,
|
||||||
|
description=f"CA '{ca.name}' updated",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(data={"ca": ca.to_dict()}, message="CA updated successfully")
|
return api_response(data={"ca": ca.to_dict()}, message="CA updated successfully")
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
return api_response(success=False, message="Validation failed", status=400, error_type="VALIDATION_ERROR", error_details=e.messages)
|
return api_response(success=False, message="Validation failed", status=400, error_type="VALIDATION_ERROR", error_details=e.messages)
|
||||||
@@ -150,6 +162,15 @@ def create_org_ca(org_id):
|
|||||||
return api_response(success=False, message="A CA with that name already exists in this organization (it may have been recently deleted — choose a different name).", status=400, error_type="DUPLICATE_NAME")
|
return api_response(success=False, message="A CA with that name already exists in this organization (it may have been recently deleted — choose a different name).", status=400, error_type="DUPLICATE_NAME")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.CA_CREATED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="CA",
|
||||||
|
resource_id=str(ca.id),
|
||||||
|
description=f"CA '{ca.name}' created",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(data={"ca": ca.to_dict()}, message="CA created successfully", status=201)
|
return api_response(data={"ca": ca.to_dict()}, message="CA created successfully", status=201)
|
||||||
except MaValidationError as e:
|
except MaValidationError as e:
|
||||||
return api_response(success=False, message="Validation failed", status=400, error_type="VALIDATION_ERROR", error_details=e.messages)
|
return api_response(success=False, message="Validation failed", status=400, error_type="VALIDATION_ERROR", error_details=e.messages)
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ from gatehouse_app.api.v1 import api_v1_bp
|
|||||||
from gatehouse_app.utils.response import api_response
|
from gatehouse_app.utils.response import api_response
|
||||||
from gatehouse_app.utils.decorators import login_required, require_admin, full_access_required
|
from gatehouse_app.utils.decorators import login_required, require_admin, full_access_required
|
||||||
from gatehouse_app.extensions import db, bcrypt
|
from gatehouse_app.extensions import db, bcrypt
|
||||||
|
from gatehouse_app.utils.constants import AuditAction
|
||||||
|
from gatehouse_app.services.audit_service import AuditService
|
||||||
|
|
||||||
|
|
||||||
@api_v1_bp.route("/organizations/<org_id>/clients", methods=["GET"])
|
@api_v1_bp.route("/organizations/<org_id>/clients", methods=["GET"])
|
||||||
@@ -79,6 +81,15 @@ def create_org_client(org_id):
|
|||||||
db.session.add(client)
|
db.session.add(client)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.ORG_CLIENT_CREATED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="oidc_client",
|
||||||
|
resource_id=str(client.id),
|
||||||
|
description=f"OIDC client '{client.name}' created",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(
|
return api_response(
|
||||||
data={
|
data={
|
||||||
"client": {
|
"client": {
|
||||||
@@ -126,6 +137,15 @@ def update_org_client(org_id, client_id):
|
|||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.ORG_CLIENT_UPDATED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="oidc_client",
|
||||||
|
resource_id=str(client.id),
|
||||||
|
description=f"OIDC client '{client.name}' updated",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(
|
return api_response(
|
||||||
data={
|
data={
|
||||||
"client": {
|
"client": {
|
||||||
@@ -155,4 +175,14 @@ def delete_org_client(org_id, client_id):
|
|||||||
|
|
||||||
client.is_active = False
|
client.is_active = False
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.ORG_CLIENT_DEACTIVATED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="oidc_client",
|
||||||
|
resource_id=str(client.id),
|
||||||
|
description=f"OIDC client '{client.name}' deactivated",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(data={}, message="Client deactivated successfully")
|
return api_response(data={}, message="Client deactivated successfully")
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ from gatehouse_app.utils.response import api_response
|
|||||||
from gatehouse_app.utils.decorators import login_required, require_admin, full_access_required
|
from gatehouse_app.utils.decorators import login_required, require_admin, full_access_required
|
||||||
from gatehouse_app.schemas.organization_schema import OrganizationCreateSchema, OrganizationUpdateSchema
|
from gatehouse_app.schemas.organization_schema import OrganizationCreateSchema, OrganizationUpdateSchema
|
||||||
from gatehouse_app.services.organization_service import OrganizationService
|
from gatehouse_app.services.organization_service import OrganizationService
|
||||||
|
from gatehouse_app.utils.constants import AuditAction
|
||||||
|
from gatehouse_app.services.audit_service import AuditService
|
||||||
|
|
||||||
|
|
||||||
@api_v1_bp.route("/organizations", methods=["POST"])
|
@api_v1_bp.route("/organizations", methods=["POST"])
|
||||||
@@ -32,6 +34,14 @@ def create_organization():
|
|||||||
description=data.get("description"),
|
description=data.get("description"),
|
||||||
logo_url=data.get("logo_url"),
|
logo_url=data.get("logo_url"),
|
||||||
)
|
)
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.ORG_CREATE,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org.id,
|
||||||
|
resource_type="organization",
|
||||||
|
resource_id=str(org.id),
|
||||||
|
description=f"Organization '{org.name}' created",
|
||||||
|
)
|
||||||
return api_response(data={"organization": org.to_dict()}, message="Organization created successfully", status=201)
|
return api_response(data={"organization": org.to_dict()}, message="Organization created successfully", status=201)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
return api_response(success=False, message="Validation failed", status=400, error_type="VALIDATION_ERROR", error_details=e.messages)
|
return api_response(success=False, message="Validation failed", status=400, error_type="VALIDATION_ERROR", error_details=e.messages)
|
||||||
@@ -60,6 +70,14 @@ def update_organization(org_id):
|
|||||||
data = schema.load(request.json)
|
data = schema.load(request.json)
|
||||||
org = OrganizationService.get_organization_by_id(org_id)
|
org = OrganizationService.get_organization_by_id(org_id)
|
||||||
org = OrganizationService.update_organization(org=org, user_id=g.current_user.id, **data)
|
org = OrganizationService.update_organization(org=org, user_id=g.current_user.id, **data)
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.ORG_UPDATE,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org.id,
|
||||||
|
resource_type="organization",
|
||||||
|
resource_id=str(org.id),
|
||||||
|
description=f"Organization '{org.name}' updated",
|
||||||
|
)
|
||||||
return api_response(data={"organization": org.to_dict()}, message="Organization updated successfully")
|
return api_response(data={"organization": org.to_dict()}, message="Organization updated successfully")
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
return api_response(success=False, message="Validation failed", status=400, error_type="VALIDATION_ERROR", error_details=e.messages)
|
return api_response(success=False, message="Validation failed", status=400, error_type="VALIDATION_ERROR", error_details=e.messages)
|
||||||
@@ -92,4 +110,12 @@ def delete_organization(org_id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
OrganizationService.force_delete_organization(org=org, user_id=caller.id)
|
OrganizationService.force_delete_organization(org=org, user_id=caller.id)
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.ORG_DELETE,
|
||||||
|
user_id=caller.id,
|
||||||
|
organization_id=org.id,
|
||||||
|
resource_type="organization",
|
||||||
|
resource_id=str(org.id),
|
||||||
|
description=f"Organization '{org.name}' deleted",
|
||||||
|
)
|
||||||
return api_response(message="Organization deleted successfully")
|
return api_response(message="Organization deleted successfully")
|
||||||
|
|||||||
@@ -136,6 +136,15 @@ def cancel_org_invite(org_id, invite_id):
|
|||||||
return api_response(success=False, message="Invite not found", status=404)
|
return api_response(success=False, message="Invite not found", status=404)
|
||||||
|
|
||||||
invite.delete(soft=True)
|
invite.delete(soft=True)
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.ORG_INVITE_CANCELLED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="org_invite",
|
||||||
|
resource_id=invite.id,
|
||||||
|
metadata={"invited_email": invite.email, "role": invite.role},
|
||||||
|
description=f"Invitation for {invite.email} cancelled",
|
||||||
|
)
|
||||||
return api_response(data={}, message="Invite cancelled")
|
return api_response(data={}, message="Invite cancelled")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ from gatehouse_app.utils.decorators import login_required, require_admin, full_a
|
|||||||
from gatehouse_app.schemas.organization_schema import InviteMemberSchema, UpdateMemberRoleSchema
|
from gatehouse_app.schemas.organization_schema import InviteMemberSchema, UpdateMemberRoleSchema
|
||||||
from gatehouse_app.services.organization_service import OrganizationService
|
from gatehouse_app.services.organization_service import OrganizationService
|
||||||
from gatehouse_app.services.user_service import UserService
|
from gatehouse_app.services.user_service import UserService
|
||||||
from gatehouse_app.utils.constants import OrganizationRole
|
from gatehouse_app.utils.constants import AuditAction, OrganizationRole
|
||||||
|
from gatehouse_app.services.audit_service import AuditService
|
||||||
|
|
||||||
|
|
||||||
@api_v1_bp.route("/organizations/<org_id>/members", methods=["GET"])
|
@api_v1_bp.route("/organizations/<org_id>/members", methods=["GET"])
|
||||||
@@ -43,6 +44,14 @@ def add_organization_member(org_id):
|
|||||||
|
|
||||||
role = OrganizationRole(data["role"])
|
role = OrganizationRole(data["role"])
|
||||||
member = OrganizationService.add_member(org=org, user_id=user.id, role=role, inviter_id=g.current_user.id)
|
member = OrganizationService.add_member(org=org, user_id=user.id, role=role, inviter_id=g.current_user.id)
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.ORG_MEMBER_ADD,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org.id,
|
||||||
|
resource_type="user",
|
||||||
|
resource_id=str(user.id),
|
||||||
|
description=f"Added user {user.email} to organization with role {role.value}",
|
||||||
|
)
|
||||||
member_dict = member.to_dict()
|
member_dict = member.to_dict()
|
||||||
member_dict["user"] = user.to_dict()
|
member_dict["user"] = user.to_dict()
|
||||||
return api_response(data={"member": member_dict}, message="Member added successfully", status=201)
|
return api_response(data={"member": member_dict}, message="Member added successfully", status=201)
|
||||||
@@ -60,6 +69,14 @@ def remove_organization_member(org_id, user_id):
|
|||||||
OrganizationService.remove_member(org=org, user_id=user_id, remover_id=g.current_user.id)
|
OrganizationService.remove_member(org=org, user_id=user_id, remover_id=g.current_user.id)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return api_response(success=False, message=str(e), status=403, error_type="OWNER_PROTECTION")
|
return api_response(success=False, message=str(e), status=403, error_type="OWNER_PROTECTION")
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.ORG_MEMBER_REMOVE,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org.id,
|
||||||
|
resource_type="user",
|
||||||
|
resource_id=str(user_id),
|
||||||
|
description=f"Removed user {user_id} from organization",
|
||||||
|
)
|
||||||
return api_response(message="Member removed successfully")
|
return api_response(message="Member removed successfully")
|
||||||
|
|
||||||
|
|
||||||
@@ -74,6 +91,14 @@ def update_member_role(org_id, user_id):
|
|||||||
org = OrganizationService.get_organization_by_id(org_id)
|
org = OrganizationService.get_organization_by_id(org_id)
|
||||||
new_role = OrganizationRole(data["role"])
|
new_role = OrganizationRole(data["role"])
|
||||||
member = OrganizationService.update_member_role(org=org, user_id=user_id, new_role=new_role, updater_id=g.current_user.id)
|
member = OrganizationService.update_member_role(org=org, user_id=user_id, new_role=new_role, updater_id=g.current_user.id)
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.ORG_MEMBER_ROLE_CHANGE,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org.id,
|
||||||
|
resource_type="user",
|
||||||
|
resource_id=str(user_id),
|
||||||
|
description=f"Changed role for user {user_id} to {new_role.value}",
|
||||||
|
)
|
||||||
member_dict = member.to_dict()
|
member_dict = member.to_dict()
|
||||||
member_dict["user"] = member.user.to_dict()
|
member_dict["user"] = member.user.to_dict()
|
||||||
return api_response(data={"member": member_dict}, message="Member role updated successfully")
|
return api_response(data={"member": member_dict}, message="Member role updated successfully")
|
||||||
@@ -180,4 +205,13 @@ def send_mfa_reminder(org_id, user_id):
|
|||||||
html_body=html_body,
|
html_body=html_body,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.ORG_MFA_REMINDER_SENT,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="user",
|
||||||
|
resource_id=str(user_id),
|
||||||
|
description=f"MFA reminder sent to {user.email}",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(data={}, message="Reminder sent successfully")
|
return api_response(data={}, message="Reminder sent successfully")
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ from flask import g, request
|
|||||||
from gatehouse_app.api.v1 import api_v1_bp
|
from gatehouse_app.api.v1 import api_v1_bp
|
||||||
from gatehouse_app.utils.response import api_response
|
from gatehouse_app.utils.response import api_response
|
||||||
from gatehouse_app.utils.decorators import login_required, require_admin, full_access_required
|
from gatehouse_app.utils.decorators import login_required, require_admin, full_access_required
|
||||||
from gatehouse_app.utils.constants import OrganizationRole
|
from gatehouse_app.utils.constants import AuditAction, OrganizationRole
|
||||||
from gatehouse_app.extensions import db
|
from gatehouse_app.extensions import db
|
||||||
|
from gatehouse_app.services.audit_service import AuditService
|
||||||
|
|
||||||
|
|
||||||
@api_v1_bp.route("/organizations/<org_id>/roles", methods=["GET"])
|
@api_v1_bp.route("/organizations/<org_id>/roles", methods=["GET"])
|
||||||
@@ -59,6 +60,16 @@ def assign_role_to_member(org_id, role_name):
|
|||||||
|
|
||||||
membership.role = new_role
|
membership.role = new_role
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.ORG_MEMBER_ROLE_CHANGE,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="user",
|
||||||
|
resource_id=str(target_user_id),
|
||||||
|
description=f"Role changed to {new_role.value} for user {target_user_id}",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(data={"user_id": target_user_id, "role": new_role.value}, message=f"Role updated to {new_role.value}")
|
return api_response(data={"user_id": target_user_id, "role": new_role.value}, message=f"Role updated to {new_role.value}")
|
||||||
|
|
||||||
|
|
||||||
@@ -82,4 +93,14 @@ def remove_role_from_member(org_id, role_name, user_id):
|
|||||||
|
|
||||||
org = OrganizationService.get_organization_by_id(org_id)
|
org = OrganizationService.get_organization_by_id(org_id)
|
||||||
OrganizationService.remove_member(org=org, user_id=user_id, remover_id=g.current_user.id)
|
OrganizationService.remove_member(org=org, user_id=user_id, remover_id=g.current_user.id)
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.ORG_MEMBER_REMOVE,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="user",
|
||||||
|
resource_id=str(user_id),
|
||||||
|
description=f"Member {user_id} removed from organization via role removal",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(data={"user_id": user_id}, message="Member removed from organization")
|
return api_response(data={"user_id": user_id}, message="Member removed from organization")
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ from gatehouse_app.services.organization_service import OrganizationService
|
|||||||
from gatehouse_app.services.user_service import UserService
|
from gatehouse_app.services.user_service import UserService
|
||||||
from gatehouse_app.exceptions import OrganizationNotFoundError
|
from gatehouse_app.exceptions import OrganizationNotFoundError
|
||||||
from gatehouse_app.extensions import db
|
from gatehouse_app.extensions import db
|
||||||
|
from gatehouse_app.utils.constants import AuditAction
|
||||||
|
from gatehouse_app.services.audit_service import AuditService
|
||||||
|
|
||||||
|
|
||||||
class PrincipalCreateSchema(Schema):
|
class PrincipalCreateSchema(Schema):
|
||||||
@@ -127,6 +129,15 @@ def create_principal(org_id):
|
|||||||
db.session.add(principal)
|
db.session.add(principal)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.PRINCIPAL_CREATED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="principal",
|
||||||
|
resource_id=str(principal.id),
|
||||||
|
description=f"Principal '{principal.name}' created",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(
|
return api_response(
|
||||||
data={"principal": principal.to_dict()},
|
data={"principal": principal.to_dict()},
|
||||||
message="Principal created successfully",
|
message="Principal created successfully",
|
||||||
@@ -255,6 +266,15 @@ def update_principal(org_id, principal_id):
|
|||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.PRINCIPAL_UPDATED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="principal",
|
||||||
|
resource_id=str(principal.id),
|
||||||
|
description=f"Principal '{principal.name}' updated",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(
|
return api_response(
|
||||||
data={"principal": principal.to_dict()},
|
data={"principal": principal.to_dict()},
|
||||||
message="Principal updated successfully",
|
message="Principal updated successfully",
|
||||||
@@ -308,6 +328,15 @@ def delete_principal(org_id, principal_id):
|
|||||||
principal.deleted_at = db.func.now()
|
principal.deleted_at = db.func.now()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.PRINCIPAL_DELETED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="principal",
|
||||||
|
resource_id=str(principal.id),
|
||||||
|
description=f"Principal '{principal.name}' deleted",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(
|
return api_response(
|
||||||
message="Principal deleted successfully",
|
message="Principal deleted successfully",
|
||||||
)
|
)
|
||||||
@@ -476,6 +505,15 @@ def add_principal_member(org_id, principal_id):
|
|||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.PRINCIPAL_MEMBER_ADDED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="user",
|
||||||
|
resource_id=str(user.id),
|
||||||
|
description=f"Added user {user.email} to principal '{principal.name}'",
|
||||||
|
)
|
||||||
|
|
||||||
member_dict = membership.to_dict()
|
member_dict = membership.to_dict()
|
||||||
member_dict["user"] = user.to_dict()
|
member_dict["user"] = user.to_dict()
|
||||||
|
|
||||||
@@ -548,6 +586,15 @@ def remove_principal_member(org_id, principal_id, user_id):
|
|||||||
membership.deleted_at = db.func.now()
|
membership.deleted_at = db.func.now()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.PRINCIPAL_MEMBER_REMOVED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="user",
|
||||||
|
resource_id=str(user_id),
|
||||||
|
description=f"Removed user from principal '{principal.name}'",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(
|
return api_response(
|
||||||
message="Member removed successfully",
|
message="Member removed successfully",
|
||||||
)
|
)
|
||||||
@@ -697,6 +744,15 @@ def link_principal_to_department(org_id, principal_id, dept_id):
|
|||||||
error_type="SERVER_ERROR",
|
error_type="SERVER_ERROR",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.PRINCIPAL_DEPARTMENT_LINKED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="principal",
|
||||||
|
resource_id=str(principal_id),
|
||||||
|
description=f"Principal '{principal.name}' linked to department '{dept.name}'",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(
|
return api_response(
|
||||||
data={
|
data={
|
||||||
"principal": principal.to_dict(),
|
"principal": principal.to_dict(),
|
||||||
@@ -774,6 +830,15 @@ def unlink_principal_from_department(org_id, principal_id, dept_id):
|
|||||||
link.deleted_at = db.func.now()
|
link.deleted_at = db.func.now()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
AuditService.log_action(
|
||||||
|
action=AuditAction.PRINCIPAL_DEPARTMENT_UNLINKED,
|
||||||
|
user_id=g.current_user.id,
|
||||||
|
organization_id=org_id,
|
||||||
|
resource_type="principal",
|
||||||
|
resource_id=str(principal_id),
|
||||||
|
description=f"Principal '{principal.name}' unlinked from department '{dept.name}'",
|
||||||
|
)
|
||||||
|
|
||||||
return api_response(
|
return api_response(
|
||||||
message="Principal unlinked from department successfully",
|
message="Principal unlinked from department successfully",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from gatehouse_app.utils.response import api_response
|
|||||||
from gatehouse_app.services.superadmin_auth_service import SuperadminAuthService
|
from gatehouse_app.services.superadmin_auth_service import SuperadminAuthService
|
||||||
from gatehouse_app.decorators.superadmin import superadmin_required, superadmin_audit_log
|
from gatehouse_app.decorators.superadmin import superadmin_required, superadmin_audit_log
|
||||||
from gatehouse_app.exceptions.auth_exceptions import InvalidCredentialsError
|
from gatehouse_app.exceptions.auth_exceptions import InvalidCredentialsError
|
||||||
|
from gatehouse_app.utils.constants import AuditAction
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -105,6 +106,7 @@ def login():
|
|||||||
|
|
||||||
@superadmin_bp.route("/auth/logout", methods=["POST"])
|
@superadmin_bp.route("/auth/logout", methods=["POST"])
|
||||||
@superadmin_required
|
@superadmin_required
|
||||||
|
@superadmin_audit_log(action=AuditAction.USER_LOGOUT, resource_type="session")
|
||||||
def logout():
|
def logout():
|
||||||
"""Superadmin logout endpoint.
|
"""Superadmin logout endpoint.
|
||||||
|
|
||||||
|
|||||||
@@ -154,6 +154,27 @@ class AuditAction(str, Enum):
|
|||||||
DEPARTMENT_DELETED = "department.deleted"
|
DEPARTMENT_DELETED = "department.deleted"
|
||||||
DEPARTMENT_MEMBER_ADDED = "department.member.added"
|
DEPARTMENT_MEMBER_ADDED = "department.member.added"
|
||||||
DEPARTMENT_MEMBER_REMOVED = "department.member.removed"
|
DEPARTMENT_MEMBER_REMOVED = "department.member.removed"
|
||||||
|
DEPARTMENT_CERT_POLICY_UPDATED = "department.cert_policy.updated"
|
||||||
|
|
||||||
|
# Organization invite actions
|
||||||
|
ORG_INVITE_CANCELLED = "org.invite.cancelled"
|
||||||
|
|
||||||
|
# MFA reminder
|
||||||
|
ORG_MFA_REMINDER_SENT = "org.mfa_reminder.sent"
|
||||||
|
|
||||||
|
# API key actions
|
||||||
|
ORG_API_KEY_CREATED = "org.api_key.created"
|
||||||
|
ORG_API_KEY_UPDATED = "org.api_key.updated"
|
||||||
|
ORG_API_KEY_DELETED = "org.api_key.deleted"
|
||||||
|
|
||||||
|
# OIDC client actions
|
||||||
|
ORG_CLIENT_CREATED = "org.client.created"
|
||||||
|
ORG_CLIENT_UPDATED = "org.client.updated"
|
||||||
|
ORG_CLIENT_DEACTIVATED = "org.client.deactivated"
|
||||||
|
|
||||||
|
# Principal department link actions
|
||||||
|
PRINCIPAL_DEPARTMENT_LINKED = "principal.department.linked"
|
||||||
|
PRINCIPAL_DEPARTMENT_UNLINKED = "principal.department.unlinked"
|
||||||
|
|
||||||
|
|
||||||
class OIDCGrantType(str, Enum):
|
class OIDCGrantType(str, Enum):
|
||||||
|
|||||||
Reference in New Issue
Block a user