openvpn-endpoint-server/server/app/api/gateways.py

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
}