Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(tokens) Adds non-admin tests for access tokens #5174

Merged
merged 20 commits into from
Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from 19 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
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ public static Authentication getAuthentication(DataFetchingEnvironment environme
return ((QueryContext) environment.getContext()).getAuthentication();
}

/**
* @apiNote DO NOT use this method if the facet filters do not include `.keyword` suffix to ensure
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there another method that will do this , while also injecting the ".keyword" part?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not exactly, you have ResolverUtils#buildFilter which takes @Nullable List<FacetFilterInput> facetFilterInputs as input instead of @Nullable List<FacetFilterInput> facetFilterInputs, @Nonnull Set<String> validFacetFields

* that it is matched against a keyword filter in ElasticSearch.
*
* @param facetFilterInputs The list of facet filters inputs
* @param validFacetFields The set of valid fields against which to filter for.
* @return A map of filter definitions to be used in ElasticSearch.
*/
@Nonnull
public static Map<String, String> buildFacetFilters(@Nullable List<FacetFilterInput> facetFilterInputs,
@Nonnull Set<String> validFacetFields) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
package com.linkedin.datahub.graphql.resolvers.auth;

import com.google.common.collect.ImmutableSet;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.generated.AccessTokenMetadata;
import com.linkedin.datahub.graphql.generated.EntityType;
import com.linkedin.datahub.graphql.generated.FacetFilterInput;
import com.linkedin.datahub.graphql.generated.ListAccessTokenInput;
import com.linkedin.datahub.graphql.generated.ListAccessTokenResult;
import com.linkedin.datahub.graphql.generated.AccessTokenMetadata;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.query.filter.SortCriterion;
import com.linkedin.metadata.query.filter.SortOrder;
import com.linkedin.metadata.search.SearchResult;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import lombok.extern.slf4j.Slf4j;

import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*;
Expand All @@ -35,8 +31,6 @@
public class ListAccessTokensResolver implements DataFetcher<CompletableFuture<ListAccessTokenResult>> {

private static final String EXPIRES_AT_FIELD_NAME = "expiresAt";
private static final Set<String> FACET_FIELDS =
ImmutableSet.of("ownerUrn", "actorUrn", "name", "createdAt", "expiredAt", "description");

private final EntityClient _entityClient;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@ public void revokeAccessToken(@Nonnull String hashedToken) throws TokenException
throw new TokenException("Access token no longer exists");
}

public boolean isTokenRevoked(@Nonnull String hashToken) {
try {
return _revokedTokenCache.get(hashToken);
} catch (ExecutionException e) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would ExecutionException be thrown? Is it worth logging a warn / error?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the API from Guava's Cache. I wanted a simpler API, to see if a token was revoked or not.

return false;
}
}

/**
* Hashes the input after salting it.
*/
Expand Down
3 changes: 2 additions & 1 deletion metadata-service/war/src/main/resources/boot/policies.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@
"actors":{
"resourceOwners":false,
"allUsers":true,
"allGroups":false
"allGroups":false,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why??

"users":[]
},
"privileges":[
"MANAGE_POLICIES",
Expand Down
24 changes: 20 additions & 4 deletions smoke-test/tests/delete/delete_test.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
import os
import json
import pytest
from time import sleep
from datahub.cli import delete_cli, ingest_cli
from datahub.cli.docker import check_local_docker_containers
from datahub.cli.cli_utils import guess_entity_type, post_entity, get_aspects_for_entity
from datahub.cli.ingest_cli import get_session_and_host
from datahub.cli.delete_cli import guess_entity_type, delete_one_urn_cmd, delete_references
from tests.utils import ingest_file_via_rest, delete_urns_from_file

# Disable telemetry
os.putenv("DATAHUB_TELEMETRY_ENABLED", "false")

@pytest.fixture(scope="session")
def wait_for_healthchecks():
# Simply assert that everything is healthy, but don't wait.
assert not check_local_docker_containers()
yield

@pytest.mark.dependency()
def test_healthchecks(wait_for_healthchecks):
# Call to wait_for_healthchecks fixture will do the actual functionality.
pass

@pytest.fixture(autouse=True)
def test_setup():
"""Fixture to execute asserts before and after a test is run"""
Expand All @@ -24,21 +40,21 @@ def test_setup():

ingested_dataset_run_id = ingest_file_via_rest("tests/delete/cli_test_data.json").config.run_id

sleep(2)
sleep(3)

assert "browsePaths" in get_aspects_for_entity(entity_urn=dataset_urn, aspects=["browsePaths"], typed=False)

yield
rollback_url = f"{gms_host}/runs?action=rollback"
session.post(rollback_url, data=json.dumps({"runId": ingested_dataset_run_id, "dryRun": False, "hardDelete": True, "safe": False}))

sleep(2)
sleep(3)

assert "browsePaths" not in get_aspects_for_entity(entity_urn=dataset_urn, aspects=["browsePaths"], typed=False)
assert "editableDatasetProperties" not in get_aspects_for_entity(entity_urn=dataset_urn, aspects=["editableDatasetProperties"], typed=False)

@pytest.mark.dependency()
def test_delete_reference():
def test_delete_reference(depends=["test_healthchecks"]):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you - this should fix many of the existing issues we are seeing correct?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should but we still have issues on the tests. I'm not seeing the adequate policies being applied.

platform = "urn:li:dataPlatform:kafka"
dataset_name = "test-delete"

Expand All @@ -58,7 +74,7 @@ def test_delete_reference():
# Delete references to the tag
delete_references(tag_urn, dry_run=False, cached_session_host=(session, gms_host))

sleep(2)
sleep(3)

# Validate that references no longer exist
references_count, related_aspects = delete_references(tag_urn, dry_run=True, cached_session_host=(session, gms_host))
Expand Down
Empty file.
223 changes: 223 additions & 0 deletions smoke-test/tests/policies/test_policies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import time
import pytest
import requests
from tests.utils import get_frontend_url
from datahub.cli.docker import check_local_docker_containers

TEST_POLICY_NAME = "Updated Platform Policy"

@pytest.fixture(scope="session")
def wait_for_healthchecks():
# Simply assert that everything is healthy, but don't wait.
assert not check_local_docker_containers()
yield


@pytest.mark.dependency()
def test_healthchecks(wait_for_healthchecks):
# Call to wait_for_healthchecks fixture will do the actual functionality.
pass


@pytest.fixture(scope="session")
def frontend_session(wait_for_healthchecks):
session = requests.Session()

headers = {
"Content-Type": "application/json",
}
data = '{"username":"datahub", "password":"datahub"}'
response = session.post(f"{get_frontend_url()}/logIn", headers=headers, data=data)
response.raise_for_status()

yield session

@pytest.mark.dependency(depends=["test_healthchecks"])
@pytest.fixture(scope='class', autouse=True)
def test_frontend_list_policies(frontend_session):
"""Fixture to execute setup before and tear down after all tests are run"""
res_data = listPolicies(frontend_session)

assert res_data
assert res_data["data"]
assert res_data["data"]["listPolicies"]
assert res_data["data"]["listPolicies"]["start"] == 0
assert res_data["data"]["listPolicies"]["count"] > 0
assert len(res_data["data"]["listPolicies"]["policies"]) > 0

# Verify that policy to be created does not exist before the test.
# If it does, this test class's state is tainted
result = filter(
lambda x: x["name"] == TEST_POLICY_NAME,
res_data["data"]["listPolicies"]["policies"],
)
assert len(list(result)) == 0

# Run remaining tests.
yield

res_data = listPolicies(frontend_session)

assert res_data
assert res_data["data"]
assert res_data["data"]["listPolicies"]

# Verify that policy that was created is no longer in the list
result = filter(
lambda x: x["name"] == TEST_POLICY_NAME,
res_data["data"]["listPolicies"]["policies"],
)
assert len(list(result)) == 0

@pytest.mark.dependency(depends=["test_healthchecks"])
def test_frontend_policy_operations(frontend_session):

json = {
"query": """mutation createPolicy($input: PolicyUpdateInput!) {\n
createPolicy(input: $input) }""",
"variables": {
"input": {
"type": "METADATA",
"name": "Test Metadata Policy",
"description": "My Metadaata Policy",
"state": "ACTIVE",
"resources": {"type": "dataset", "allResources": True},
"privileges": ["EDIT_ENTITY_TAGS"],
"actors": {
"users": ["urn:li:corpuser:datahub"],
"resourceOwners": False,
"allUsers": False,
"allGroups": False,
},
}
},
}

response = frontend_session.post(f"{get_frontend_url()}/api/v2/graphql", json=json)
response.raise_for_status()
res_data = response.json()

assert res_data
assert res_data["data"]
assert res_data["data"]["createPolicy"]

new_urn = res_data["data"]["createPolicy"]

# Sleep for eventual consistency
time.sleep(3)

update_json = {
"query": """mutation updatePolicy($urn: String!, $input: PolicyUpdateInput!) {\n
updatePolicy(urn: $urn, input: $input) }""",
"variables": {
"urn": new_urn,
"input": {
"type": "METADATA",
"state": "ACTIVE",
"name": "Test Metadata Policy",
"description": "Updated Metadaata Policy",
"privileges": ["EDIT_ENTITY_TAGS", "EDIT_ENTITY_GLOSSARY_TERMS"],
"actors": {
"resourceOwners": False,
"allUsers": True,
"allGroups": False,
},
},
},
}

response = frontend_session.post(f"{get_frontend_url()}/api/v2/graphql", json=update_json)
response.raise_for_status()
res_data = response.json()

# Check updated was submitted successfully
assert res_data
assert res_data["data"]
assert res_data["data"]["updatePolicy"]
assert res_data["data"]["updatePolicy"] == new_urn

# Sleep for eventual consistency
time.sleep(3)

res_data = listPolicies(frontend_session)

assert res_data
assert res_data["data"]
assert res_data["data"]["listPolicies"]

# Verify that the updated policy appears in the list and has the appropriate changes
result = list(filter(
lambda x: x["urn"] == new_urn, res_data["data"]["listPolicies"]["policies"]
))
print(result)

assert len(result) == 1
assert result[0]["description"] == "Updated Metadaata Policy"
assert result[0]["privileges"] == ["EDIT_ENTITY_TAGS", "EDIT_ENTITY_GLOSSARY_TERMS"]
assert result[0]["actors"]["allUsers"] == True

# Now test that the policy can be deleted
json = {
"query": """mutation deletePolicy($urn: String!) {\n
deletePolicy(urn: $urn) }""",
"variables": {"urn": new_urn},
}

response = frontend_session.post(f"{get_frontend_url()}/api/v2/graphql", json=json)
response.raise_for_status()
res_data = response.json()

res_data = listPolicies(frontend_session)

assert res_data
assert res_data["data"]
assert res_data["data"]["listPolicies"]

# Verify that the URN is no longer in the list
result = filter(
lambda x: x["urn"] == new_urn,
res_data["data"]["listPolicies"]["policies"],
)
assert len(list(result)) == 0

def listPolicies(session):
json = {
"query": """query listPolicies($input: ListPoliciesInput!) {\n
listPolicies(input: $input) {\n
start\n
count\n
total\n
policies {\n
urn\n
type\n
name\n
description\n
state\n
resources {\n
type\n
allResources\n
resources\n
}\n
privileges\n
actors {\n
users\n
groups\n
allUsers\n
allGroups\n
resourceOwners\n
}\n
editable\n
}\n
}\n
}""",
"variables": {
"input": {
"start": "0",
"count": "20",
}
},
}
response = session.post(f"{get_frontend_url()}/api/v2/graphql", json=json)
response.raise_for_status()

return response.json()
Loading