mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 14:01:15 +02:00
Refactor tests and start work on database integration.
This commit is contained in:
1 parent
ae55c0ec9c
commit
bf7cf862af
69 files changed
+2955
-1877
No files matched your search
@@ -35,7 +35,7 @@ router = APIRouter()
|
||||
@router.get("/capabilities",
|
||||
response_model=schemas.Capabilities
|
||||
)
|
||||
def get_compute_capabilities():
|
||||
def get_capabilities():
|
||||
|
||||
node_types = []
|
||||
for module in MODULES:
|
||||
|
||||
@@ -104,7 +104,7 @@ def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep_node)
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def delete_node(node: Cloud = Depends(dep_node)):
|
||||
async def delete_cloud(node: Cloud = Depends(dep_node)):
|
||||
"""
|
||||
Delete a cloud node.
|
||||
"""
|
||||
@@ -151,10 +151,10 @@ async def suspend_cloud(node: Cloud = Depends(dep_node)):
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
responses=responses)
|
||||
async def create_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: Cloud = Depends(dep_node)):
|
||||
async def create_cloud_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: Cloud = Depends(dep_node)):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
@@ -169,10 +169,10 @@ async def create_nio(adapter_number: int,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
responses=responses)
|
||||
async def update_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: Cloud = Depends(dep_node)):
|
||||
async def update_cloud_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: Cloud = Depends(dep_node)):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) to the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
@@ -188,7 +188,7 @@ async def update_nio(adapter_number: int,
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def delete_nio(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)):
|
||||
async def delete_cloud_nio(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)):
|
||||
"""
|
||||
Remove a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
@@ -199,10 +199,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: Cloud = Depend
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||
responses=responses)
|
||||
async def start_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: Cloud = Depends(dep_node)):
|
||||
async def start_cloud_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: Cloud = Depends(dep_node)):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
@@ -216,7 +216,7 @@ async def start_capture(adapter_number: int,
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def stop_capture(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)):
|
||||
async def stop_cloud_capture(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
|
||||
@@ -78,7 +78,7 @@ def network_ports() -> dict:
|
||||
|
||||
|
||||
@router.get("/version")
|
||||
def version() -> dict:
|
||||
def compute_version() -> dict:
|
||||
"""
|
||||
Retrieve the server version number.
|
||||
"""
|
||||
@@ -89,7 +89,7 @@ def version() -> dict:
|
||||
|
||||
|
||||
@router.get("/statistics")
|
||||
def statistics() -> dict:
|
||||
def compute_statistics() -> dict:
|
||||
"""
|
||||
Retrieve the server version number.
|
||||
"""
|
||||
@@ -124,19 +124,19 @@ def statistics() -> dict:
|
||||
|
||||
|
||||
@router.get("/qemu/binaries")
|
||||
async def get_binaries(archs: Optional[List[str]] = Body(None, embed=True)):
|
||||
async def get_qemu_binaries(archs: Optional[List[str]] = Body(None, embed=True)):
|
||||
|
||||
return await Qemu.binary_list(archs)
|
||||
|
||||
|
||||
@router.get("/qemu/img-binaries")
|
||||
async def get_img_binaries():
|
||||
async def get_image_binaries():
|
||||
|
||||
return await Qemu.img_binary_list()
|
||||
|
||||
|
||||
@router.get("/qemu/capabilities")
|
||||
async def get_capabilities() -> dict:
|
||||
async def get_qemu_capabilities() -> dict:
|
||||
capabilities = {"kvm": []}
|
||||
kvms = await Qemu.get_kvm_archs()
|
||||
if kvms:
|
||||
@@ -147,7 +147,7 @@ async def get_capabilities() -> dict:
|
||||
@router.post("/qemu/img",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to create Qemu image"}})
|
||||
async def create_img(image_data: schemas.QemuImageCreate):
|
||||
async def create_qemu_image(image_data: schemas.QemuImageCreate):
|
||||
"""
|
||||
Create a Qemu image.
|
||||
"""
|
||||
@@ -163,7 +163,7 @@ async def create_img(image_data: schemas.QemuImageCreate):
|
||||
@router.put("/qemu/img",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to update Qemu image"}})
|
||||
async def update_img(image_data: schemas.QemuImageUpdate):
|
||||
async def update_qemu_image(image_data: schemas.QemuImageUpdate):
|
||||
"""
|
||||
Update a Qemu image.
|
||||
"""
|
||||
|
||||
@@ -98,7 +98,7 @@ def get_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.Docker,
|
||||
responses=responses)
|
||||
async def update_docker(node_data: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)):
|
||||
async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Update a Docker node.
|
||||
"""
|
||||
@@ -217,10 +217,10 @@ async def duplicate_docker_node(destination_node_id: UUID = Body(..., embed=True
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses=responses)
|
||||
async def create_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: DockerVM = Depends(dep_node)):
|
||||
async def create_docker_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The port number on the Docker node is always 0.
|
||||
@@ -235,9 +235,9 @@ async def create_nio(adapter_number: int,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses=responses)
|
||||
async def update_nio(adapter_number: int,
|
||||
port_number: int, nio_data: schemas.UDPNIO,
|
||||
node: DockerVM = Depends(dep_node)):
|
||||
async def update_docker_node_nio(adapter_number: int,
|
||||
port_number: int, nio_data: schemas.UDPNIO,
|
||||
node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
The port number on the Docker node is always 0.
|
||||
@@ -253,7 +253,7 @@ async def update_nio(adapter_number: int,
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def delete_nio(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)):
|
||||
async def delete_docker_node_nio(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The port number on the Docker node is always 0.
|
||||
@@ -264,10 +264,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: DockerVM = Dep
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||
responses=responses)
|
||||
async def start_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: DockerVM = Depends(dep_node)):
|
||||
async def start_docker_node_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The port number on the Docker node is always 0.
|
||||
@@ -281,7 +281,7 @@ async def start_capture(adapter_number: int,
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def stop_capture(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)):
|
||||
async def stop_docker_node_capture(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The port number on the Docker node is always 0.
|
||||
|
||||
@@ -159,7 +159,7 @@ async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def stop(node: IOUVM = Depends(dep_node)):
|
||||
async def stop_iou_node(node: IOUVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop an IOU node.
|
||||
"""
|
||||
@@ -194,10 +194,10 @@ async def reload_iou_node(node: IOUVM = Depends(dep_node)):
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
responses=responses)
|
||||
async def create_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: IOUVM = Depends(dep_node)):
|
||||
async def create_iou_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: IOUVM = Depends(dep_node)):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
"""
|
||||
@@ -211,10 +211,10 @@ async def create_nio(adapter_number: int,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
responses=responses)
|
||||
async def update_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: IOUVM = Depends(dep_node)):
|
||||
async def update_iou_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: IOUVM = Depends(dep_node)):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
"""
|
||||
@@ -229,7 +229,7 @@ async def update_nio(adapter_number: int,
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def delete_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)):
|
||||
async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
"""
|
||||
@@ -239,10 +239,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: IOUVM = Depend
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||
responses=responses)
|
||||
async def start_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: IOUVM = Depends(dep_node)):
|
||||
async def start_iou_node_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: IOUVM = Depends(dep_node)):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
"""
|
||||
@@ -255,7 +255,7 @@ async def start_capture(adapter_number: int,
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def stop_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)):
|
||||
async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
"""
|
||||
|
||||
@@ -52,7 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID):
|
||||
response_model=schemas.NAT,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create NAT node"}})
|
||||
async def create_nat(project_id: UUID, node_data: schemas.NATCreate):
|
||||
async def create_nat_node(project_id: UUID, node_data: schemas.NATCreate):
|
||||
"""
|
||||
Create a new NAT node.
|
||||
"""
|
||||
@@ -72,7 +72,7 @@ async def create_nat(project_id: UUID, node_data: schemas.NATCreate):
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.NAT,
|
||||
responses=responses)
|
||||
def get_nat(node: Nat = Depends(dep_node)):
|
||||
def get_nat_node(node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Return a NAT node.
|
||||
"""
|
||||
@@ -83,7 +83,7 @@ def get_nat(node: Nat = Depends(dep_node)):
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.NAT,
|
||||
responses=responses)
|
||||
def update_nat(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node)):
|
||||
def update_nat_node(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Update a NAT node.
|
||||
"""
|
||||
@@ -99,7 +99,7 @@ def update_nat(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node)):
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def delete_nat(node: Nat = Depends(dep_node)):
|
||||
async def delete_nat_node(node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Delete a cloud node.
|
||||
"""
|
||||
@@ -110,7 +110,7 @@ async def delete_nat(node: Nat = Depends(dep_node)):
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def start_nat(node: Nat = Depends(dep_node)):
|
||||
async def start_nat_node(node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Start a NAT node.
|
||||
"""
|
||||
@@ -121,7 +121,7 @@ async def start_nat(node: Nat = Depends(dep_node)):
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def stop_nat(node: Nat = Depends(dep_node)):
|
||||
async def stop_nat_node(node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Stop a NAT node.
|
||||
This endpoint results in no action since cloud nodes cannot be stopped.
|
||||
@@ -133,7 +133,7 @@ async def stop_nat(node: Nat = Depends(dep_node)):
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def suspend_nat(node: Nat = Depends(dep_node)):
|
||||
async def suspend_nat_node(node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Suspend a NAT node.
|
||||
This endpoint results in no action since NAT nodes cannot be suspended.
|
||||
@@ -146,10 +146,10 @@ async def suspend_nat(node: Nat = Depends(dep_node)):
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
responses=responses)
|
||||
async def create_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: Nat = Depends(dep_node)):
|
||||
async def create_nat_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
@@ -164,10 +164,10 @@ async def create_nio(adapter_number: int,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
responses=responses)
|
||||
async def update_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: Nat = Depends(dep_node)):
|
||||
async def update_nat_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) to the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
@@ -183,7 +183,7 @@ async def update_nio(adapter_number: int,
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def delete_nio(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)):
|
||||
async def delete_nat_node_nio(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Remove a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
@@ -194,10 +194,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: Nat = Depends(
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||
responses=responses)
|
||||
async def start_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: Nat = Depends(dep_node)):
|
||||
async def start_nat_node_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
@@ -211,7 +211,7 @@ async def start_capture(adapter_number: int,
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def stop_capture(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)):
|
||||
async def stop_nat_node_capture(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
|
||||
@@ -52,7 +52,7 @@ def dep_project(project_id: UUID):
|
||||
|
||||
|
||||
@router.get("/projects", response_model=List[schemas.Project])
|
||||
def get_projects():
|
||||
def get_compute_projects():
|
||||
"""
|
||||
Get all projects opened on the compute.
|
||||
"""
|
||||
@@ -64,7 +64,7 @@ def get_projects():
|
||||
@router.post("/projects",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project)
|
||||
def create_project(project_data: schemas.ProjectCreate):
|
||||
def create_compute_project(project_data: schemas.ProjectCreate):
|
||||
"""
|
||||
Create a new project on the compute.
|
||||
"""
|
||||
@@ -80,7 +80,7 @@ def create_project(project_data: schemas.ProjectCreate):
|
||||
|
||||
@router.put("/projects/{project_id}",
|
||||
response_model=schemas.Project)
|
||||
async def update_project(project_data: schemas.ProjectUpdate, project: Project = Depends(dep_project)):
|
||||
async def update_compute_project(project_data: schemas.ProjectUpdate, project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Update project on the compute.
|
||||
"""
|
||||
@@ -91,7 +91,7 @@ async def update_project(project_data: schemas.ProjectUpdate, project: Project =
|
||||
|
||||
@router.get("/projects/{project_id}",
|
||||
response_model=schemas.Project)
|
||||
def get_project(project: Project = Depends(dep_project)):
|
||||
def get_compute_project(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Return a project from the compute.
|
||||
"""
|
||||
@@ -101,7 +101,7 @@ def get_project(project: Project = Depends(dep_project)):
|
||||
|
||||
@router.post("/projects/{project_id}/close",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def close_project(project: Project = Depends(dep_project)):
|
||||
async def close_compute_project(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Close a project on the compute.
|
||||
"""
|
||||
@@ -120,7 +120,7 @@ async def close_project(project: Project = Depends(dep_project)):
|
||||
|
||||
@router.delete("/projects/{project_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_project(project: Project = Depends(dep_project)):
|
||||
async def delete_compute_project(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Delete project from the compute.
|
||||
"""
|
||||
@@ -184,7 +184,7 @@ async def delete_project(project: Project = Depends(dep_project)):
|
||||
|
||||
@router.get("/projects/{project_id}/files",
|
||||
response_model=List[schemas.ProjectFile])
|
||||
async def get_project_files(project: Project = Depends(dep_project)):
|
||||
async def get_compute_project_files(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Return files belonging to a project.
|
||||
"""
|
||||
@@ -193,7 +193,7 @@ async def get_project_files(project: Project = Depends(dep_project)):
|
||||
|
||||
|
||||
@router.get("/projects/{project_id}/files/{file_path:path}")
|
||||
async def get_file(file_path: str, project: Project = Depends(dep_project)):
|
||||
async def get_compute_project_file(file_path: str, project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Get a file from a project.
|
||||
"""
|
||||
@@ -213,7 +213,7 @@ async def get_file(file_path: str, project: Project = Depends(dep_project)):
|
||||
|
||||
@router.post("/projects/{project_id}/files/{file_path:path}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def write_file(file_path: str, request: Request, project: Project = Depends(dep_project)):
|
||||
async def write_compute_project_file(file_path: str, request: Request, project: Project = Depends(dep_project)):
|
||||
|
||||
path = os.path.normpath(file_path)
|
||||
|
||||
|
||||
@@ -210,7 +210,10 @@ async def resume_qemu_node(node: QemuVM = Depends(dep_node)):
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses=responses)
|
||||
async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: QemuVM = Depends(dep_node)):
|
||||
async def create_qemu_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: QemuVM = Depends(dep_node)):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The port number on the Qemu node is always 0.
|
||||
@@ -225,7 +228,10 @@ async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UD
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses=responses)
|
||||
async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: QemuVM = Depends(dep_node)):
|
||||
async def update_qemu_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: QemuVM = Depends(dep_node)):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
The port number on the Qemu node is always 0.
|
||||
@@ -243,7 +249,9 @@ async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UD
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def delete_nio(adapter_number: int, port_number: int, node: QemuVM = Depends(dep_node)):
|
||||
async def delete_qemu_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
node: QemuVM = Depends(dep_node)):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The port number on the Qemu node is always 0.
|
||||
@@ -254,10 +262,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: QemuVM = Depen
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||
responses=responses)
|
||||
async def start_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: QemuVM = Depends(dep_node)):
|
||||
async def start_qemu_node_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: QemuVM = Depends(dep_node)):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The port number on the Qemu node is always 0.
|
||||
@@ -271,7 +279,7 @@ async def start_capture(adapter_number: int,
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def stop_capture(adapter_number: int, port_number: int, node: QemuVM = Depends(dep_node)):
|
||||
async def stop_qemu_node_capture(adapter_number: int, port_number: int, node: QemuVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The port number on the Qemu node is always 0.
|
||||
|
||||
@@ -212,10 +212,10 @@ async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses=responses)
|
||||
async def create_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: VirtualBoxVM = Depends(dep_node)):
|
||||
async def create_virtualbox_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: VirtualBoxVM = Depends(dep_node)):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The port number on the VirtualBox node is always 0.
|
||||
@@ -230,10 +230,10 @@ async def create_nio(adapter_number: int,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses=responses)
|
||||
async def update_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: VirtualBoxVM = Depends(dep_node)):
|
||||
async def update_virtualbox_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: VirtualBoxVM = Depends(dep_node)):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
The port number on the VirtualBox node is always 0.
|
||||
@@ -251,7 +251,7 @@ async def update_nio(adapter_number: int,
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def delete_nio(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)):
|
||||
async def delete_virtualbox_node_nio(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The port number on the VirtualBox node is always 0.
|
||||
@@ -262,10 +262,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: VirtualBoxVM =
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||
responses=responses)
|
||||
async def start_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: VirtualBoxVM = Depends(dep_node)):
|
||||
async def start_virtualbox_node_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: VirtualBoxVM = Depends(dep_node)):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The port number on the VirtualBox node is always 0.
|
||||
@@ -279,7 +279,7 @@ async def start_capture(adapter_number: int,
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def stop_capture(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)):
|
||||
async def stop_virtualbox_node_capture(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The port number on the VirtualBox node is always 0.
|
||||
|
||||
@@ -180,10 +180,10 @@ async def reload_vmware_node(node: VMwareVM = Depends(dep_node)):
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses=responses)
|
||||
async def create_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: VMwareVM = Depends(dep_node)):
|
||||
async def create_vmware_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: VMwareVM = Depends(dep_node)):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The port number on the VMware node is always 0.
|
||||
@@ -198,9 +198,9 @@ async def create_nio(adapter_number: int,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses=responses)
|
||||
async def update_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO, node: VMwareVM = Depends(dep_node)):
|
||||
async def update_vmware_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO, node: VMwareVM = Depends(dep_node)):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
The port number on the VMware node is always 0.
|
||||
@@ -216,7 +216,7 @@ async def update_nio(adapter_number: int,
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def delete_nio(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)):
|
||||
async def delete_vmware_node_nio(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The port number on the VMware node is always 0.
|
||||
@@ -227,10 +227,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: VMwareVM = Dep
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||
responses=responses)
|
||||
async def start_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: VMwareVM = Depends(dep_node)):
|
||||
async def start_vmware_node_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: VMwareVM = Depends(dep_node)):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The port number on the VMware node is always 0.
|
||||
@@ -244,7 +244,7 @@ async def start_capture(adapter_number: int,
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def stop_capture(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)):
|
||||
async def stop_vmware_node_capture(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The port number on the VMware node is always 0.
|
||||
|
||||
@@ -168,7 +168,10 @@ async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)):
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses=responses)
|
||||
async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VPCSVM = Depends(dep_node)):
|
||||
async def create_vpcs_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: VPCSVM = Depends(dep_node)):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The adapter number on the VPCS node is always 0.
|
||||
@@ -183,7 +186,10 @@ async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UD
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses=responses)
|
||||
async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VPCSVM = Depends(dep_node)):
|
||||
async def update_vpcs_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: VPCSVM = Depends(dep_node)):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
The adapter number on the VPCS node is always 0.
|
||||
@@ -199,7 +205,9 @@ async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UD
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def delete_nio(adapter_number: int, port_number: int, node: VPCSVM = Depends(dep_node)):
|
||||
async def delete_vpcs_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
node: VPCSVM = Depends(dep_node)):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the VPCS node is always 0.
|
||||
@@ -210,10 +218,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: VPCSVM = Depen
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||
responses=responses)
|
||||
async def start_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: VPCSVM = Depends(dep_node)):
|
||||
async def start_vpcs_node_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: VPCSVM = Depends(dep_node)):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The adapter number on the VPCS node is always 0.
|
||||
@@ -227,7 +235,7 @@ async def start_capture(adapter_number: int,
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def stop_capture(adapter_number: int, port_number: int, node: VPCSVM = Depends(dep_node)):
|
||||
async def stop_vpcs_node_capture(adapter_number: int, port_number: int, node: VPCSVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the VPCS node is always 0.
|
||||
|
||||
@@ -28,9 +28,11 @@ from . import projects
|
||||
from . import snapshots
|
||||
from . import symbols
|
||||
from . import templates
|
||||
from . import users
|
||||
|
||||
router = APIRouter()
|
||||
router.include_router(controller.router, tags=["Controller"])
|
||||
router.include_router(users.router, prefix="/users", tags=["Users"])
|
||||
router.include_router(appliances.router, prefix="/appliances", tags=["Appliances"])
|
||||
router.include_router(computes.router, prefix="/computes", tags=["Computes"])
|
||||
router.include_router(drawings.router, prefix="/projects/{project_id}/drawings", tags=["Drawings"])
|
||||
|
||||
@@ -71,7 +71,7 @@ async def shutdown():
|
||||
|
||||
@router.get("/version",
|
||||
response_model=schemas.Version)
|
||||
def version():
|
||||
def get_version():
|
||||
"""
|
||||
Return the server version number.
|
||||
"""
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.db.repositories.users import UsersRepository
|
||||
from gns3server.services import auth_service
|
||||
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/v3/users/login") # FIXME: URL prefix
|
||||
|
||||
|
||||
async def get_user_from_token(token: str = Depends(oauth2_scheme),
|
||||
user_repo: UsersRepository = Depends()) -> schemas.User:
|
||||
|
||||
username = auth_service.get_username_from_token(token)
|
||||
user = await user_repo.get_user_by_username(username)
|
||||
if user is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"}
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
async def get_current_active_user(current_user: schemas.User = Depends(get_user_from_token)) -> schemas.User:
|
||||
|
||||
if not current_user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Not an active user",
|
||||
headers={"WWW-Authenticate": "Bearer"}
|
||||
)
|
||||
return current_user
|
||||
@@ -416,7 +416,7 @@ async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)):
|
||||
@router.post("/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses=responses)
|
||||
async def reset_console_all(project: Project = Depends(dep_project)):
|
||||
async def reset_console_all_nodes(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Reset console for all nodes belonging to the project.
|
||||
"""
|
||||
|
||||
@@ -347,7 +347,7 @@ async def import_project(project_id: UUID, request: Request, path: Optional[Path
|
||||
**responses,
|
||||
409: {"model": schemas.ErrorMessage, "description": "Could not duplicate project"}
|
||||
})
|
||||
async def duplicate(project_data: schemas.ProjectDuplicate, project: Project = Depends(dep_project)):
|
||||
async def duplicate_project(project_data: schemas.ProjectDuplicate, project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Duplicate a project.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2016 GNS3 Technologies Inc.
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API routes for users.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||
from uuid import UUID
|
||||
from typing import List
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.controller.controller_error import ControllerBadRequestError, ControllerNotFoundError
|
||||
from gns3server.db.repositories.users import UsersRepository
|
||||
from gns3server.services import auth_service
|
||||
|
||||
from .dependencies.authentication import get_current_active_user
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("", response_model=List[schemas.User])
|
||||
async def get_users(user_repo: UsersRepository = Depends()) -> List[schemas.User]:
|
||||
"""
|
||||
Get all users.
|
||||
"""
|
||||
|
||||
users = await user_repo.get_users()
|
||||
return users
|
||||
|
||||
|
||||
@router.post("", response_model=schemas.User, status_code=status.HTTP_201_CREATED)
|
||||
async def create_user(new_user: schemas.UserCreate, user_repo: UsersRepository = Depends()) -> schemas.User:
|
||||
"""
|
||||
Create a new user.
|
||||
"""
|
||||
|
||||
if await user_repo.get_user_by_username(new_user.username):
|
||||
raise ControllerBadRequestError(f"Username '{new_user.username}' is already registered")
|
||||
|
||||
if new_user.email and await user_repo.get_user_by_email(new_user.email):
|
||||
raise ControllerBadRequestError(f"Email '{new_user.email}' is already registered")
|
||||
|
||||
return await user_repo.create_user(new_user)
|
||||
|
||||
|
||||
@router.get("/{user_id}", response_model=schemas.User)
|
||||
async def get_user(user_id: UUID, user_repo: UsersRepository = Depends()) -> schemas.User:
|
||||
"""
|
||||
Get an user.
|
||||
"""
|
||||
|
||||
user = await user_repo.get_user(user_id)
|
||||
if not user:
|
||||
raise ControllerNotFoundError(f"User '{user_id}' not found")
|
||||
return user
|
||||
|
||||
|
||||
@router.put("/{user_id}", response_model=schemas.User)
|
||||
async def update_user(user_id: UUID,
|
||||
update_user: schemas.UserUpdate,
|
||||
user_repo: UsersRepository = Depends()) -> schemas.User:
|
||||
"""
|
||||
Update an user.
|
||||
"""
|
||||
|
||||
user = await user_repo.update_user(user_id, update_user)
|
||||
if not user:
|
||||
raise ControllerNotFoundError(f"User '{user_id}' not found")
|
||||
return user
|
||||
|
||||
|
||||
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_user(user_id: UUID, user_repo: UsersRepository = Depends()):
|
||||
"""
|
||||
Delete an user.
|
||||
"""
|
||||
|
||||
success = await user_repo.delete_user(user_id)
|
||||
if not success:
|
||||
raise ControllerNotFoundError(f"User '{user_id}' not found")
|
||||
|
||||
|
||||
@router.post("/login", response_model=schemas.Token)
|
||||
async def login(user_repo: UsersRepository = Depends(),
|
||||
form_data: OAuth2PasswordRequestForm = Depends()) -> schemas.Token:
|
||||
"""
|
||||
User login.
|
||||
"""
|
||||
|
||||
user = await user_repo.authenticate_user(username=form_data.username, password=form_data.password)
|
||||
if not user:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Authentication was unsuccessful.",
|
||||
headers={"WWW-Authenticate": "Bearer"})
|
||||
|
||||
token = schemas.Token(access_token=auth_service.create_access_token(user.username), token_type="bearer")
|
||||
return token
|
||||
|
||||
|
||||
@router.get("/users/me/", response_model=schemas.User)
|
||||
async def get_current_active_user(current_user: schemas.User = Depends(get_current_active_user)) -> schemas.User:
|
||||
"""
|
||||
Get the current active user.
|
||||
"""
|
||||
|
||||
return current_user
|
||||
@@ -30,6 +30,7 @@ from fastapi.responses import JSONResponse
|
||||
from gns3server.controller.controller_error import (
|
||||
ControllerError,
|
||||
ControllerNotFoundError,
|
||||
ControllerBadRequestError,
|
||||
ControllerTimeoutError,
|
||||
ControllerForbiddenError,
|
||||
ControllerUnauthorizedError
|
||||
@@ -119,6 +120,15 @@ async def controller_not_found_error_handler(request: Request, exc: ControllerNo
|
||||
)
|
||||
|
||||
|
||||
@app.exception_handler(ControllerBadRequestError)
|
||||
async def controller_bad_request_error_handler(request: Request, exc: ControllerBadRequestError):
|
||||
log.error(f"Controller bad request error: {exc}")
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"message": str(exc)},
|
||||
)
|
||||
|
||||
|
||||
# make sure the content key is "message", not "detail" per default
|
||||
@app.exception_handler(StarletteHTTPException)
|
||||
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
|
||||
|
||||
@@ -35,6 +35,12 @@ class ControllerNotFoundError(ControllerError):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class ControllerBadRequestError(ControllerError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class ControllerUnauthorizedError(ControllerError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
|
||||
@@ -100,12 +100,12 @@ class Template:
|
||||
try:
|
||||
template_schema = TEMPLATE_TYPE_TO_SHEMA[self.template_type]
|
||||
template_settings_with_defaults = template_schema.parse_obj(self.__json__())
|
||||
self._settings = template_settings_with_defaults.dict()
|
||||
self._settings = jsonable_encoder(template_settings_with_defaults.dict())
|
||||
if self.template_type == "dynamips":
|
||||
# special case for Dynamips to cover all platform types that contain specific settings
|
||||
dynamips_template_schema = DYNAMIPS_PLATFORM_TO_SHEMA[self._settings["platform"]]
|
||||
dynamips_template_settings_with_defaults = dynamips_template_schema.parse_obj(self.__json__())
|
||||
self._settings = dynamips_template_settings_with_defaults.dict()
|
||||
self._settings = jsonable_encoder(dynamips_template_settings_with_defaults.dict())
|
||||
except ValidationError as e:
|
||||
print(e) #TODO: handle errors
|
||||
raise
|
||||
|
||||
@@ -25,7 +25,8 @@ from gns3server.controller import Controller
|
||||
from gns3server.compute import MODULES
|
||||
from gns3server.compute.port_manager import PortManager
|
||||
from gns3server.utils.http_client import HTTPClient
|
||||
#from gns3server.db.tasks import connect_to_db, close_db_connection
|
||||
from gns3server.db.tasks import connect_to_db
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -54,7 +55,7 @@ def create_startup_handler(app: FastAPI) -> Callable:
|
||||
loop.set_debug(True)
|
||||
|
||||
# connect to the database
|
||||
# await connect_to_db(app)
|
||||
await connect_to_db()
|
||||
|
||||
await Controller.instance().start()
|
||||
# Because with a large image collection
|
||||
@@ -89,7 +90,4 @@ def create_shutdown_handler(app: FastAPI) -> Callable:
|
||||
if PortManager.instance().udp_ports:
|
||||
log.warning("UDP ports are still used {}".format(PortManager.instance().udp_ports))
|
||||
|
||||
# close the connection to the database
|
||||
# await close_db_connection(app)
|
||||
|
||||
return shutdown_handler
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
from sqlalchemy.orm import declarative_base
|
||||
|
||||
SQLALCHEMY_DATABASE_URL = os.environ.get("DATABASE_URI", "sqlite:///./sql_app.db")
|
||||
|
||||
engine = create_async_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
|
||||
Base = declarative_base()
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, DateTime, func
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.types import TypeDecorator, CHAR
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from .database import Base
|
||||
|
||||
|
||||
class GUID(TypeDecorator):
|
||||
"""Platform-independent GUID type.
|
||||
Uses PostgreSQL's UUID type, otherwise uses
|
||||
CHAR(32), storing as stringified hex values.
|
||||
"""
|
||||
impl = CHAR
|
||||
|
||||
def load_dialect_impl(self, dialect):
|
||||
if dialect.name == 'postgresql':
|
||||
return dialect.type_descriptor(UUID())
|
||||
else:
|
||||
return dialect.type_descriptor(CHAR(32))
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
if value is None:
|
||||
return value
|
||||
elif dialect.name == 'postgresql':
|
||||
return str(value)
|
||||
else:
|
||||
if not isinstance(value, uuid.UUID):
|
||||
return "%.32x" % uuid.UUID(value).int
|
||||
else:
|
||||
# hexstring
|
||||
return "%.32x" % value.int
|
||||
|
||||
def process_result_value(self, value, dialect):
|
||||
if value is None:
|
||||
return value
|
||||
else:
|
||||
if not isinstance(value, uuid.UUID):
|
||||
value = uuid.UUID(value)
|
||||
return value
|
||||
|
||||
|
||||
class BaseTable(Base):
|
||||
|
||||
__abstract__ = True
|
||||
|
||||
created_at = Column(DateTime, default=func.current_timestamp())
|
||||
updated_at = Column(DateTime,
|
||||
default=func.current_timestamp(),
|
||||
onupdate=func.current_timestamp())
|
||||
|
||||
|
||||
class User(BaseTable):
|
||||
|
||||
__tablename__ = "users"
|
||||
|
||||
user_id = Column(GUID, primary_key=True, default=str(uuid.uuid4()))
|
||||
username = Column(String, unique=True, index=True)
|
||||
email = Column(String, unique=True, index=True)
|
||||
full_name = Column(String)
|
||||
hashed_password = Column(String)
|
||||
is_active = Column(Boolean, default=True)
|
||||
is_superuser = Column(Boolean, default=False)
|
||||
|
||||
|
||||
# items = relationship("Item", back_populates="owner")
|
||||
#
|
||||
#
|
||||
# class Item(Base):
|
||||
# __tablename__ = "items"
|
||||
#
|
||||
# id = Column(Integer, primary_key=True, index=True)
|
||||
# title = Column(String, index=True)
|
||||
# description = Column(String, index=True)
|
||||
# owner_id = Column(Integer, ForeignKey("users.id"))
|
||||
#
|
||||
# owner = relationship("User", back_populates="items")
|
||||
Whitespace-only changes.
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from ..database import engine
|
||||
|
||||
|
||||
class BaseRepository:
|
||||
|
||||
async def db(self):
|
||||
session = AsyncSession(engine)
|
||||
try:
|
||||
yield session
|
||||
finally:
|
||||
await session.close()
|
||||
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from uuid import UUID
|
||||
from typing import Optional, List
|
||||
from sqlalchemy import select, update, delete
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from ..database import engine
|
||||
from .base import BaseRepository
|
||||
|
||||
import gns3server.db.models as models
|
||||
from gns3server import schemas
|
||||
from gns3server.services import auth_service
|
||||
|
||||
|
||||
class UsersRepository(BaseRepository):
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
||||
super().__init__()
|
||||
self._auth_service = auth_service
|
||||
|
||||
async def get_user(self, user_id: UUID) -> Optional[models.User]:
|
||||
|
||||
async with AsyncSession(engine) as session:
|
||||
result = await session.execute(select(models.User).where(models.User.user_id == user_id))
|
||||
return result.scalars().first()
|
||||
|
||||
async def get_user_by_username(self, username: str) -> Optional[models.User]:
|
||||
|
||||
async with AsyncSession(engine) as session:
|
||||
result = await session.execute(select(models.User).where(models.User.username == username))
|
||||
return result.scalars().first()
|
||||
|
||||
async def get_user_by_email(self, email: str) -> Optional[models.User]:
|
||||
|
||||
async with AsyncSession(engine) as session:
|
||||
result = await session.execute(select(models.User).where(models.User.email == email))
|
||||
return result.scalars().first()
|
||||
|
||||
async def get_users(self) -> List[models.User]:
|
||||
|
||||
async with AsyncSession(engine) as session:
|
||||
result = await session.execute(select(models.User))
|
||||
return result.scalars().all()
|
||||
|
||||
async def create_user(self, user: schemas.UserCreate) -> models.User:
|
||||
|
||||
async with AsyncSession(engine) as session:
|
||||
hashed_password = self._auth_service.hash_password(user.password)
|
||||
db_user = models.User(username=user.username,
|
||||
email=user.email,
|
||||
full_name=user.full_name,
|
||||
hashed_password=hashed_password)
|
||||
session.add(db_user)
|
||||
await session.commit()
|
||||
await session.refresh(db_user)
|
||||
return db_user
|
||||
|
||||
async def update_user(self, user_id: UUID, user_update: schemas.UserUpdate) -> Optional[models.User]:
|
||||
|
||||
async with AsyncSession(engine) as session:
|
||||
|
||||
update_values = user_update.dict(exclude_unset=True)
|
||||
password = update_values.pop("password", None)
|
||||
if password:
|
||||
update_values["hashed_password"] = self._auth_service.hash_password(password=password)
|
||||
|
||||
print(update_values)
|
||||
query = update(models.User) \
|
||||
.where(models.User.user_id == user_id) \
|
||||
.values(update_values)
|
||||
|
||||
await session.execute(query)
|
||||
await session.commit()
|
||||
return await self.get_user(user_id)
|
||||
|
||||
async def delete_user(self, user_id: UUID) -> bool:
|
||||
|
||||
async with AsyncSession(engine) as session:
|
||||
query = delete(models.User).where(models.User.user_id == user_id)
|
||||
result = await session.execute(query)
|
||||
await session.commit()
|
||||
return result.rowcount > 0
|
||||
#except:
|
||||
# await session.rollback()
|
||||
|
||||
async def authenticate_user(self, username: str, password: str) -> Optional[models.User]:
|
||||
|
||||
user = await self.get_user_by_username(username)
|
||||
if not user:
|
||||
return None
|
||||
if not self._auth_service.verify_password(password, user.hashed_password):
|
||||
return None
|
||||
return user
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from .database import engine
|
||||
from .models import Base
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def connect_to_db() -> None:
|
||||
|
||||
try:
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
log.info("Successfully connected to the database")
|
||||
except SQLAlchemyError as e:
|
||||
log.error(f"Error while connecting to the database: {e}")
|
||||
@@ -26,6 +26,8 @@ from .drawings import Drawing
|
||||
from .gns3vm import GNS3VM
|
||||
from .nodes import NodeUpdate, NodeDuplicate, NodeCapture, Node
|
||||
from .projects import ProjectCreate, ProjectUpdate, ProjectDuplicate, Project, ProjectFile
|
||||
from .users import UserCreate, UserUpdate, User
|
||||
from .tokens import Token
|
||||
from .snapshots import SnapshotCreate, Snapshot
|
||||
from .capabilities import Capabilities
|
||||
from .nios import UDPNIO, TAPNIO, EthernetNIO
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class DateTimeModelMixin(BaseModel):
|
||||
|
||||
created_at: Optional[datetime]
|
||||
updated_at: Optional[datetime]
|
||||
@@ -24,14 +24,14 @@ from typing import Optional, List
|
||||
from enum import Enum
|
||||
|
||||
DEFAULT_PORTS = [
|
||||
dict(port_number=0, name="Ethernet0", vlan=1, type="access", ethertype=""),
|
||||
dict(port_number=1, name="Ethernet1", vlan=1, type="access", ethertype=""),
|
||||
dict(port_number=2, name="Ethernet2", vlan=1, type="access", ethertype=""),
|
||||
dict(port_number=3, name="Ethernet3", vlan=1, type="access", ethertype=""),
|
||||
dict(port_number=4, name="Ethernet4", vlan=1, type="access", ethertype=""),
|
||||
dict(port_number=5, name="Ethernet5", vlan=1, type="access", ethertype=""),
|
||||
dict(port_number=6, name="Ethernet6", vlan=1, type="access", ethertype=""),
|
||||
dict(port_number=7, name="Ethernet7", vlan=1, type="access", ethertype="")
|
||||
dict(port_number=0, name="Ethernet0", vlan=1, type="access", ethertype="0x8100"),
|
||||
dict(port_number=1, name="Ethernet1", vlan=1, type="access", ethertype="0x8100"),
|
||||
dict(port_number=2, name="Ethernet2", vlan=1, type="access", ethertype="0x8100"),
|
||||
dict(port_number=3, name="Ethernet3", vlan=1, type="access", ethertype="0x8100"),
|
||||
dict(port_number=4, name="Ethernet4", vlan=1, type="access", ethertype="0x8100"),
|
||||
dict(port_number=5, name="Ethernet5", vlan=1, type="access", ethertype="0x8100"),
|
||||
dict(port_number=6, name="Ethernet6", vlan=1, type="access", ethertype="0x8100"),
|
||||
dict(port_number=7, name="Ethernet7", vlan=1, type="access", ethertype="0x8100")
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
|
||||
access_token: str
|
||||
token_type: str
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
|
||||
username: Optional[str] = None
|
||||
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Optional
|
||||
from pydantic import EmailStr, BaseModel, Field
|
||||
from uuid import UUID
|
||||
|
||||
from .base import DateTimeModelMixin
|
||||
|
||||
|
||||
class UserBase(BaseModel):
|
||||
"""
|
||||
Common user properties.
|
||||
"""
|
||||
|
||||
username: Optional[str] = Field(None, min_length=3, regex="[a-zA-Z0-9_-]+$")
|
||||
email: Optional[EmailStr]
|
||||
full_name: Optional[str]
|
||||
|
||||
|
||||
class UserCreate(UserBase):
|
||||
"""
|
||||
Properties to create an user.
|
||||
"""
|
||||
|
||||
username: str = Field(..., min_length=3, regex="[a-zA-Z0-9_-]+$")
|
||||
password: str = Field(..., min_length=7, max_length=100)
|
||||
|
||||
|
||||
class UserUpdate(UserBase):
|
||||
"""
|
||||
Properties to update an user.
|
||||
"""
|
||||
|
||||
password: Optional[str] = Field(None, min_length=7, max_length=100)
|
||||
|
||||
|
||||
class User(DateTimeModelMixin, UserBase):
|
||||
|
||||
user_id: UUID
|
||||
is_active: bool = True
|
||||
is_superuser: bool = False
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .authentication import AuthService
|
||||
auth_service = AuthService()
|
||||
@@ -0,0 +1,74 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import bcrypt
|
||||
from jose import JWTError, jwt
|
||||
from datetime import datetime, timedelta
|
||||
from passlib.context import CryptContext
|
||||
|
||||
from typing import Optional
|
||||
from fastapi import HTTPException, status
|
||||
from gns3server.schemas.tokens import TokenData
|
||||
from pydantic import ValidationError
|
||||
|
||||
# FIXME: temporary variables to move to config
|
||||
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
||||
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
|
||||
#class AuthException(BaseException):
|
||||
# pass
|
||||
|
||||
|
||||
class AuthService:
|
||||
|
||||
def hash_password(self, password: str) -> str:
|
||||
|
||||
return pwd_context.hash(password)
|
||||
|
||||
def verify_password(self, password, hashed_password) -> bool:
|
||||
|
||||
return pwd_context.verify(password, hashed_password)
|
||||
|
||||
def create_access_token(self, username):
|
||||
|
||||
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
to_encode = {"sub": username, "exp": expire}
|
||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
def get_username_from_token(self, token: str) -> Optional[str]:
|
||||
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
username: str = payload.get("sub")
|
||||
if username is None:
|
||||
raise credentials_exception
|
||||
token_data = TokenData(username=username)
|
||||
except (JWTError, ValidationError):
|
||||
raise credentials_exception
|
||||
return token_data.username
|
||||
Reference in new issue
Block a user