Skip to content

Commit

Permalink
extract datafiltering code into a separate package: permit-datafilter
Browse files Browse the repository at this point in the history
  • Loading branch information
Asaf Cohen committed Aug 12, 2024
1 parent b593e13 commit c97fda1
Show file tree
Hide file tree
Showing 29 changed files with 174 additions and 138 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
helm/
.venv/
.github/
lib/
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
Expand Down
38 changes: 5 additions & 33 deletions horizon/enforcer/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
DEFAULT_POLICY_STORE_GETTER,
)
from opal_client.utils import proxy_response
from permit_datafilter.compile_api.compile_client import OpaCompileClient
from pydantic import parse_obj_as
from starlette.responses import JSONResponse

from horizon.authentication import enforce_pdp_token
from horizon.config import sidecar_config
from horizon.enforcer.data_filtering.compile_api.compile_client import OpaCompileClient
from horizon.enforcer.schemas import (
AuthorizationQuery,
AuthorizationResult,
Expand Down Expand Up @@ -722,12 +722,14 @@ async def filter_resources(
x_permit_sdk_language: Optional[str] = Depends(notify_seen_sdk),
):
headers = transform_headers(request)
client = OpaCompileClient(headers=headers)
client = OpaCompileClient(
base_url=f"{opal_client_config.POLICY_STORE_URL}", headers=headers
)
COMPILE_ROOT_RULE_REFERENCE = f"data.{MAIN_PARTIAL_EVAL_PACKAGE}.allow"
query = f"{COMPILE_ROOT_RULE_REFERENCE} == true"
residual_policy = await client.compile_query(
query=query,
input=input,
input=input.dict(),
unknowns=[
"input.resource.key",
"input.resource.tenant",
Expand All @@ -741,34 +743,4 @@ async def filter_resources(
media_type="application/json",
)

@router.post(
"/filter_resources_get_sql",
response_model=AuthorizationResult,
status_code=status.HTTP_200_OK,
response_model_exclude_none=True,
dependencies=[Depends(enforce_pdp_token)],
)
async def filter_resources_get_sql(
request: Request,
input: AuthorizationQuery,
x_permit_sdk_language: Optional[str] = Depends(notify_seen_sdk),
):
"""
TODO: temp endpoint, instead we should wrap the capability in the SDK
"""
headers = transform_headers(request)
client = OpaCompileClient(headers=headers)
COMPILE_ROOT_RULE_REFERENCE = f"data.{MAIN_PARTIAL_EVAL_PACKAGE}.allow"
query = f"{COMPILE_ROOT_RULE_REFERENCE} == true"
residual_policy = await client.compile_query(
query=query,
input=input,
unknowns=[
"input.resource.key",
"input.resource.tenant",
"input.resource.attributes",
],
raw=True,
)

return router
72 changes: 0 additions & 72 deletions horizon/enforcer/data_filtering/sdk/permit_filter.py

This file was deleted.

1 change: 1 addition & 0 deletions lib/permit-datafilter/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include *.md requirements.txt
11 changes: 11 additions & 0 deletions lib/permit-datafilter/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.PHONY: help

.DEFAULT_GOAL := help

clean:
rm -rf *.egg-info build/ dist/

publish:
$(MAKE) clean
python setup.py sdist bdist_wheel
python -m twine upload dist/*
9 changes: 9 additions & 0 deletions lib/permit-datafilter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Permit.io Data Filtering SDK (EAP)

Initial SDK to enable data filtering scenarios based on compiling OPA policies from Rego AST into SQL-like expressions.

## Installation

```py
pip install permit-datafilter
```
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from horizon.enforcer.data_filtering.rego_ast import parser as ast
from horizon.enforcer.data_filtering.boolean_expression.schemas import (
from permit_datafilter.rego_ast import parser as ast
from permit_datafilter.boolean_expression.schemas import (
CALL_OPERATOR,
Operand,
ResidualPolicyResponse,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import json

import aiohttp
from fastapi import HTTPException, Response, status
from opal_client.config import opal_client_config
from opal_client.logger import logger
from fastapi import HTTPException, status
from loguru import logger

from horizon.enforcer.schemas import AuthorizationQuery
from horizon.enforcer.data_filtering.compile_api.schemas import CompileResponse
from horizon.enforcer.data_filtering.rego_ast import parser as ast
from horizon.enforcer.data_filtering.boolean_expression.schemas import (
from permit_datafilter.compile_api.schemas import CompileResponse
from permit_datafilter.rego_ast import parser as ast
from permit_datafilter.boolean_expression.schemas import (
ResidualPolicyResponse,
)
from horizon.enforcer.data_filtering.boolean_expression.translator import (
from permit_datafilter.boolean_expression.translator import (
translate_opa_queryset,
)


class OpaCompileClient:
def __init__(self, headers: dict):
self._base_url = f"{opal_client_config.POLICY_STORE_URL}"
def __init__(self, base_url: str, headers: dict):
self._base_url = base_url # f"{opal_client_config.POLICY_STORE_URL}"
self._headers = headers
self._client = aiohttp.ClientSession(
base_url=self._base_url, headers=self._headers
Expand All @@ -27,27 +25,27 @@ def __init__(self, headers: dict):
async def compile_query(
self,
query: str,
input: AuthorizationQuery,
input: dict,
unknowns: list[str],
raw: bool = False,
) -> ResidualPolicyResponse:
# we don't want debug rules when we try to reduce the policy into a partial policy
input = {**input.dict(), "use_debugger": False}
input = {**input, "use_debugger": False}
data = {
"query": query,
"input": input,
"unknowns": unknowns,
}
try:
logger.info("Compiling OPA query: {}", data)
logger.debug("Compiling OPA query: {}", data)
async with self._client as session:
async with session.post(
"/v1/compile",
data=json.dumps(data),
raise_for_status=True,
) as response:
opa_compile_result = await response.json()
logger.info(
logger.debug(
"OPA compile query result: status={status}, response={response}",
status=response.status,
response=json.dumps(opa_compile_result),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,11 @@
from types import NoneType
from typing import Generic, List, TypeVar

from horizon.enforcer.data_filtering.compile_api.schemas import (
from permit_datafilter.compile_api.schemas import (
CRExpression,
CRQuery,
CompileResponse,
CRTerm,
CRSupportModule,
)


Expand Down
72 changes: 72 additions & 0 deletions lib/permit-datafilter/permit_datafilter/sdk/permit_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# from typing import Union

# from horizon.enforcer import schemas as enforcer_schemas
# from horizon.enforcer.api import MAIN_PARTIAL_EVAL_PACKAGE
# from permit_datafilter.boolean_expression.schemas import (
# ResidualPolicyResponse,
# )
# from permit_datafilter.compile_api.compile_client import OpaCompileClient

# User = Union[dict, str]
# Action = str
# Resource = Union[dict, str]


# def normalize_user(user: User) -> dict:
# if isinstance(user, str):
# return dict(key=user)
# else:
# return user


# def normalize_resource_type(resource: Resource) -> str:
# if isinstance(resource, dict):
# t = resource.get("type", None)
# if t is not None and isinstance(t, str):
# return t
# raise ValueError("no resource type provided")
# else:
# return resource


# def filter_resource_query(
# user: User, action: Action, resource: Resource
# ) -> enforcer_schemas.AuthorizationQuery:
# normalized_user = normalize_user(user)
# resource_type: str = normalize_resource_type(resource)
# return enforcer_schemas.AuthorizationQuery(
# user=normalized_user,
# action=action,
# resource=enforcer_schemas.Resource(type=resource_type),
# )


# class Permit:
# """
# stub for future SDK code
# """

# def __init__(self, token: str):
# self._headers = {
# "Authorization": f"Bearer {token}",
# "Content-Type": "application/json",
# }

# async def filter_resources(
# self, user: User, action: Action, resource: Resource
# ) -> ResidualPolicyResponse:
# """
# stub for future permit.filter_resources() function
# """
# client = OpaCompileClient(headers=self._headers)
# input = filter_resource_query(user, action, resource)
# return await client.compile_query(
# query=f"data.{MAIN_PARTIAL_EVAL_PACKAGE}.allow == true",
# input=input,
# unknowns=[
# "input.resource.key",
# "input.resource.tenant",
# "input.resource.attributes",
# ],
# raw=True,
# )
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

import sqlalchemy as sa

# import Column, Table, and_, not_, or_, select
from sqlalchemy.orm import DeclarativeMeta, InstrumentedAttribute
from sqlalchemy.sql import Select
from sqlalchemy.sql.expression import BinaryExpression, ColumnOperators

from horizon.enforcer.data_filtering.boolean_expression.schemas import (
from permit_datafilter.boolean_expression.schemas import (
CALL_OPERATOR,
LOGICAL_AND,
LOGICAL_NOT,
Expand Down
7 changes: 7 additions & 0 deletions lib/permit-datafilter/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
aiohttp>=3.9.4,<4
ddtrace
fastapi>=0.109.1,<1
loguru
pydantic[email]>=1.9.1,<2
pytest
SQLAlchemy==1.4.46
Loading

0 comments on commit c97fda1

Please sign in to comment.