mirror of
https://github.com/eduardogsilva/wireguard_webadmin.git
synced 2026-02-19 19:26:17 +00:00
Add API endpoints for listing peers and retrieving peer details
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views_api import api_v2_manage_peer
|
from .views_api import api_v2_manage_peer, api_v2_peer_list, api_v2_peer_detail
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('manage_peer/', api_v2_manage_peer, name='api_v2_manage_peer'),
|
path('manage_peer/', api_v2_manage_peer, name='api_v2_manage_peer'),
|
||||||
|
path('peer_list/', api_v2_peer_list, name='api_v2_peer_list'),
|
||||||
|
path('peer_detail/', api_v2_peer_detail, name='api_v2_peer_detail'),
|
||||||
]
|
]
|
||||||
@@ -29,20 +29,21 @@ def api_doc(*, summary: str, auth: str, params: list, returns: list, examples: O
|
|||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
return view_func(*args, **kwargs)
|
return view_func(*args, **kwargs)
|
||||||
|
|
||||||
wrapper.__dict__.update(getattr(view_func, "__dict__", {}))
|
|
||||||
wrapper.api_doc = view_func.api_doc
|
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def validate_api_key(request, wireguard_instance: WireGuardInstance):
|
def validate_api_key(request, wireguard_instance: WireGuardInstance | None = None):
|
||||||
"""
|
"""
|
||||||
Validates the API key and checks whether it can manage the given instance.
|
Validates the API token and optionally validates access to a given WireGuard instance.
|
||||||
|
|
||||||
Rule:
|
Rules:
|
||||||
- If ApiKey.allowed_instances is empty => key can manage any instance.
|
- token must exist and be enabled
|
||||||
- Otherwise, wireguard_instance must be included in ApiKey.allowed_instances.
|
- if ApiKey.allowed_instances is empty => key can access any instance
|
||||||
|
- otherwise, wireguard_instance must be included in ApiKey.allowed_instances
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- If wireguard_instance is None, only validates the token (no instance scoping).
|
||||||
"""
|
"""
|
||||||
token = request.headers.get("token")
|
token = request.headers.get("token")
|
||||||
if not token:
|
if not token:
|
||||||
@@ -53,13 +54,13 @@ def validate_api_key(request, wireguard_instance: WireGuardInstance):
|
|||||||
except ApiKey.DoesNotExist:
|
except ApiKey.DoesNotExist:
|
||||||
return None, "Invalid API key."
|
return None, "Invalid API key."
|
||||||
|
|
||||||
if api_key.allowed_instances.exists():
|
if wireguard_instance is not None:
|
||||||
if not api_key.allowed_instances.filter(uuid=wireguard_instance.uuid).exists():
|
if api_key.allowed_instances.exists():
|
||||||
return None, "This API key is not allowed to manage the requested instance."
|
if not api_key.allowed_instances.filter(uuid=wireguard_instance.uuid).exists():
|
||||||
|
return None, "This API key is not allowed to access the requested instance."
|
||||||
|
|
||||||
return api_key, ""
|
return api_key, ""
|
||||||
|
|
||||||
|
|
||||||
def _parse_ipv4_cidrs(value) -> Tuple[Optional[List[Tuple[str, int]]], Optional[str]]:
|
def _parse_ipv4_cidrs(value) -> Tuple[Optional[List[Tuple[str, int]]], Optional[str]]:
|
||||||
"""
|
"""
|
||||||
Parses a list of CIDR strings into [(allowed_ip, netmask), ...].
|
Parses a list of CIDR strings into [(allowed_ip, netmask), ...].
|
||||||
@@ -161,6 +162,7 @@ def _get_wireguard_instance(instance_name: str) -> Optional[WireGuardInstance]:
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
@api_doc(
|
@api_doc(
|
||||||
summary="Create / Update / Delete a WireGuard peer (and optionally reload the interface)",
|
summary="Create / Update / Delete a WireGuard peer (and optionally reload the interface)",
|
||||||
auth="Header token: <ApiKey.token>",
|
auth="Header token: <ApiKey.token>",
|
||||||
@@ -227,7 +229,6 @@ def _get_wireguard_instance(instance_name: str) -> Optional[WireGuardInstance]:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@csrf_exempt
|
|
||||||
def api_v2_manage_peer(request):
|
def api_v2_manage_peer(request):
|
||||||
if request.method not in ("POST", "PUT", "DELETE"):
|
if request.method not in ("POST", "PUT", "DELETE"):
|
||||||
return JsonResponse({"status": "error", "error_message": "Method not allowed."}, status=405)
|
return JsonResponse({"status": "error", "error_message": "Method not allowed."}, status=405)
|
||||||
@@ -443,3 +444,173 @@ def api_v2_manage_peer(request):
|
|||||||
},
|
},
|
||||||
status=200
|
status=200
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
@api_doc(
|
||||||
|
summary="List peers for a specific instance (required)",
|
||||||
|
auth="Header token: <ApiKey.token>",
|
||||||
|
params=[
|
||||||
|
{"name": "instance", "in": "json", "type": "string", "required": True, "example": "wg2",
|
||||||
|
"description": "Required. Target instance name in the format wg{instance_id} (e.g. wg0, wg1)."},
|
||||||
|
],
|
||||||
|
returns=[
|
||||||
|
{"status": 200, "body": {"status": "success", "instance": "wg2", "peers": [{"uuid": "...", "public_key": "..."}]}},
|
||||||
|
{"status": 400, "body": {"status": "error", "error_message": "Invalid or missing WireGuard instance."}},
|
||||||
|
{"status": 403, "body": {"status": "error", "error_message": "Invalid API key."}},
|
||||||
|
],
|
||||||
|
examples={
|
||||||
|
"list_wg2": {"method": "POST", "json": {"instance": "wg2"}},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def api_v2_peer_list(request):
|
||||||
|
if request.method not in ("POST", "GET"):
|
||||||
|
return JsonResponse({"status": "error", "error_message": "Method not allowed."}, status=405)
|
||||||
|
|
||||||
|
payload = {}
|
||||||
|
if request.method == "POST":
|
||||||
|
try:
|
||||||
|
payload = json.loads(request.body.decode("utf-8")) if request.body else {}
|
||||||
|
except Exception:
|
||||||
|
return JsonResponse({"status": "error", "error_message": "Invalid JSON body."}, status=400)
|
||||||
|
else:
|
||||||
|
payload = request.GET.dict()
|
||||||
|
|
||||||
|
try:
|
||||||
|
wireguard_instance = WireGuardInstance.objects.get(
|
||||||
|
instance_id=int(str(payload.get("instance")).replace("wg", ""))
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
wireguard_instance = None
|
||||||
|
|
||||||
|
if not wireguard_instance:
|
||||||
|
return JsonResponse({"status": "error", "error_message": "Invalid or missing WireGuard instance."}, status=400)
|
||||||
|
|
||||||
|
api_key, api_error = validate_api_key(request, wireguard_instance=wireguard_instance)
|
||||||
|
if not api_key:
|
||||||
|
return JsonResponse({"status": "error", "error_message": api_error}, status=403)
|
||||||
|
|
||||||
|
peer_qs = (
|
||||||
|
Peer.objects
|
||||||
|
.filter(wireguard_instance=wireguard_instance)
|
||||||
|
.prefetch_related("peerallowedip_set")
|
||||||
|
.order_by("sort_order", "name", "public_key")
|
||||||
|
)
|
||||||
|
|
||||||
|
peers = []
|
||||||
|
for current_peer in peer_qs:
|
||||||
|
peers.append({
|
||||||
|
"uuid": str(current_peer.uuid),
|
||||||
|
"name": current_peer.name or "",
|
||||||
|
"public_key": current_peer.public_key,
|
||||||
|
"suspended": bool(current_peer.suspended),
|
||||||
|
"suspend_reason": current_peer.suspend_reason or "",
|
||||||
|
"disabled_by_schedule": bool(current_peer.disabled_by_schedule),
|
||||||
|
"main_addresses": current_peer.main_addresses,
|
||||||
|
})
|
||||||
|
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"instance": f"wg{wireguard_instance.instance_id}",
|
||||||
|
"peers": peers,
|
||||||
|
},
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
@api_doc(
|
||||||
|
summary="Peer details for a specific instance (required) by peer_uuid or peer_public_key",
|
||||||
|
auth="Header token: <ApiKey.token>",
|
||||||
|
params=[
|
||||||
|
{"name": "instance", "in": "json", "type": "string", "required": True, "example": "wg2",
|
||||||
|
"description": "Required. Target instance name in the format wg{instance_id} (e.g. wg0, wg1)."},
|
||||||
|
{"name": "peer_uuid", "in": "json", "type": "string", "required": False,
|
||||||
|
"description": "Peer UUID selector."},
|
||||||
|
{"name": "peer_public_key", "in": "json", "type": "string", "required": False,
|
||||||
|
"description": "Peer public key selector."},
|
||||||
|
],
|
||||||
|
returns=[
|
||||||
|
{"status": 200, "body": {"status": "success", "peer": {"uuid": "...", "name": "...", "public_key": "..."}}},
|
||||||
|
{"status": 400, "body": {"status": "error", "error_message": "Missing peer selector (peer_uuid or peer_public_key)."}},
|
||||||
|
{"status": 404, "body": {"status": "error", "error_message": "Peer not found."}},
|
||||||
|
{"status": 403, "body": {"status": "error", "error_message": "Invalid API key."}},
|
||||||
|
],
|
||||||
|
examples={
|
||||||
|
"detail_by_uuid": {"method": "POST", "json": {"instance": "wg2", "peer_uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"}},
|
||||||
|
"detail_by_public_key": {"method": "POST", "json": {"instance": "wg2", "peer_public_key": "BASE64PUBLICKEY..."}},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def api_v2_peer_detail(request):
|
||||||
|
if request.method not in ("POST", "GET"):
|
||||||
|
return JsonResponse({"status": "error", "error_message": "Method not allowed."}, status=405)
|
||||||
|
|
||||||
|
payload = {}
|
||||||
|
if request.method == "POST":
|
||||||
|
try:
|
||||||
|
payload = json.loads(request.body.decode("utf-8")) if request.body else {}
|
||||||
|
except Exception:
|
||||||
|
return JsonResponse({"status": "error", "error_message": "Invalid JSON body."}, status=400)
|
||||||
|
else:
|
||||||
|
payload = request.GET.dict()
|
||||||
|
|
||||||
|
try:
|
||||||
|
wireguard_instance = WireGuardInstance.objects.get(
|
||||||
|
instance_id=int(str(payload.get("instance")).replace("wg", ""))
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
wireguard_instance = None
|
||||||
|
|
||||||
|
if not wireguard_instance:
|
||||||
|
return JsonResponse({"status": "error", "error_message": "Invalid or missing WireGuard instance."}, status=400)
|
||||||
|
|
||||||
|
api_key, api_error = validate_api_key(request, wireguard_instance=wireguard_instance)
|
||||||
|
if not api_key:
|
||||||
|
return JsonResponse({"status": "error", "error_message": api_error}, status=403)
|
||||||
|
|
||||||
|
selector_peer_uuid = payload.get("peer_uuid")
|
||||||
|
selector_peer_public_key = payload.get("peer_public_key")
|
||||||
|
|
||||||
|
if not selector_peer_uuid and not selector_peer_public_key:
|
||||||
|
return JsonResponse(
|
||||||
|
{"status": "error", "error_message": "Missing peer selector (peer_uuid or peer_public_key)."},
|
||||||
|
status=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
peer_qs = (
|
||||||
|
Peer.objects
|
||||||
|
.filter(wireguard_instance=wireguard_instance)
|
||||||
|
.select_related("routing_template", "wireguard_instance")
|
||||||
|
.prefetch_related("peerallowedip_set")
|
||||||
|
)
|
||||||
|
|
||||||
|
if selector_peer_uuid:
|
||||||
|
current_peer = peer_qs.filter(uuid=selector_peer_uuid).first()
|
||||||
|
else:
|
||||||
|
current_peer = peer_qs.filter(public_key=selector_peer_public_key).first()
|
||||||
|
|
||||||
|
if not current_peer:
|
||||||
|
return JsonResponse({"status": "error", "error_message": "Peer not found."}, status=404)
|
||||||
|
|
||||||
|
peer_data = {
|
||||||
|
"uuid": str(current_peer.uuid),
|
||||||
|
"name": current_peer.name or "",
|
||||||
|
"public_key": current_peer.public_key,
|
||||||
|
"pre_shared_key": current_peer.pre_shared_key,
|
||||||
|
"private_key": current_peer.private_key or "",
|
||||||
|
"persistent_keepalive": int(current_peer.persistent_keepalive),
|
||||||
|
"routing_template_uuid": str(current_peer.routing_template.uuid) if current_peer.routing_template else "",
|
||||||
|
"suspended": bool(current_peer.suspended),
|
||||||
|
"suspend_reason": current_peer.suspend_reason or "",
|
||||||
|
"disabled_by_schedule": bool(current_peer.disabled_by_schedule),
|
||||||
|
"enabled": bool(current_peer.enabled),
|
||||||
|
"main_addresses": current_peer.main_addresses,
|
||||||
|
"announced_networks": current_peer.announced_networks,
|
||||||
|
"client_routes": current_peer.client_routes,
|
||||||
|
"instance": f"wg{wireguard_instance.instance_id}",
|
||||||
|
"instance_uuid": str(wireguard_instance.uuid),
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonResponse({"status": "success", "peer": peer_data}, status=200)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user