138 lines
4.8 KiB
Python
138 lines
4.8 KiB
Python
import os
|
|
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
|
|
from fastapi.responses import FileResponse
|
|
from sqlalchemy.orm import Session
|
|
from database import get_db
|
|
from utils.auth import verify_token
|
|
from models import UserProfile
|
|
from schemas import UserProfile as UserProfileSchema, UserProfileCreate
|
|
from services import pki, generator
|
|
from datetime import datetime
|
|
|
|
router = APIRouter(dependencies=[Depends(verify_token)])
|
|
|
|
@router.get("/profiles", response_model=list[UserProfileSchema])
|
|
def list_profiles(db: Session = Depends(get_db)):
|
|
# 1. Fetch profiles from DB
|
|
profiles = db.query(UserProfile).all()
|
|
|
|
# 2. Get PKI Data (Index mapping: CN -> Expiration Date)
|
|
pki_data = pki.get_pki_index_data(db)
|
|
|
|
now = datetime.utcnow()
|
|
|
|
updated_profiles = []
|
|
|
|
for profile in profiles:
|
|
# Sync expiration if available in PKI data
|
|
if profile.username in pki_data:
|
|
exp_date = pki_data[profile.username]
|
|
# Update DB if different
|
|
if profile.expiration_date != exp_date:
|
|
profile.expiration_date = exp_date
|
|
db.add(profile)
|
|
|
|
# Calculate derived fields
|
|
|
|
# 1. is_expired
|
|
is_expired = False
|
|
if profile.expiration_date:
|
|
if now > profile.expiration_date:
|
|
is_expired = True
|
|
|
|
# 2. is_revoked
|
|
# (Assuming status='revoked' in DB is the source of truth)
|
|
is_revoked = profile.status == 'revoked'
|
|
|
|
# 3. days_remaining (computed field)
|
|
days_remaining = None
|
|
if profile.expiration_date:
|
|
delta = profile.expiration_date - now
|
|
days_remaining = delta.days
|
|
|
|
# Update DB fields for persistence if they differ
|
|
if profile.is_expired != is_expired:
|
|
profile.is_expired = is_expired
|
|
db.add(profile)
|
|
|
|
if profile.is_revoked != is_revoked:
|
|
profile.is_revoked = is_revoked
|
|
db.add(profile)
|
|
|
|
# Inject computed fields for response schema
|
|
# Since 'days_remaining' is not a DB column, we attach it to the object instance
|
|
setattr(profile, 'days_remaining', days_remaining)
|
|
|
|
updated_profiles.append(profile)
|
|
|
|
db.commit() # Save any updates
|
|
|
|
return updated_profiles
|
|
|
|
@router.post("/profiles", response_model=UserProfileSchema)
|
|
def create_profile(
|
|
profile_in: UserProfileCreate,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
# Check existing
|
|
existing = db.query(UserProfile).filter(UserProfile.username == profile_in.username).first()
|
|
if existing:
|
|
raise HTTPException(status_code=400, detail="User already exists")
|
|
|
|
# Build PKI
|
|
try:
|
|
pki.build_client(profile_in.username, db)
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"PKI Build failed: {str(e)}")
|
|
|
|
# Generate Config
|
|
client_conf_dir = "client-config"
|
|
os.makedirs(client_conf_dir, exist_ok=True)
|
|
file_path = os.path.join(client_conf_dir, f"{profile_in.username}.ovpn")
|
|
|
|
try:
|
|
generator.generate_client_config(db, profile_in.username, file_path)
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Config Generation failed: {str(e)}")
|
|
|
|
# Create DB Entry
|
|
new_profile = UserProfile(
|
|
username=profile_in.username,
|
|
status="active",
|
|
created_at=datetime.utcnow(),
|
|
file_path=file_path
|
|
# expired_at would be extracted from cert in a real robust implementation
|
|
)
|
|
db.add(new_profile)
|
|
db.commit()
|
|
db.refresh(new_profile)
|
|
return new_profile
|
|
|
|
@router.delete("/profiles/{profile_id}")
|
|
def revoke_profile(profile_id: int, db: Session = Depends(get_db)):
|
|
profile = db.query(UserProfile).filter(UserProfile.id == profile_id).first()
|
|
if not profile:
|
|
raise HTTPException(status_code=404, detail="Profile not found")
|
|
|
|
try:
|
|
pki.revoke_client(profile.username, db)
|
|
except Exception as e:
|
|
# Log but maybe continue to update DB status?
|
|
raise HTTPException(status_code=500, detail=f"Revocation failed: {str(e)}")
|
|
|
|
profile.status = "revoked"
|
|
profile.revoked_at = datetime.utcnow()
|
|
db.commit()
|
|
return {"message": f"Profile {profile.username} revoked"}
|
|
|
|
@router.get("/profiles/{profile_id}/download")
|
|
def download_profile(profile_id: int, db: Session = Depends(get_db)):
|
|
profile = db.query(UserProfile).filter(UserProfile.id == profile_id).first()
|
|
if not profile:
|
|
raise HTTPException(status_code=404, detail="Profile not found")
|
|
|
|
if not profile.file_path or not os.path.exists(profile.file_path):
|
|
raise HTTPException(status_code=404, detail="Config file not found")
|
|
|
|
return FileResponse(profile.file_path, filename=os.path.basename(profile.file_path))
|