Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion charts/pdp/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
apiVersion: v2
name: pdp
description: An official Helm chart for Permit.io PDP (Policy Decision Point)
version: 0.0.4
version: 0.0.4
31 changes: 25 additions & 6 deletions horizon/facts/client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Annotated
from typing import Annotated, Any
from urllib.parse import urljoin

from fastapi import Depends, HTTPException
Expand Down Expand Up @@ -30,7 +30,13 @@ def client(self) -> AsyncClient:
)
return self._client

async def build_forward_request(self, request: FastApiRequest, path: str) -> HttpxRequest:
async def build_forward_request(
self,
request: FastApiRequest,
path: str,
*,
query_params: dict[str, Any] | None = None,
) -> HttpxRequest:
"""
Build an HTTPX request from a FastAPI request to forward to the facts service.
:param request: FastAPI request
Expand All @@ -50,10 +56,11 @@ async def build_forward_request(self, request: FastApiRequest, path: str) -> Htt
)

full_path = urljoin(f"/v2/facts/{project_id}/{environment_id}/", path.removeprefix("/"))
_query_params = {**request.query_params, **(query_params or {})}
return self.client.build_request(
method=request.method,
url=full_path,
params=request.query_params,
params=_query_params,
headers=forward_headers,
content=request.stream(),
)
Expand All @@ -62,18 +69,28 @@ async def send(self, request: HttpxRequest, *, stream: bool = False) -> HttpxRes
logger.info(f"Forwarding facts request: {request.method} {request.url}")
return await self.client.send(request, stream=stream)

async def send_forward_request(self, request: FastApiRequest, path: str) -> HttpxResponse:
async def send_forward_request(
self,
request: FastApiRequest,
path: str,
*,
query_params: dict[str, Any] | None = None,
) -> HttpxResponse:
"""
Send a forward request to the facts service.
:param request: FastAPI request
:param path: Backend facts service path to forward to
:return: HTTPX response
"""
forward_request = await self.build_forward_request(request, path)
forward_request = await self.build_forward_request(request, path, query_params=query_params)
return await self.send(forward_request)

@staticmethod
def convert_response(response: HttpxResponse, *, stream: bool = False) -> FastApiResponse:
def convert_response(
response: HttpxResponse,
*,
stream: bool = False,
) -> FastApiResponse:
"""
Convert an HTTPX response to a FastAPI response.
:param response: HTTPX response
Expand Down Expand Up @@ -103,6 +120,8 @@ def extract_body(response: HttpxResponse):
return None

try:
if response.status_code == 204:
return None
body = response.json()
except Exception: # noqa: BLE001
logger.exception("Failed to parse response body as JSON, skipping wait for update.")
Expand Down
50 changes: 49 additions & 1 deletion horizon/facts/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,27 @@ async def assign_user_role(
)


@facts_router.delete("/users/{user_id}/roles")
async def unassign_user_role(
request: FastApiRequest,
client: FactsClientDependency,
update_subscriber: DataUpdateSubscriberDependency,
wait_timeout: WaitTimeoutDependency,
timeout_policy: TimeoutPolicyDependency,
user_id: str,
):
return await forward_request_then_wait_for_update(
client,
request,
update_subscriber,
wait_timeout,
path=f"/users/{user_id}/roles",
query_params={"return_deleted": True},
entries_callback=create_role_assignment_data_entries,
timeout_policy=timeout_policy,
)


@facts_router.post("/role_assignments")
async def create_role_assignment(
request: FastApiRequest,
Expand All @@ -211,6 +232,26 @@ async def create_role_assignment(
)


@facts_router.delete("/role_assignments")
async def delete_role_assignment(
request: FastApiRequest,
client: FactsClientDependency,
update_subscriber: DataUpdateSubscriberDependency,
wait_timeout: WaitTimeoutDependency,
timeout_policy: TimeoutPolicyDependency,
):
return await forward_request_then_wait_for_update(
client,
request,
update_subscriber,
wait_timeout,
path="/role_assignments",
entries_callback=create_role_assignment_data_entries,
timeout_policy=timeout_policy,
query_params={"return_deleted": True},
)


@facts_router.post("/resource_instances")
async def create_resource_instance(
request: FastApiRequest,
Expand Down Expand Up @@ -293,6 +334,12 @@ async def create_relationship_tuple(
)


def cast_delete_200_to_204(response: Response) -> Response:
if response.status_code == 200:
return Response(status_code=204)
return response


async def forward_request_then_wait_for_update(
client: FactsClient,
request: FastApiRequest,
Expand All @@ -303,9 +350,10 @@ async def forward_request_then_wait_for_update(
update_id: UUID | None = None,
entries_callback: Callable[[FastApiRequest, dict[str, Any], UUID | None], Iterable[DataSourceEntry]],
timeout_policy: TimeoutPolicy = TimeoutPolicy.IGNORE,
query_params: dict[str, Any] | None = None,
) -> Response:
_update_id = update_id or uuid4()
response = await client.send_forward_request(request, path)
response = await client.send_forward_request(request, path, query_params=query_params)
body = client.extract_body(response)
if body is None:
return client.convert_response(response)
Expand Down