diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/ResolverUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/ResolverUtils.java index a396a719ce4948..3959b325e9bf71 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/ResolverUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/ResolverUtils.java @@ -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 + * 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 buildFacetFilters(@Nullable List facetFilterInputs, @Nonnull Set validFacetFields) { diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/auth/ListAccessTokensResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/auth/ListAccessTokensResolver.java index 92298c4cd31cca..b6bd4a7d89c89d 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/auth/ListAccessTokensResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/auth/ListAccessTokensResolver.java @@ -1,14 +1,13 @@ 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; @@ -16,13 +15,10 @@ 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.*; @@ -35,8 +31,6 @@ public class ListAccessTokensResolver implements DataFetcher> { private static final String EXPIRES_AT_FIELD_NAME = "expiresAt"; - private static final Set FACET_FIELDS = - ImmutableSet.of("ownerUrn", "actorUrn", "name", "createdAt", "expiredAt", "description"); private final EntityClient _entityClient; diff --git a/metadata-service/auth-impl/src/main/java/com/datahub/authentication/token/StatefulTokenService.java b/metadata-service/auth-impl/src/main/java/com/datahub/authentication/token/StatefulTokenService.java index 6598cb70f7e116..b35ad32ee88abf 100644 --- a/metadata-service/auth-impl/src/main/java/com/datahub/authentication/token/StatefulTokenService.java +++ b/metadata-service/auth-impl/src/main/java/com/datahub/authentication/token/StatefulTokenService.java @@ -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) { + return false; + } + } + /** * Hashes the input after salting it. */ diff --git a/metadata-service/war/src/main/resources/boot/policies.json b/metadata-service/war/src/main/resources/boot/policies.json index 643f7a5e882a86..7a5b0781aae86c 100644 --- a/metadata-service/war/src/main/resources/boot/policies.json +++ b/metadata-service/war/src/main/resources/boot/policies.json @@ -74,7 +74,8 @@ "actors":{ "resourceOwners":false, "allUsers":true, - "allGroups":false + "allGroups":false, + "users":[] }, "privileges":[ "MANAGE_POLICIES", diff --git a/smoke-test/tests/delete/delete_test.py b/smoke-test/tests/delete/delete_test.py index a5a855daa23311..a7dde49487ebdd 100644 --- a/smoke-test/tests/delete/delete_test.py +++ b/smoke-test/tests/delete/delete_test.py @@ -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""" @@ -24,7 +40,7 @@ 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) @@ -32,13 +48,13 @@ def test_setup(): 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"]): platform = "urn:li:dataPlatform:kafka" dataset_name = "test-delete" @@ -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)) diff --git a/smoke-test/tests/policies/__init__.py b/smoke-test/tests/policies/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/smoke-test/tests/policies/test_policies.py b/smoke-test/tests/policies/test_policies.py new file mode 100644 index 00000000000000..b94765a75b0a44 --- /dev/null +++ b/smoke-test/tests/policies/test_policies.py @@ -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() diff --git a/smoke-test/tests/tokens/revokable_access_token_test.py b/smoke-test/tests/tokens/revokable_access_token_test.py index a28f33c4c3bd88..4a20105787d66b 100644 --- a/smoke-test/tests/tokens/revokable_access_token_test.py +++ b/smoke-test/tests/tokens/revokable_access_token_test.py @@ -1,12 +1,101 @@ -from time import sleep - +import os import pytest import requests -from tests.utils import get_frontend_url, ingest_file_via_rest +from time import sleep + +from datahub.cli.docker import check_local_docker_containers +from datahub.cli.ingest_cli import get_session_and_host +from tests.utils import get_frontend_url + +# 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.mark.dependency(depends=["test_healthchecks"]) +@pytest.fixture(scope='class', autouse=True) +def custom_user_setup(): + """Fixture to execute setup before and tear down after all tests are run""" + admin_session = loginAs("datahub", "datahub") + + res_data = removeUser(admin_session, "urn:li:corpuser:user") + assert res_data + assert "error" not in res_data + + # Test getting the invite token + get_invite_token_json = { + "query": """query getNativeUserInviteToken {\n + getNativeUserInviteToken{\n + inviteToken\n + }\n + }""" + } + + get_invite_token_response = admin_session.post(f"{get_frontend_url()}/api/v2/graphql", json=get_invite_token_json) + get_invite_token_response.raise_for_status() + get_invite_token_res_data = get_invite_token_response.json() + + assert get_invite_token_res_data + assert get_invite_token_res_data["data"] + invite_token = get_invite_token_res_data["data"]["getNativeUserInviteToken"]["inviteToken"] + assert invite_token is not None + assert "error" not in invite_token + + # Pass the invite token when creating the user + sign_up_json = { + "fullName": "Test User", + "email": "user", + "password": "user", + "title": "Date Engineer", + "inviteToken": invite_token + } + + sign_up_response = admin_session.post(f"{get_frontend_url()}/signUp", json=sign_up_json) + sign_up_response.raise_for_status() + assert sign_up_response + assert "error" not in sign_up_response + # Sleep for eventual consistency + sleep(3) + + # signUp will override the session cookie to the new user to be signed up. + admin_session.cookies.clear() + admin_session = loginAs("datahub", "datahub") + + # Make user created user is there. + res_data = listUsers(admin_session) + assert res_data["data"] + assert res_data["data"]["listUsers"] + assert {'username': 'user'} in res_data["data"]["listUsers"]["users"] + + yield + + # Delete created user + res_data = removeUser(admin_session, "urn:li:corpuser:user") + assert res_data + assert res_data['data'] + assert res_data['data']['removeUser'] == True + # Sleep for eventual consistency + sleep(3) + + # Make user created user is not there. + res_data = listUsers(admin_session) + assert res_data["data"] + assert res_data["data"]["listUsers"] + assert {'username': 'user'} not in res_data["data"]["listUsers"]["users"] + +@pytest.mark.dependency(depends=["test_healthchecks"]) @pytest.fixture(autouse=True) -def test_setup(): +def access_token_setup(): """Fixture to execute asserts before and after a test is run""" admin_session = loginAs("datahub", "datahub") @@ -16,22 +105,18 @@ def test_setup(): assert res_data["data"]["listAccessTokens"]["total"] == 0 assert not res_data["data"]["listAccessTokens"]["tokens"] - ingest_file_via_rest("tests/tokens/revokable_test_data.json") - - sleep(5) - yield - sleep(5) - # Clean up res_data = listAccessTokens(admin_session) for metadata in res_data["data"]["listAccessTokens"]["tokens"]: revokeAccessToken(admin_session, metadata["id"]) + # Sleep for eventual consistency + sleep(3) -@pytest.mark.dependency(depends=["test_healthchecks", "test_run_ingestion"]) -def test_admin_can_create_list_and_revoke_tokens(): +@pytest.mark.dependency(depends=["test_healthchecks"]) +def test_admin_can_create_list_and_revoke_tokens(wait_for_healthchecks): admin_session = loginAs("datahub", "datahub") # Using a super account, there should be no tokens @@ -47,11 +132,10 @@ def test_admin_can_create_list_and_revoke_tokens(): assert res_data["data"] assert res_data["data"]["createAccessToken"] assert res_data["data"]["createAccessToken"]["accessToken"] - assert ( - res_data["data"]["createAccessToken"]["metadata"]["actorUrn"] - == "urn:li:corpuser:datahub" - ) + assert res_data["data"]["createAccessToken"]["metadata"]["actorUrn"] == "urn:li:corpuser:datahub" admin_tokenId = res_data["data"]["createAccessToken"]["metadata"]["id"] + # Sleep for eventual consistency + sleep(3) # Using a super account, list the previously created token. res_data = listAccessTokens(admin_session) @@ -59,14 +143,8 @@ def test_admin_can_create_list_and_revoke_tokens(): assert res_data["data"] assert res_data["data"]["listAccessTokens"]["total"] is not None assert len(res_data["data"]["listAccessTokens"]["tokens"]) == 1 - assert ( - res_data["data"]["listAccessTokens"]["tokens"][1]["actorUrn"] - == "urn:li:corpuser:datahub" - ) - assert ( - res_data["data"]["listAccessTokens"]["tokens"][1]["ownerUrn"] - == "urn:li:corpuser:datahub" - ) + assert res_data["data"]["listAccessTokens"]["tokens"][0]["actorUrn"] == "urn:li:corpuser:datahub" + assert res_data["data"]["listAccessTokens"]["tokens"][0]["ownerUrn"] == "urn:li:corpuser:datahub" # Check that the super account can revoke tokens that it created res_data = revokeAccessToken(admin_session, admin_tokenId) @@ -74,6 +152,8 @@ def test_admin_can_create_list_and_revoke_tokens(): assert res_data["data"] assert res_data["data"]["revokeAccessToken"] assert res_data["data"]["revokeAccessToken"] == True + # Sleep for eventual consistency + sleep(3) # Using a super account, there should be no tokens res_data = listAccessTokens(admin_session) @@ -82,9 +162,8 @@ def test_admin_can_create_list_and_revoke_tokens(): assert res_data["data"]["listAccessTokens"]["total"] is not None assert len(res_data["data"]["listAccessTokens"]["tokens"]) == 0 - -@pytest.mark.dependency(depends=["test_healthchecks", "test_run_ingestion"]) -def test_admin_can_create_and_revoke_tokens_for_other_user(): +@pytest.mark.dependency(depends=["test_healthchecks"]) +def test_admin_can_create_and_revoke_tokens_for_other_user(wait_for_healthchecks): admin_session = loginAs("datahub", "datahub") # Using a super account, there should be no tokens @@ -100,11 +179,10 @@ def test_admin_can_create_and_revoke_tokens_for_other_user(): assert res_data["data"] assert res_data["data"]["createAccessToken"] assert res_data["data"]["createAccessToken"]["accessToken"] - assert ( - res_data["data"]["createAccessToken"]["metadata"]["actorUrn"] - == "urn:li:corpuser:user" - ) + assert res_data["data"]["createAccessToken"]["metadata"]["actorUrn"] == "urn:li:corpuser:user" user_tokenId = res_data["data"]["createAccessToken"]["metadata"]["id"] + # Sleep for eventual consistency + sleep(3) # Using a super account, list the previously created tokens. res_data = listAccessTokens(admin_session) @@ -112,14 +190,8 @@ def test_admin_can_create_and_revoke_tokens_for_other_user(): assert res_data["data"] assert res_data["data"]["listAccessTokens"]["total"] is not None assert len(res_data["data"]["listAccessTokens"]["tokens"]) == 1 - assert ( - res_data["data"]["listAccessTokens"]["tokens"][0]["actorUrn"] - == "urn:li:corpuser:user" - ) - assert ( - res_data["data"]["listAccessTokens"]["tokens"][0]["ownerUrn"] - == "urn:li:corpuser:datahub" - ) + assert res_data["data"]["listAccessTokens"]["tokens"][0]["actorUrn"] == "urn:li:corpuser:user" + assert res_data["data"]["listAccessTokens"]["tokens"][0]["ownerUrn"] == "urn:li:corpuser:datahub" # Check that the super account can revoke tokens that it created for another user res_data = revokeAccessToken(admin_session, user_tokenId) @@ -127,6 +199,8 @@ def test_admin_can_create_and_revoke_tokens_for_other_user(): assert res_data["data"] assert res_data["data"]["revokeAccessToken"] assert res_data["data"]["revokeAccessToken"] == True + # Sleep for eventual consistency + sleep(3) # Using a super account, there should be no tokens res_data = listAccessTokens(admin_session) @@ -135,12 +209,9 @@ def test_admin_can_create_and_revoke_tokens_for_other_user(): assert res_data["data"]["listAccessTokens"]["total"] is not None assert len(res_data["data"]["listAccessTokens"]["tokens"]) == 0 - -""" -@pytest.mark.dependency(depends=["test_healthchecks", "test_run_ingestion"]) -def test_non_admin_can_create_list_revoke_tokens(): +@pytest.mark.dependency(depends=["test_healthchecks"]) +def test_non_admin_can_create_list_revoke_tokens(wait_for_healthchecks): user_session = loginAs("user", "user") - admin_session = loginAs("datahub", "datahub") # Normal user should be able to generate token for himself. res_data = generateAccessToken_v2(user_session, "urn:li:corpuser:user") @@ -150,6 +221,8 @@ def test_non_admin_can_create_list_revoke_tokens(): assert res_data["data"]["createAccessToken"]["accessToken"] assert res_data["data"]["createAccessToken"]["metadata"]["actorUrn"] == "urn:li:corpuser:user" user_tokenId = res_data["data"]["createAccessToken"]["metadata"]["id"] + # Sleep for eventual consistency + sleep(3) # User should be able to list his own token res_data = listAccessTokens(user_session, [{"field": "ownerUrn","value": "urn:li:corpuser:user"}]) @@ -167,6 +240,8 @@ def test_non_admin_can_create_list_revoke_tokens(): assert res_data["data"] assert res_data["data"]["revokeAccessToken"] assert res_data["data"]["revokeAccessToken"] == True + # Sleep for eventual consistency + sleep(3) # Using a normal account, check that all its tokens where removed. res_data = listAccessTokens(user_session, [{"field": "ownerUrn","value": "urn:li:corpuser:user"}]) @@ -175,9 +250,8 @@ def test_non_admin_can_create_list_revoke_tokens(): assert res_data["data"]["listAccessTokens"]["total"] is not None assert len(res_data["data"]["listAccessTokens"]["tokens"]) == 0 -@pytest.mark.dependency(depends=["test_healthchecks", "test_run_ingestion"]) -def test_admin_can_manage_tokens_generated_by_other_user(): - user_session = loginAs("user", "user") +@pytest.mark.dependency(depends=["test_healthchecks"]) +def test_admin_can_manage_tokens_generated_by_other_user(wait_for_healthchecks): admin_session = loginAs("datahub", "datahub") # Using a super account, there should be no tokens @@ -187,17 +261,23 @@ def test_admin_can_manage_tokens_generated_by_other_user(): assert res_data["data"]["listAccessTokens"]["total"] is not None assert len(res_data["data"]["listAccessTokens"]["tokens"]) == 0 - # Normal user should be able to generate token for himself. + admin_session.cookies.clear() + user_session = loginAs("user", "user") res_data = generateAccessToken_v2(user_session, "urn:li:corpuser:user") assert res_data assert res_data["data"] assert res_data["data"]["createAccessToken"] assert res_data["data"]["createAccessToken"]["accessToken"] assert res_data["data"]["createAccessToken"]["metadata"]["actorUrn"] == "urn:li:corpuser:user" + assert res_data["data"]["createAccessToken"]["metadata"]["ownerUrn"] == "urn:li:corpuser:user" user_tokenId = res_data["data"]["createAccessToken"]["metadata"]["id"] + # Sleep for eventual consistency + sleep(3) # Admin should be able to list other tokens - res_data = listAccessTokens(admin_session, [{"field": "actorUrn","value": "urn:li:corpuser:user"}]) + user_session.cookies.clear() + admin_session = loginAs("datahub", "datahub") + res_data = listAccessTokens(admin_session, [{"field": "ownerUrn","value": "urn:li:corpuser:user"}]) assert res_data assert res_data["data"] assert res_data["data"]["listAccessTokens"]["total"] is not None @@ -207,54 +287,41 @@ def test_admin_can_manage_tokens_generated_by_other_user(): assert res_data["data"]["listAccessTokens"]["tokens"][0]["id"] == user_tokenId # Admin can delete token created by someone else. + admin_session.cookies.clear() + admin_session = loginAs("datahub", "datahub") res_data = revokeAccessToken(admin_session, user_tokenId) assert res_data assert res_data["data"] assert res_data["data"]["revokeAccessToken"] assert res_data["data"]["revokeAccessToken"] == True + # Sleep for eventual consistency + sleep(3) # Using a normal account, check that all its tokens where removed. - res_data = listAccessTokens(user_session, [{"field": "actorUrn","value": "urn:li:corpuser:user"}]) + user_session.cookies.clear() + user_session = loginAs("user", "user") + res_data = listAccessTokens(user_session, [{"field": "ownerUrn","value": "urn:li:corpuser:user"}]) assert res_data assert res_data["data"] assert res_data["data"]["listAccessTokens"]["total"] is not None assert len(res_data["data"]["listAccessTokens"]["tokens"]) == 0 # Using the super account, check that all tokens where removed. - res_data = listAccessTokens(admin_session, [{"field": "actorUrn","value": "urn:li:corpuser:user"}]) + admin_session = loginAs("datahub", "datahub") + res_data = listAccessTokens(admin_session, [{"field": "ownerUrn","value": "urn:li:corpuser:user"}]) assert res_data assert res_data["data"] assert res_data["data"]["listAccessTokens"]["total"] is not None assert len(res_data["data"]["listAccessTokens"]["tokens"]) == 0 -@pytest.mark.dependency(depends=["test_healthchecks", "test_run_ingestion"]) -def test_non_admin_can_not_generate_tokens_for_others(): +@pytest.mark.dependency(depends=["test_healthchecks"]) +def test_non_admin_can_not_generate_tokens_for_others(wait_for_healthchecks): user_session = loginAs("user", "user") - # Normal user should not be able to generate token for another user + # Normal user should not be able to generate token for another user res_data = generateAccessToken_v2(user_session, "urn:li:corpuser:datahub") assert res_data assert res_data["errors"] assert res_data["errors"][0]["message"] == "Unauthorized to perform this action. Please contact your DataHub administrator." -""" - - -def generateAccessToken_v1(session, actorUrn): - # Create new token - json = { - "query": """query getAccessToken($input: GetAccessTokenInput!) {\n - getAccessToken(input: $input) {\n - accessToken\n - }\n - }""", - "variables": { - "input": {"type": "PERSONAL", "actorUrn": actorUrn, "duration": "ONE_HOUR"} - }, - } - - response = session.post(f"{get_frontend_url()}/api/v2/graphql", json=json) - response.raise_for_status() - return response.json() - def generateAccessToken_v2(session, actorUrn): # Create new token @@ -283,8 +350,6 @@ def generateAccessToken_v2(session, actorUrn): response = session.post(f"{get_frontend_url()}/api/v2/graphql", json=json) response.raise_for_status() - - sleep(5) return response.json() @@ -330,8 +395,8 @@ def revokeAccessToken(session, tokenId): } response = session.post(f"{get_frontend_url()}/api/v2/graphql", json=json) - sleep(5) response.raise_for_status() + return response.json() @@ -346,3 +411,53 @@ def loginAs(username, password): response.raise_for_status() return session + + +def removeUser(session, urn): + # Remove user + json = { + "query": """mutation removeUser($urn: String!) {\n + removeUser(urn: $urn) + }""", + "variables": { + "urn": urn + } + } + + response = session.post( + f"{get_frontend_url()}/api/v2/graphql", json=json + ) + + response.raise_for_status() + return response.json() + + +def listUsers(session): + input = { + "start": "0", + "count": "20", + } + + # list users + json = { + "query": """query listUsers($input: ListUsersInput!) {\n + listUsers(input: $input) {\n + start\n + count\n + total\n + users {\n + username\n + } + } + }""", + "variables": { + "input": input + } + } + + response = session.post( + f"{get_frontend_url()}/api/v2/graphql", json=json + ) + + response.raise_for_status() + return response.json()