Updated ZeroTier network membership flow and logic
This commit is contained in:
@@ -924,16 +924,25 @@ def admin_list_memberships(org_id):
|
||||
@require_admin
|
||||
@full_access_required
|
||||
def admin_delete_membership(org_id, membership_id):
|
||||
"""Hard-delete a membership and remove it from ZeroTier (admin only)."""
|
||||
"""Force-delete a membership and remove it from ZeroTier (admin only).
|
||||
|
||||
Handles the full lifecycle: deactivates if active, removes the member
|
||||
from the ZeroTier controller, and hard-deletes the DB record.
|
||||
"""
|
||||
org, err = _org_check(org_id)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
network_access_service.hard_delete_request(membership_id)
|
||||
network_access_service.admin_force_delete_request(
|
||||
membership_id,
|
||||
admin_user_id=g.current_user.id,
|
||||
)
|
||||
return api_response(message="Request permanently deleted")
|
||||
except ApprovalNotFoundError as e:
|
||||
return api_response(success=False, message=str(e), status=404, error_type=e.error_type)
|
||||
except AppValidationError as e:
|
||||
return api_response(success=False, message=str(e.message), status=400, error_type=e.error_type)
|
||||
|
||||
|
||||
# ── ZeroTier Controller ───────────────────────────────────────────────────────
|
||||
|
||||
@@ -760,7 +760,7 @@ def join_network_for_device(
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
if existing.status in (ApprovalState.APPROVED, ApprovalState.PENDING):
|
||||
if existing.status == ApprovalState.PENDING or (existing.status == ApprovalState.APPROVED and existing.active):
|
||||
raise ApprovalAlreadyExistsError("Already have access or pending request.")
|
||||
# Re-open
|
||||
existing.status = ApprovalState.APPROVED
|
||||
@@ -861,3 +861,62 @@ def hard_delete_request(
|
||||
|
||||
db.session.delete(request)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def admin_force_delete_request(
|
||||
request_id: str,
|
||||
admin_user_id: str,
|
||||
) -> None:
|
||||
"""Force-delete a network access request (admin only).
|
||||
|
||||
Handles the full lifecycle: deactivates if active, removes the member
|
||||
from the ZeroTier controller entirely, then hard-deletes the DB record.
|
||||
Does NOT require the request to be soft-deleted first.
|
||||
"""
|
||||
request = NetworkAccessRequest.query.filter(
|
||||
NetworkAccessRequest.id == request_id,
|
||||
).first()
|
||||
if not request:
|
||||
raise ApprovalNotFoundError(f"Request {request_id} not found.")
|
||||
|
||||
# Deactivate if active
|
||||
if request.active:
|
||||
deactivate_request(
|
||||
request_id,
|
||||
reason="manual_revoke",
|
||||
deactivated_by_user_id=admin_user_id,
|
||||
)
|
||||
|
||||
# Remove from ZeroTier controller entirely
|
||||
device = Device.query.get(request.device_id)
|
||||
network = PortalNetwork.query.get(request.portal_network_id)
|
||||
if device and network:
|
||||
try:
|
||||
zt.delete_network_member(
|
||||
network.zerotier_network_id,
|
||||
device.node_id,
|
||||
organization_id=request.organization_id,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
f"[admin_force_delete] Could not remove {device.node_id} "
|
||||
f"from ZT network {network.zerotier_network_id}: {exc}"
|
||||
)
|
||||
|
||||
# Hard-delete from DB
|
||||
db.session.delete(request)
|
||||
db.session.commit()
|
||||
|
||||
AuditService.log_action(
|
||||
action=AuditAction.ZT_REQUEST_REVOKED,
|
||||
user_id=admin_user_id,
|
||||
organization_id=request.organization_id,
|
||||
resource_type="network_access_request",
|
||||
resource_id=request.id,
|
||||
metadata={
|
||||
"target_user_id": request.user_id,
|
||||
"force_delete": True,
|
||||
},
|
||||
description=f"Network access request force-deleted by admin for user {request.user_id}",
|
||||
success=True,
|
||||
)
|
||||
|
||||
@@ -304,16 +304,15 @@ def delete_network(network_id: str, user_id: str) -> None:
|
||||
|
||||
|
||||
def get_network_members(network_id: str) -> list:
|
||||
"""Return all approved and active NetworkAccessRequests for a network."""
|
||||
"""Return all approved NetworkAccessRequests for a network (active or inactive)."""
|
||||
from gatehouse_app.models import NetworkAccessRequest
|
||||
from gatehouse_app.utils.constants import ApprovalState
|
||||
|
||||
return NetworkAccessRequest.query.filter(
|
||||
NetworkAccessRequest.portal_network_id == network_id,
|
||||
NetworkAccessRequest.status == ApprovalState.APPROVED,
|
||||
NetworkAccessRequest.active == True,
|
||||
NetworkAccessRequest.deleted_at.is_(None),
|
||||
).all()
|
||||
).order_by(NetworkAccessRequest.created_at.desc()).all()
|
||||
|
||||
|
||||
def get_network_pending_requests(network_id: str) -> list:
|
||||
|
||||
Reference in New Issue
Block a user