336 lines
11 KiB
Python
336 lines
11 KiB
Python
"""Gateway management API routes."""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status, Response
|
|
from sqlalchemy.orm import Session
|
|
from ..database import get_db
|
|
from ..models.gateway import Gateway
|
|
from ..models.user import User, UserRole
|
|
from ..models.access import UserGatewayAccess
|
|
from ..models.vpn_connection_log import VPNConnectionLog
|
|
from ..schemas.gateway import GatewayCreate, GatewayUpdate, GatewayResponse, GatewayStatus
|
|
from ..services.vpn_sync_service import VPNSyncService
|
|
from .deps import get_current_user, require_admin
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def get_accessible_gateways_query(db: Session, user: User):
|
|
"""Get query for gateways accessible by user."""
|
|
if user.role == UserRole.SUPER_ADMIN:
|
|
return db.query(Gateway)
|
|
elif user.role == UserRole.ADMIN:
|
|
return db.query(Gateway).filter(Gateway.tenant_id == user.tenant_id)
|
|
else:
|
|
# Technicians and viewers only see assigned gateways
|
|
return db.query(Gateway).join(
|
|
UserGatewayAccess,
|
|
UserGatewayAccess.gateway_id == Gateway.id
|
|
).filter(UserGatewayAccess.user_id == user.id)
|
|
|
|
|
|
@router.get("/", response_model=list[GatewayResponse])
|
|
def list_gateways(
|
|
skip: int = 0,
|
|
limit: int = 100,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""List gateways accessible by current user."""
|
|
query = get_accessible_gateways_query(db, current_user)
|
|
gateways = query.offset(skip).limit(limit).all()
|
|
return gateways
|
|
|
|
|
|
@router.get("/status", response_model=list[GatewayStatus])
|
|
def get_gateways_status(
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get online status of all accessible gateways."""
|
|
query = get_accessible_gateways_query(db, current_user)
|
|
gateways = query.all()
|
|
|
|
return [
|
|
GatewayStatus(
|
|
id=gw.id,
|
|
name=gw.name,
|
|
is_online=gw.is_online,
|
|
last_seen=gw.last_seen,
|
|
vpn_ip=gw.vpn_ip
|
|
)
|
|
for gw in gateways
|
|
]
|
|
|
|
|
|
@router.post("/", response_model=GatewayResponse, status_code=status.HTTP_201_CREATED)
|
|
def create_gateway(
|
|
gateway_data: GatewayCreate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_admin)
|
|
):
|
|
"""Create a new gateway (admin only)."""
|
|
# Determine tenant_id
|
|
if current_user.role == UserRole.SUPER_ADMIN:
|
|
# Super admin must specify tenant_id via query param or we use a default
|
|
tenant_id = current_user.tenant_id or 1 # Fallback
|
|
else:
|
|
tenant_id = current_user.tenant_id
|
|
|
|
# Generate VPN certificate CN
|
|
vpn_cert_cn = f"gateway-{gateway_data.name.lower().replace(' ', '-')}"
|
|
|
|
gateway = Gateway(
|
|
**gateway_data.model_dump(),
|
|
tenant_id=tenant_id,
|
|
vpn_cert_cn=vpn_cert_cn
|
|
)
|
|
db.add(gateway)
|
|
db.commit()
|
|
db.refresh(gateway)
|
|
return gateway
|
|
|
|
|
|
@router.get("/{gateway_id}", response_model=GatewayResponse)
|
|
def get_gateway(
|
|
gateway_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get gateway by ID."""
|
|
query = get_accessible_gateways_query(db, current_user)
|
|
gateway = query.filter(Gateway.id == gateway_id).first()
|
|
|
|
if not gateway:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Gateway not found or access denied"
|
|
)
|
|
|
|
return gateway
|
|
|
|
|
|
@router.put("/{gateway_id}", response_model=GatewayResponse)
|
|
def update_gateway(
|
|
gateway_id: int,
|
|
gateway_data: GatewayUpdate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_admin)
|
|
):
|
|
"""Update gateway (admin only)."""
|
|
gateway = db.query(Gateway).filter(Gateway.id == gateway_id).first()
|
|
if not gateway:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Gateway not found"
|
|
)
|
|
|
|
# Check tenant access
|
|
if current_user.role != UserRole.SUPER_ADMIN:
|
|
if gateway.tenant_id != current_user.tenant_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Cannot modify gateways from other tenants"
|
|
)
|
|
|
|
update_data = gateway_data.model_dump(exclude_unset=True)
|
|
for field, value in update_data.items():
|
|
setattr(gateway, field, value)
|
|
|
|
db.commit()
|
|
db.refresh(gateway)
|
|
return gateway
|
|
|
|
|
|
@router.delete("/{gateway_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
def delete_gateway(
|
|
gateway_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_admin)
|
|
):
|
|
"""Delete gateway (admin only)."""
|
|
gateway = db.query(Gateway).filter(Gateway.id == gateway_id).first()
|
|
if not gateway:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Gateway not found"
|
|
)
|
|
|
|
# Check tenant access
|
|
if current_user.role != UserRole.SUPER_ADMIN:
|
|
if gateway.tenant_id != current_user.tenant_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Cannot delete gateways from other tenants"
|
|
)
|
|
|
|
db.delete(gateway)
|
|
db.commit()
|
|
|
|
|
|
@router.get("/{gateway_id}/provision")
|
|
def download_provisioning_package(
|
|
gateway_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_admin)
|
|
):
|
|
"""Download provisioning package for gateway.
|
|
|
|
DEPRECATED: Use VPN profiles for provisioning.
|
|
GET /api/gateways/{gateway_id}/profiles/{profile_id}/provision
|
|
"""
|
|
gateway = db.query(Gateway).filter(Gateway.id == gateway_id).first()
|
|
if not gateway:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Gateway not found"
|
|
)
|
|
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Provisioning is now done through VPN profiles. "
|
|
"Please create a VPN profile for this gateway first, "
|
|
"then use GET /api/gateways/{gateway_id}/profiles/{profile_id}/provision"
|
|
)
|
|
|
|
|
|
@router.post("/{gateway_id}/access/{user_id}", status_code=status.HTTP_201_CREATED)
|
|
def grant_gateway_access(
|
|
gateway_id: int,
|
|
user_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_admin)
|
|
):
|
|
"""Grant user access to gateway."""
|
|
gateway = db.query(Gateway).filter(Gateway.id == gateway_id).first()
|
|
if not gateway:
|
|
raise HTTPException(status_code=404, detail="Gateway not found")
|
|
|
|
user = db.query(User).filter(User.id == user_id).first()
|
|
if not user:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
|
|
# Check existing access
|
|
existing = db.query(UserGatewayAccess).filter(
|
|
UserGatewayAccess.gateway_id == gateway_id,
|
|
UserGatewayAccess.user_id == user_id
|
|
).first()
|
|
|
|
if existing:
|
|
raise HTTPException(status_code=400, detail="Access already granted")
|
|
|
|
access = UserGatewayAccess(
|
|
user_id=user_id,
|
|
gateway_id=gateway_id,
|
|
granted_by_id=current_user.id
|
|
)
|
|
db.add(access)
|
|
db.commit()
|
|
|
|
return {"message": "Access granted"}
|
|
|
|
|
|
@router.delete("/{gateway_id}/access/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
def revoke_gateway_access(
|
|
gateway_id: int,
|
|
user_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_admin)
|
|
):
|
|
"""Revoke user access to gateway."""
|
|
access = db.query(UserGatewayAccess).filter(
|
|
UserGatewayAccess.gateway_id == gateway_id,
|
|
UserGatewayAccess.user_id == user_id
|
|
).first()
|
|
|
|
if not access:
|
|
raise HTTPException(status_code=404, detail="Access not found")
|
|
|
|
db.delete(access)
|
|
db.commit()
|
|
|
|
|
|
@router.get("/{gateway_id}/vpn-logs")
|
|
def get_gateway_vpn_logs(
|
|
gateway_id: int,
|
|
limit: int = 50,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get VPN connection logs for a gateway."""
|
|
# Check access
|
|
query = get_accessible_gateways_query(db, current_user)
|
|
gateway = query.filter(Gateway.id == gateway_id).first()
|
|
|
|
if not gateway:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Gateway not found or access denied"
|
|
)
|
|
|
|
sync_service = VPNSyncService(db)
|
|
logs = sync_service.get_gateway_connection_logs(gateway_id, limit=limit)
|
|
|
|
return [
|
|
{
|
|
"id": log.id,
|
|
"profile_id": log.vpn_profile_id,
|
|
"profile_name": log.vpn_profile.name if log.vpn_profile else None,
|
|
"server_id": log.vpn_server_id,
|
|
"server_name": log.vpn_server.name if log.vpn_server else None,
|
|
"common_name": log.common_name,
|
|
"real_address": log.real_address,
|
|
"vpn_ip": log.vpn_ip,
|
|
"connected_at": log.connected_at.isoformat() if log.connected_at else None,
|
|
"disconnected_at": log.disconnected_at.isoformat() if log.disconnected_at else None,
|
|
"duration_seconds": log.duration_seconds,
|
|
"bytes_received": log.bytes_received,
|
|
"bytes_sent": log.bytes_sent,
|
|
"is_active": log.is_active
|
|
}
|
|
for log in logs
|
|
]
|
|
|
|
|
|
@router.get("/{gateway_id}/vpn-status")
|
|
def get_gateway_vpn_status(
|
|
gateway_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get current VPN connection status for a gateway."""
|
|
# Check access
|
|
query = get_accessible_gateways_query(db, current_user)
|
|
gateway = query.filter(Gateway.id == gateway_id).first()
|
|
|
|
if not gateway:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Gateway not found or access denied"
|
|
)
|
|
|
|
# Sync connections first
|
|
sync_service = VPNSyncService(db)
|
|
sync_service.sync_all_connections()
|
|
|
|
# Get active connection
|
|
active_log = db.query(VPNConnectionLog).filter(
|
|
VPNConnectionLog.gateway_id == gateway_id,
|
|
VPNConnectionLog.disconnected_at.is_(None)
|
|
).first()
|
|
|
|
if active_log:
|
|
return {
|
|
"is_connected": True,
|
|
"profile_name": active_log.vpn_profile.name if active_log.vpn_profile else None,
|
|
"server_name": active_log.vpn_server.name if active_log.vpn_server else None,
|
|
"real_address": active_log.real_address,
|
|
"connected_since": active_log.connected_at.isoformat() if active_log.connected_at else None,
|
|
"bytes_received": active_log.bytes_received,
|
|
"bytes_sent": active_log.bytes_sent
|
|
}
|
|
else:
|
|
return {
|
|
"is_connected": False,
|
|
"last_connection": gateway.last_seen.isoformat() if gateway.last_seen else None
|
|
}
|