Skip to content

Commit

Permalink
Merge pull request feast-dev#51 from tmihalac/fix-auth-tests-with-per…
Browse files Browse the repository at this point in the history
…missions

Fix auth tests with permissions
  • Loading branch information
tmihalac authored Jul 25, 2024
2 parents ed8bb52 + 11366b4 commit 8113ee2
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 55 deletions.
4 changes: 2 additions & 2 deletions sdk/python/feast/permissions/client/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

def create_auth_header(
auth_config: AuthConfig,
) -> tuple[tuple[str, str]]:
) -> list[tuple[bytes, bytes]]:
auth_client_manager = get_auth_client_manager(auth_config)
token = auth_client_manager.get_token()

return (("authorization", "Bearer " + token),)
return [(b"authorization", b"Bearer " + token.encode("utf-8"))]


def create_flight_call_options(auth_config: AuthConfig) -> fl.FlightCallOptions:
Expand Down
4 changes: 2 additions & 2 deletions sdk/python/feast/permissions/enforcer.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def enforce_policy(
_permitted_resources.append(resource)
break
else:
_permitted_resources.append(resource)
message = f"No permissions defined to manage {actions} on {type(resource)}/{resource.name}."
logger.info(f"**PERMISSION GRANTED**: {message}")
logger.exception(f"**PERMISSION NOT GRANTED**: {message}")
raise PermissionError(message)
return _permitted_resources
3 changes: 1 addition & 2 deletions sdk/python/feast/permissions/security_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ def assert_permissions(
def assert_permissions(
resource: FeastObject,
actions: Union[AuthzedAction, List[AuthzedAction]],
filter_only: bool = False,
) -> FeastObject:
"""
A utility function to invoke the `assert_permissions` method on the global security manager.
Expand All @@ -101,7 +100,7 @@ def assert_permissions(
FeastObject: The original `resource`, if permitted.
Raises:
PermissionError: If the current user is not authorized to eecute the requested actions on the given resources (and `filter_only` is `False`).
PermissionError: If the current user is not authorized to execute the requested actions on the given resources.
"""
sm = get_security_manager()
if sm is None:
Expand Down
25 changes: 15 additions & 10 deletions sdk/python/feast/permissions/server/grpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from feast.permissions.auth.auth_manager import (
get_auth_manager,
)
from feast.permissions.security_manager import get_security_manager
from feast.permissions.server.utils import (
AuthManagerType,
)
Expand Down Expand Up @@ -35,15 +36,19 @@ def grpc_interceptors(

class AuthInterceptor(grpc.ServerInterceptor):
def intercept_service(self, continuation, handler_call_details):
auth_manager = get_auth_manager()
access_token = auth_manager.token_extractor.extract_access_token(
metadata=dict(handler_call_details.invocation_metadata)
)

print(f"Fetching user for token: {len(access_token)}")
current_user = asyncio.run(
auth_manager.token_parser.user_details_from_access_token(access_token)
)
print(f"User is: {current_user}")
sm = get_security_manager()

if sm is not None:
auth_manager = get_auth_manager()
access_token = auth_manager.token_extractor.extract_access_token(
metadata=dict(handler_call_details.invocation_metadata)
)

print(f"Fetching user for token: {len(access_token)}")
current_user = asyncio.run(
auth_manager.token_parser.user_details_from_access_token(access_token)
)
print(f"User is: {current_user}")
sm.set_current_user(current_user)

return continuation(handler_call_details)
90 changes: 86 additions & 4 deletions sdk/python/tests/unit/permissions/auth/server/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,68 @@
import pytest
import yaml

from feast import FeatureStore
from feast import (
Entity,
FeatureStore,
FeatureView,
OnDemandFeatureView,
StreamFeatureView,
)
from feast.permissions.action import AuthzedAction
from feast.permissions.permission import Permission
from feast.permissions.policy import RoleBasedPolicy
from tests.unit.permissions.auth.server.mock_utils import PROJECT_NAME
from tests.utils.cli_repo_creator import CliRunner
from tests.utils.http_server import free_port # noqa: E402

list_permissions_perm = Permission(
name="list_permissions_perm",
types=Permission,
policy=RoleBasedPolicy(roles=["reader"]),
actions=[AuthzedAction.READ],
)

list_entities_perm = Permission(
name="list_entities_perm",
types=Entity,
with_subclasses=False,
policy=RoleBasedPolicy(roles=["reader"]),
actions=[AuthzedAction.READ],
)

list_fv_perm = Permission(
name="list_fv_perm",
types=FeatureView,
with_subclasses=False,
policy=RoleBasedPolicy(roles=["reader"]),
actions=[AuthzedAction.READ],
)


list_odfv_perm = Permission(
name="list_odfv_perm",
types=OnDemandFeatureView,
with_subclasses=False,
policy=RoleBasedPolicy(roles=["reader"]),
actions=[AuthzedAction.READ],
)

list_sfv_perm = Permission(
name="list_sfv_perm",
types=StreamFeatureView,
with_subclasses=False,
policy=RoleBasedPolicy(roles=["reader"]),
actions=[AuthzedAction.READ],
)

invalid_list_entities_perm = Permission(
name="invalid_list_entity_perm",
types=Entity,
with_subclasses=False,
policy=RoleBasedPolicy(roles=["dancer"]),
actions=[AuthzedAction.READ],
)


@pytest.fixture(
scope="module",
Expand Down Expand Up @@ -47,16 +104,34 @@ def temp_dir():


@pytest.fixture
def feature_store(temp_dir, auth_config):
def feature_store(temp_dir, auth_config, applied_permissions):
print(f"Creating store at {temp_dir}")
return _default_store(str(temp_dir), auth_config)
return _default_store(str(temp_dir), auth_config, applied_permissions)


@pytest.fixture
def server_port():
return free_port()


@pytest.fixture(
scope="module",
params=[
[],
[invalid_list_entities_perm],
[
list_entities_perm,
list_permissions_perm,
list_fv_perm,
list_odfv_perm,
list_sfv_perm,
],
],
)
def applied_permissions(request):
return request.param


def _include_auth_config(file_path, auth_config: str):
with open(file_path, "r") as file:
existing_content = yaml.safe_load(file)
Expand All @@ -70,7 +145,11 @@ def _include_auth_config(file_path, auth_config: str):
print(f"Updated auth section at {file_path}")


def _default_store(temp_dir, auth_config: str):
def _default_store(
temp_dir,
auth_config: str,
permissions: list[Permission],
):
runner = CliRunner()
result = runner.run(["init", PROJECT_NAME], cwd=temp_dir)
repo_path = os.path.join(temp_dir, PROJECT_NAME, "feature_repo")
Expand All @@ -84,4 +163,7 @@ def _default_store(temp_dir, auth_config: str):
assert result.returncode == 0

fs = FeatureStore(repo_path=repo_path)

fs.apply(permissions)

return fs
7 changes: 3 additions & 4 deletions sdk/python/tests/unit/permissions/auth/server/mock_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from unittest.mock import MagicMock, Mock, mock_open
from unittest.mock import MagicMock, Mock

from requests import Response

Expand Down Expand Up @@ -68,8 +68,7 @@ def _mock_kubernetes(request, monkeypatch):
"feast.permissions.auth.kubernetes_token_parser.client.RbacAuthorizationV1Api.list_cluster_role_binding",
lambda *args, **kwargs: clusterrolebindings["items"],
)
m = mock_open(read_data="my-token")
monkeypatch.setattr(
"builtins.open",
m,
"feast.permissions.client.kubernetes_auth_client_manager.KubernetesAuthClientManager.get_token",
lambda self: "my-token",
)
Loading

0 comments on commit 8113ee2

Please sign in to comment.