diff --git a/datahub-frontend/app/auth/AuthModule.java b/datahub-frontend/app/auth/AuthModule.java index 32dfba00d47dbf..d0d17fda263926 100644 --- a/datahub-frontend/app/auth/AuthModule.java +++ b/datahub-frontend/app/auth/AuthModule.java @@ -25,7 +25,7 @@ import java.util.Collections; import io.datahubproject.metadata.context.ActorContext; -import io.datahubproject.metadata.context.AuthorizerContext; +import io.datahubproject.metadata.context.AuthorizationContext; import io.datahubproject.metadata.context.EntityRegistryContext; import io.datahubproject.metadata.context.OperationContext; import io.datahubproject.metadata.context.OperationContextConfig; @@ -183,10 +183,10 @@ protected OperationContext provideOperationContext( return OperationContext.builder() .operationContextConfig(systemConfig) .systemActorContext(systemActorContext) + // Authorizer.EMPTY is fine since it doesn't actually apply to system auth + .authorizationContext(AuthorizationContext.builder().authorizer(Authorizer.EMPTY).build()) .searchContext(SearchContext.EMPTY) .entityRegistryContext(EntityRegistryContext.builder().build(EmptyEntityRegistry.EMPTY)) - // Authorizer.EMPTY doesn't actually apply to system auth - .authorizerContext(AuthorizerContext.builder().authorizer(Authorizer.EMPTY).build()) .build(systemAuthentication); } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/authorization/AuthorizationUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/authorization/AuthorizationUtils.java index 4fb49d79a0aa70..ca60acaa805387 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/authorization/AuthorizationUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/authorization/AuthorizationUtils.java @@ -11,7 +11,6 @@ import com.datahub.authorization.ConjunctivePrivilegeGroup; import com.datahub.authorization.DisjunctivePrivilegeGroup; import com.datahub.authorization.EntitySpec; -import com.datahub.plugins.auth.authorization.Authorizer; import com.google.common.collect.ImmutableList; import com.linkedin.common.urn.Urn; import com.linkedin.datahub.graphql.QueryContext; @@ -21,7 +20,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; -import java.util.Set; import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.reflect.ConstructorUtils; @@ -40,29 +38,25 @@ public class AuthorizationUtils { public static boolean canManageUsersAndGroups(@Nonnull QueryContext context) { return AuthUtil.isAuthorizedEntityType( - context.getActorUrn(), - context.getAuthorizer(), + context.getOperationContext(), MANAGE, List.of(CORP_USER_ENTITY_NAME, CORP_GROUP_ENTITY_NAME)); } public static boolean canManagePolicies(@Nonnull QueryContext context) { return AuthUtil.isAuthorizedEntityType( - context.getActorUrn(), context.getAuthorizer(), MANAGE, List.of(POLICY_ENTITY_NAME)); + context.getOperationContext(), MANAGE, List.of(POLICY_ENTITY_NAME)); } public static boolean canGeneratePersonalAccessToken(@Nonnull QueryContext context) { return AuthUtil.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - PoliciesConfig.GENERATE_PERSONAL_ACCESS_TOKENS_PRIVILEGE) - || AuthUtil.isAuthorized( - context.getAuthorizer(), context.getActorUrn(), MANAGE_ACCESS_TOKENS); + context.getOperationContext(), PoliciesConfig.GENERATE_PERSONAL_ACCESS_TOKENS_PRIVILEGE) + || AuthUtil.isAuthorized(context.getOperationContext(), MANAGE_ACCESS_TOKENS); } public static boolean canManageTokens(@Nonnull QueryContext context) { return AuthUtil.isAuthorizedEntityType( - context.getActorUrn(), context.getAuthorizer(), MANAGE, List.of(ACCESS_TOKEN_ENTITY_NAME)); + context.getOperationContext(), MANAGE, List.of(ACCESS_TOKEN_ENTITY_NAME)); } /** @@ -78,13 +72,12 @@ public static boolean canCreateDomains(@Nonnull QueryContext context) { new ConjunctivePrivilegeGroup( ImmutableList.of(PoliciesConfig.MANAGE_DOMAINS_PRIVILEGE.getType())))); - return AuthUtil.isAuthorized( - context.getAuthorizer(), context.getActorUrn(), orPrivilegeGroups, null); + return AuthUtil.isAuthorized(context.getOperationContext(), orPrivilegeGroups, null); } public static boolean canManageDomains(@Nonnull QueryContext context) { return AuthUtil.isAuthorized( - context.getAuthorizer(), context.getActorUrn(), PoliciesConfig.MANAGE_DOMAINS_PRIVILEGE); + context.getOperationContext(), PoliciesConfig.MANAGE_DOMAINS_PRIVILEGE); } /** @@ -100,25 +93,22 @@ public static boolean canCreateTags(@Nonnull QueryContext context) { new ConjunctivePrivilegeGroup( ImmutableList.of(PoliciesConfig.MANAGE_TAGS_PRIVILEGE.getType())))); - return AuthUtil.isAuthorized( - context.getAuthorizer(), context.getActorUrn(), orPrivilegeGroups, null); + return AuthUtil.isAuthorized(context.getOperationContext(), orPrivilegeGroups, null); } public static boolean canManageTags(@Nonnull QueryContext context) { return AuthUtil.isAuthorized( - context.getAuthorizer(), context.getActorUrn(), PoliciesConfig.MANAGE_TAGS_PRIVILEGE); + context.getOperationContext(), PoliciesConfig.MANAGE_TAGS_PRIVILEGE); } public static boolean canDeleteEntity(@Nonnull Urn entityUrn, @Nonnull QueryContext context) { return AuthUtil.isAuthorizedEntityUrns( - context.getAuthorizer(), context.getActorUrn(), DELETE, List.of(entityUrn)); + context.getOperationContext(), DELETE, List.of(entityUrn)); } public static boolean canManageUserCredentials(@Nonnull QueryContext context) { return AuthUtil.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - PoliciesConfig.MANAGE_USER_CREDENTIALS_PRIVILEGE); + context.getOperationContext(), PoliciesConfig.MANAGE_USER_CREDENTIALS_PRIVILEGE); } public static boolean canEditGroupMembers( @@ -130,12 +120,7 @@ public static boolean canEditGroupMembers( new ConjunctivePrivilegeGroup( ImmutableList.of(PoliciesConfig.EDIT_GROUP_MEMBERS_PRIVILEGE.getType())))); - return isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - CORP_GROUP_ENTITY_NAME, - groupUrnStr, - orPrivilegeGroups); + return isAuthorized(context, CORP_GROUP_ENTITY_NAME, groupUrnStr, orPrivilegeGroups); } public static boolean canCreateGlobalAnnouncements(@Nonnull QueryContext context) { @@ -149,27 +134,21 @@ public static boolean canCreateGlobalAnnouncements(@Nonnull QueryContext context ImmutableList.of( PoliciesConfig.MANAGE_GLOBAL_ANNOUNCEMENTS_PRIVILEGE.getType())))); - return AuthUtil.isAuthorized( - context.getAuthorizer(), context.getActorUrn(), orPrivilegeGroups, null); + return AuthUtil.isAuthorized(context.getOperationContext(), orPrivilegeGroups, null); } public static boolean canManageGlobalAnnouncements(@Nonnull QueryContext context) { return AuthUtil.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - PoliciesConfig.MANAGE_GLOBAL_ANNOUNCEMENTS_PRIVILEGE); + context.getOperationContext(), PoliciesConfig.MANAGE_GLOBAL_ANNOUNCEMENTS_PRIVILEGE); } public static boolean canManageGlobalViews(@Nonnull QueryContext context) { - return AuthUtil.isAuthorized( - context.getAuthorizer(), context.getActorUrn(), PoliciesConfig.MANAGE_GLOBAL_VIEWS); + return AuthUtil.isAuthorized(context.getOperationContext(), PoliciesConfig.MANAGE_GLOBAL_VIEWS); } public static boolean canManageOwnershipTypes(@Nonnull QueryContext context) { return AuthUtil.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - PoliciesConfig.MANAGE_GLOBAL_OWNERSHIP_TYPES); + context.getOperationContext(), PoliciesConfig.MANAGE_GLOBAL_OWNERSHIP_TYPES); } public static boolean canEditProperties(@Nonnull Urn targetUrn, @Nonnull QueryContext context) { @@ -183,11 +162,7 @@ public static boolean canEditProperties(@Nonnull Urn targetUrn, @Nonnull QueryCo ImmutableList.of(PoliciesConfig.EDIT_ENTITY_PROPERTIES_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - targetUrn.getEntityType(), - targetUrn.toString(), - orPrivilegeGroups); + context, targetUrn.getEntityType(), targetUrn.toString(), orPrivilegeGroups); } public static boolean canEditEntityQueries( @@ -202,11 +177,7 @@ public static boolean canEditEntityQueries( .allMatch( entityUrn -> isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - entityUrn.getEntityType(), - entityUrn.toString(), - orPrivilegeGroups)); + context, entityUrn.getEntityType(), entityUrn.toString(), orPrivilegeGroups)); } public static boolean canCreateQuery( @@ -251,28 +222,7 @@ public static boolean canView(@Nonnull OperationContext opContext, @Nonnull Urn && !opContext.isSystemAuth() && VIEW_RESTRICTED_ENTITY_TYPES.contains(urn.getEntityType())) { - return opContext - .getViewAuthorizationContext() - .map( - viewAuthContext -> { - - // check cache - if (viewAuthContext.canView(Set.of(urn))) { - return true; - } - - if (!canViewEntity( - opContext.getSessionAuthentication().getActor().toUrnStr(), - opContext.getAuthorizerContext().getAuthorizer(), - urn)) { - return false; - } - - // cache viewable urn - viewAuthContext.addViewableUrns(Set.of(urn)); - return true; - }) - .orElse(false); + return canViewEntity(opContext, urn); } return true; } @@ -386,38 +336,32 @@ public static T restrictEntity(@Nonnull Object entity, Class clazz) { public static boolean canManageStructuredProperties(@Nonnull QueryContext context) { return AuthUtil.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - PoliciesConfig.MANAGE_STRUCTURED_PROPERTIES_PRIVILEGE); + context.getOperationContext(), PoliciesConfig.MANAGE_STRUCTURED_PROPERTIES_PRIVILEGE); } public static boolean canManageForms(@Nonnull QueryContext context) { return AuthUtil.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - PoliciesConfig.MANAGE_DOCUMENTATION_FORMS_PRIVILEGE); + context.getOperationContext(), PoliciesConfig.MANAGE_DOCUMENTATION_FORMS_PRIVILEGE); } public static boolean canManageFeatures(@Nonnull QueryContext context) { return AuthUtil.isAuthorized( - context.getAuthorizer(), context.getActorUrn(), PoliciesConfig.MANAGE_FEATURES_PRIVILEGE); + context.getOperationContext(), PoliciesConfig.MANAGE_FEATURES_PRIVILEGE); } public static boolean isAuthorized( - @Nonnull Authorizer authorizer, - @Nonnull String actor, + @Nonnull QueryContext context, @Nonnull String resourceType, @Nonnull String resource, @Nonnull DisjunctivePrivilegeGroup privilegeGroup) { final EntitySpec resourceSpec = new EntitySpec(resourceType, resource); - return AuthUtil.isAuthorized(authorizer, actor, privilegeGroup, resourceSpec); + return AuthUtil.isAuthorized(context.getOperationContext(), privilegeGroup, resourceSpec); } public static boolean isViewDatasetUsageAuthorized( final QueryContext context, final Urn resourceUrn) { return AuthUtil.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), + context.getOperationContext(), PoliciesConfig.VIEW_DATASET_USAGE_PRIVILEGE, new EntitySpec(resourceUrn.getEntityType(), resourceUrn.toString())); } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/MeResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/MeResolver.java index 1f4ebbb88bf1a6..b1101ae3ee8657 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/MeResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/MeResolver.java @@ -108,20 +108,19 @@ public CompletableFuture get(DataFetchingEnvironment environm /** Returns true if the authenticated user has privileges to view analytics. */ private boolean canViewAnalytics(final QueryContext context) { - return isAuthorized(context.getActorUrn(), context.getAuthorizer(), ANALYTICS, READ); + return isAuthorized(context.getOperationContext(), ANALYTICS, READ); } /** Returns true if the authenticated user has privileges to manage policies analytics. */ private boolean canManagePolicies(final QueryContext context) { return isAuthorizedEntityType( - context.getActorUrn(), context.getAuthorizer(), MANAGE, List.of(POLICY_ENTITY_NAME)); + context.getOperationContext(), MANAGE, List.of(POLICY_ENTITY_NAME)); } /** Returns true if the authenticated user has privileges to manage users & groups. */ private boolean canManageUsersGroups(final QueryContext context) { return isAuthorizedEntityType( - context.getActorUrn(), - context.getAuthorizer(), + context.getOperationContext(), MANAGE, List.of(CORP_USER_ENTITY_NAME, CORP_GROUP_ENTITY_NAME)); } @@ -129,46 +128,37 @@ private boolean canManageUsersGroups(final QueryContext context) { /** Returns true if the authenticated user has privileges to generate personal access tokens */ private boolean canGeneratePersonalAccessToken(final QueryContext context) { return isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - PoliciesConfig.GENERATE_PERSONAL_ACCESS_TOKENS_PRIVILEGE); + context.getOperationContext(), PoliciesConfig.GENERATE_PERSONAL_ACCESS_TOKENS_PRIVILEGE); } /** Returns true if the authenticated user has privileges to view tests. */ private boolean canViewTests(final QueryContext context) { - return isAuthorized( - context.getAuthorizer(), context.getActorUrn(), PoliciesConfig.VIEW_TESTS_PRIVILEGE); + return isAuthorized(context.getOperationContext(), PoliciesConfig.VIEW_TESTS_PRIVILEGE); } /** Returns true if the authenticated user has privileges to manage (add or remove) tests. */ private boolean canManageTests(final QueryContext context) { - return isAuthorized( - context.getAuthorizer(), context.getActorUrn(), PoliciesConfig.MANAGE_TESTS_PRIVILEGE); + return isAuthorized(context.getOperationContext(), PoliciesConfig.MANAGE_TESTS_PRIVILEGE); } /** Returns true if the authenticated user has privileges to manage domains */ private boolean canManageDomains(final QueryContext context) { - return isAuthorized( - context.getAuthorizer(), context.getActorUrn(), PoliciesConfig.MANAGE_DOMAINS_PRIVILEGE); + return isAuthorized(context.getOperationContext(), PoliciesConfig.MANAGE_DOMAINS_PRIVILEGE); } /** Returns true if the authenticated user has privileges to manage access tokens */ private boolean canManageTokens(final QueryContext context) { - return isAuthorized( - context.getAuthorizer(), context.getActorUrn(), PoliciesConfig.MANAGE_ACCESS_TOKENS); + return isAuthorized(context.getOperationContext(), PoliciesConfig.MANAGE_ACCESS_TOKENS); } /** Returns true if the authenticated user has privileges to manage glossaries */ private boolean canManageGlossaries(final QueryContext context) { - return isAuthorized( - context.getAuthorizer(), context.getActorUrn(), PoliciesConfig.MANAGE_GLOSSARIES_PRIVILEGE); + return isAuthorized(context.getOperationContext(), PoliciesConfig.MANAGE_GLOSSARIES_PRIVILEGE); } /** Returns true if the authenticated user has privileges to manage user credentials */ private boolean canManageUserCredentials(@Nonnull QueryContext context) { return isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - PoliciesConfig.MANAGE_USER_CREDENTIALS_PRIVILEGE); + context.getOperationContext(), PoliciesConfig.MANAGE_USER_CREDENTIALS_PRIVILEGE); } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/assertion/AssertionUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/assertion/AssertionUtils.java index 757ff38de60065..a632ab5487000e 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/assertion/AssertionUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/assertion/AssertionUtils.java @@ -18,10 +18,6 @@ public static boolean isAuthorizedToEditAssertionFromAssertee( new ConjunctivePrivilegeGroup( ImmutableList.of(PoliciesConfig.EDIT_ENTITY_ASSERTIONS_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - asserteeUrn.getEntityType(), - asserteeUrn.toString(), - orPrivilegeGroups); + context, asserteeUrn.getEntityType(), asserteeUrn.toString(), orPrivilegeGroups); } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/assertion/DeleteAssertionResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/assertion/DeleteAssertionResolver.java index 1cf233221d4d33..d36611da0dc4db 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/assertion/DeleteAssertionResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/assertion/DeleteAssertionResolver.java @@ -113,11 +113,7 @@ private boolean isAuthorizedToDeleteAssertionFromAssertee( new ConjunctivePrivilegeGroup( ImmutableList.of(PoliciesConfig.EDIT_ENTITY_ASSERTIONS_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - asserteeUrn.getEntityType(), - asserteeUrn.toString(), - orPrivilegeGroups); + context, asserteeUrn.getEntityType(), asserteeUrn.toString(), orPrivilegeGroups); } private Urn getAsserteeUrnFromInfo(final AssertionInfo info) { diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/businessattribute/BusinessAttributeAuthorizationUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/businessattribute/BusinessAttributeAuthorizationUtils.java index 041f5e9ade77f0..364a5c982b0e03 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/businessattribute/BusinessAttributeAuthorizationUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/businessattribute/BusinessAttributeAuthorizationUtils.java @@ -20,8 +20,7 @@ public static boolean canCreateBusinessAttribute(@Nonnull QueryContext context) new ConjunctivePrivilegeGroup( ImmutableList.of( PoliciesConfig.MANAGE_BUSINESS_ATTRIBUTE_PRIVILEGE.getType())))); - return AuthUtil.isAuthorized( - context.getAuthorizer(), context.getActorUrn(), orPrivilegeGroups, null); + return AuthUtil.isAuthorized(context.getOperationContext(), orPrivilegeGroups, null); } public static boolean canManageBusinessAttribute(@Nonnull QueryContext context) { @@ -31,7 +30,6 @@ public static boolean canManageBusinessAttribute(@Nonnull QueryContext context) new ConjunctivePrivilegeGroup( ImmutableList.of( PoliciesConfig.MANAGE_BUSINESS_ATTRIBUTE_PRIVILEGE.getType())))); - return AuthUtil.isAuthorized( - context.getAuthorizer(), context.getActorUrn(), orPrivilegeGroups, null); + return AuthUtil.isAuthorized(context.getOperationContext(), orPrivilegeGroups, null); } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/connection/ConnectionUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/connection/ConnectionUtils.java index bcdd6460ae75ed..30c2fb672a0c30 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/connection/ConnectionUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/connection/ConnectionUtils.java @@ -14,9 +14,7 @@ public class ConnectionUtils { */ public static boolean canManageConnections(@Nonnull QueryContext context) { return AuthUtil.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - PoliciesConfig.MANAGE_CONNECTIONS_PRIVILEGE); + context.getOperationContext(), PoliciesConfig.MANAGE_CONNECTIONS_PRIVILEGE); } private ConnectionUtils() {} diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/datacontract/DataContractUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/datacontract/DataContractUtils.java index 3dd7cd9df63838..a04024a29dc97b 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/datacontract/DataContractUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/datacontract/DataContractUtils.java @@ -21,11 +21,7 @@ public static boolean canEditDataContract(@Nonnull QueryContext context, Urn ent PoliciesConfig.EDIT_ENTITY_DATA_CONTRACT_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - entityUrn.getEntityType(), - entityUrn.toString(), - orPrivilegeGroups); + context, entityUrn.getEntityType(), entityUrn.toString(), orPrivilegeGroups); } private DataContractUtils() {} diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataproduct/DataProductAuthorizationUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataproduct/DataProductAuthorizationUtils.java index f6fe11a587a39b..a8357fc0a1a3ff 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataproduct/DataProductAuthorizationUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataproduct/DataProductAuthorizationUtils.java @@ -30,11 +30,7 @@ public static boolean isAuthorizedToUpdateDataProductsForEntity( PoliciesConfig.EDIT_ENTITY_DATA_PRODUCTS_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - entityUrn.getEntityType(), - entityUrn.toString(), - orPrivilegeGroups); + context, entityUrn.getEntityType(), entityUrn.toString(), orPrivilegeGroups); } public static boolean isAuthorizedToManageDataProducts( @@ -47,11 +43,7 @@ public static boolean isAuthorizedToManageDataProducts( ImmutableList.of(PoliciesConfig.MANAGE_DATA_PRODUCTS_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - domainUrn.getEntityType(), - domainUrn.toString(), - orPrivilegeGroups); + context, domainUrn.getEntityType(), domainUrn.toString(), orPrivilegeGroups); } public static boolean isAuthorizedToEditDataProduct( @@ -60,10 +52,6 @@ public static boolean isAuthorizedToEditDataProduct( new DisjunctivePrivilegeGroup(ImmutableList.of(ALL_PRIVILEGES_GROUP)); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - dataProductUrn.getEntityType(), - dataProductUrn.toString(), - orPrivilegeGroups); + context, dataProductUrn.getEntityType(), dataProductUrn.toString(), orPrivilegeGroups); } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataset/DatasetStatsSummaryResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataset/DatasetStatsSummaryResolver.java index 7d3603ec050e94..bab7ecaf302f54 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataset/DatasetStatsSummaryResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataset/DatasetStatsSummaryResolver.java @@ -49,7 +49,7 @@ public CompletableFuture get(DataFetchingEnvironment enviro log.debug( "User {} is not authorized to view profile information for dataset {}", context.getActorUrn(), - resourceUrn.toString()); + resourceUrn); return null; } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/deprecation/UpdateDeprecationResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/deprecation/UpdateDeprecationResolver.java index c568ff6db3a27d..a2230cf6b6e886 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/deprecation/UpdateDeprecationResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/deprecation/UpdateDeprecationResolver.java @@ -100,11 +100,7 @@ private boolean isAuthorizedToUpdateDeprecationForEntity( ImmutableList.of(PoliciesConfig.EDIT_ENTITY_DEPRECATION_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - entityUrn.getEntityType(), - entityUrn.toString(), - orPrivilegeGroups); + context, entityUrn.getEntityType(), entityUrn.toString(), orPrivilegeGroups); } public static Boolean validateUpdateDeprecationInput( diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/entity/EntityPrivilegesResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/entity/EntityPrivilegesResolver.java index b25f5598b44bc0..67ab9bb2878141 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/entity/EntityPrivilegesResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/entity/EntityPrivilegesResolver.java @@ -106,8 +106,7 @@ private EntityPrivileges getGlossaryNodePrivileges(Urn nodeUrn, QueryContext con } private boolean canEditEntityLineage(Urn urn, QueryContext context) { - return AuthUtil.isAuthorizedUrns( - context.getAuthorizer(), context.getActorUrn(), LINEAGE, UPDATE, List.of(urn)); + return AuthUtil.isAuthorizedUrns(context.getOperationContext(), LINEAGE, UPDATE, List.of(urn)); } private EntityPrivileges getDatasetPrivileges(Urn urn, QueryContext context) { diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/incident/RaiseIncidentResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/incident/RaiseIncidentResolver.java index 454ba693da95a7..68aef26bf4aa17 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/incident/RaiseIncidentResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/incident/RaiseIncidentResolver.java @@ -123,10 +123,6 @@ private boolean isAuthorizedToCreateIncidentForResource( ImmutableList.of(PoliciesConfig.EDIT_ENTITY_INCIDENTS_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - resourceUrn.getEntityType(), - resourceUrn.toString(), - orPrivilegeGroups); + context, resourceUrn.getEntityType(), resourceUrn.toString(), orPrivilegeGroups); } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/incident/UpdateIncidentStatusResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/incident/UpdateIncidentStatusResolver.java index d51ceab31e60ec..dee92247ba311a 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/incident/UpdateIncidentStatusResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/incident/UpdateIncidentStatusResolver.java @@ -103,10 +103,6 @@ private boolean isAuthorizedToUpdateIncident(final Urn resourceUrn, final QueryC new ConjunctivePrivilegeGroup( ImmutableList.of(PoliciesConfig.EDIT_ENTITY_INCIDENTS_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - resourceUrn.getEntityType(), - resourceUrn.toString(), - orPrivilegeGroups); + context, resourceUrn.getEntityType(), resourceUrn.toString(), orPrivilegeGroups); } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/ingest/IngestionAuthUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/ingest/IngestionAuthUtils.java index 24d0e946145054..be8d4fa7b8c68d 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/ingest/IngestionAuthUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/ingest/IngestionAuthUtils.java @@ -14,15 +14,12 @@ public class IngestionAuthUtils { public static boolean canManageIngestion(@Nonnull QueryContext context) { return AuthUtil.isAuthorizedEntityType( - context.getActorUrn(), - context.getAuthorizer(), - MANAGE, - List.of(INGESTION_SOURCE_ENTITY_NAME)); + context.getOperationContext(), MANAGE, List.of(INGESTION_SOURCE_ENTITY_NAME)); } public static boolean canManageSecrets(@Nonnull QueryContext context) { return isAuthorizedEntityType( - context.getActorUrn(), context.getAuthorizer(), MANAGE, List.of(SECRETS_ENTITY_NAME)); + context.getOperationContext(), MANAGE, List.of(SECRETS_ENTITY_NAME)); } private IngestionAuthUtils() {} diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/lineage/UpdateLineageResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/lineage/UpdateLineageResolver.java index d462fb0820aa03..928e33d44c84e7 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/lineage/UpdateLineageResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/lineage/UpdateLineageResolver.java @@ -211,11 +211,7 @@ private boolean isAuthorized( @Nonnull final Urn urn, @Nonnull final DisjunctivePrivilegeGroup orPrivilegesGroup) { return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - urn.getEntityType(), - urn.toString(), - orPrivilegesGroup); + context, urn.getEntityType(), urn.toString(), orPrivilegesGroup); } private void checkLineageEdgePrivileges( diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/load/TimeSeriesAspectResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/load/TimeSeriesAspectResolver.java index 8fc26e3cec9d06..4d4b898618bf9c 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/load/TimeSeriesAspectResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/load/TimeSeriesAspectResolver.java @@ -78,8 +78,7 @@ private boolean isAuthorized(QueryContext context, String urn) { if (_entityName.equals(Constants.DATASET_ENTITY_NAME) && _aspectName.equals(Constants.DATASET_PROFILE_ASPECT_NAME)) { return AuthUtil.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), + context.getOperationContext(), PoliciesConfig.VIEW_DATASET_PROFILE_PRIVILEGE, new EntitySpec(_entityName, urn)); } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/DescriptionUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/DescriptionUtils.java index 6e2fc77e703af3..917f1b1c1d574d 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/DescriptionUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/DescriptionUtils.java @@ -336,11 +336,7 @@ public static boolean isAuthorizedToUpdateFieldDescription( PoliciesConfig.EDIT_DATASET_COL_DESCRIPTION_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - targetUrn.getEntityType(), - targetUrn.toString(), - orPrivilegeGroups); + context, targetUrn.getEntityType(), targetUrn.toString(), orPrivilegeGroups); } public static boolean isAuthorizedToUpdateDomainDescription( @@ -353,11 +349,7 @@ public static boolean isAuthorizedToUpdateDomainDescription( ImmutableList.of(PoliciesConfig.EDIT_ENTITY_DOCS_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - targetUrn.getEntityType(), - targetUrn.toString(), - orPrivilegeGroups); + context, targetUrn.getEntityType(), targetUrn.toString(), orPrivilegeGroups); } public static boolean isAuthorizedToUpdateContainerDescription( @@ -370,11 +362,7 @@ public static boolean isAuthorizedToUpdateContainerDescription( ImmutableList.of(PoliciesConfig.EDIT_ENTITY_DOCS_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - targetUrn.getEntityType(), - targetUrn.toString(), - orPrivilegeGroups); + context, targetUrn.getEntityType(), targetUrn.toString(), orPrivilegeGroups); } public static boolean isAuthorizedToUpdateDescription( @@ -387,11 +375,7 @@ public static boolean isAuthorizedToUpdateDescription( ImmutableList.of(PoliciesConfig.EDIT_ENTITY_DOCS_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - targetUrn.getEntityType(), - targetUrn.toString(), - orPrivilegeGroups); + context, targetUrn.getEntityType(), targetUrn.toString(), orPrivilegeGroups); } public static void updateMlModelDescription( diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DeleteUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DeleteUtils.java index 73e7f9ec1cca7c..1d3a9c229e63e2 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DeleteUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DeleteUtils.java @@ -25,7 +25,7 @@ private DeleteUtils() {} public static boolean isAuthorizedToDeleteEntity(@Nonnull QueryContext context, Urn entityUrn) { return AuthUtil.isAuthorizedEntityUrns( - context.getAuthorizer(), context.getActorUrn(), DELETE, List.of(entityUrn)); + context.getOperationContext(), DELETE, List.of(entityUrn)); } public static void updateStatusForResources( diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DeprecationUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DeprecationUtils.java index 3912ffa6226bff..541224b02c1b52 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DeprecationUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DeprecationUtils.java @@ -42,11 +42,7 @@ public static boolean isAuthorizedToUpdateDeprecationForEntity( ImmutableList.of(PoliciesConfig.EDIT_ENTITY_DEPRECATION_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - entityUrn.getEntityType(), - entityUrn.toString(), - orPrivilegeGroups); + context, entityUrn.getEntityType(), entityUrn.toString(), orPrivilegeGroups); } public static void updateDeprecationForResources( diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DomainUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DomainUtils.java index 1114cf7344e8f4..1dcdd988f5e7c1 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DomainUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DomainUtils.java @@ -67,11 +67,7 @@ public static boolean isAuthorizedToUpdateDomainsForEntity( ImmutableList.of(PoliciesConfig.EDIT_ENTITY_DOMAINS_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - entityUrn.getEntityType(), - entityUrn.toString(), - orPrivilegeGroups); + context, entityUrn.getEntityType(), entityUrn.toString(), orPrivilegeGroups); } public static void setDomainForResources( diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/EmbedUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/EmbedUtils.java index 15c93904fc3bdd..5ebb434b21c9f4 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/EmbedUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/EmbedUtils.java @@ -28,10 +28,6 @@ public static boolean isAuthorizedToUpdateEmbedForEntity( ImmutableList.of(PoliciesConfig.EDIT_ENTITY_EMBED_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - entityUrn.getEntityType(), - entityUrn.toString(), - orPrivilegeGroups); + context, entityUrn.getEntityType(), entityUrn.toString(), orPrivilegeGroups); } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/GlossaryUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/GlossaryUtils.java index 16df9911f3bec3..0d8e505a948e5a 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/GlossaryUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/GlossaryUtils.java @@ -33,7 +33,7 @@ private GlossaryUtils() {} */ public static boolean canManageGlossaries(@Nonnull QueryContext context) { return AuthUtil.isAuthorized( - context.getAuthorizer(), context.getActorUrn(), PoliciesConfig.MANAGE_GLOSSARIES_PRIVILEGE); + context.getOperationContext(), PoliciesConfig.MANAGE_GLOSSARIES_PRIVILEGE); } /** @@ -79,11 +79,7 @@ public static boolean hasManagePrivilege( ImmutableList.of(new ConjunctivePrivilegeGroup(ImmutableList.of(privilege.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - parentNodeUrn.getEntityType(), - parentNodeUrn.toString(), - orPrivilegeGroups); + context, parentNodeUrn.getEntityType(), parentNodeUrn.toString(), orPrivilegeGroups); } /** diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LabelUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LabelUtils.java index 3eac819a9cc48d..cffd019307f34a 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LabelUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LabelUtils.java @@ -251,11 +251,7 @@ public static boolean isAuthorizedToUpdateTags( : PoliciesConfig.EDIT_ENTITY_TAGS_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - targetUrn.getEntityType(), - targetUrn.toString(), - orPrivilegeGroups); + context, targetUrn.getEntityType(), targetUrn.toString(), orPrivilegeGroups); } public static boolean isAuthorizedToUpdateTerms( @@ -277,11 +273,7 @@ public static boolean isAuthorizedToUpdateTerms( : PoliciesConfig.EDIT_ENTITY_GLOSSARY_TERMS_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - targetUrn.getEntityType(), - targetUrn.toString(), - orPrivilegeGroups); + context, targetUrn.getEntityType(), targetUrn.toString(), orPrivilegeGroups); } public static void validateResourceAndLabel( diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LinkUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LinkUtils.java index a2d4692db5b7b7..e6f9d09412119a 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LinkUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LinkUtils.java @@ -115,11 +115,7 @@ public static boolean isAuthorizedToUpdateLinks(@Nonnull QueryContext context, U ImmutableList.of(PoliciesConfig.EDIT_ENTITY_DOC_LINKS_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - resourceUrn.getEntityType(), - resourceUrn.toString(), - orPrivilegeGroups); + context, resourceUrn.getEntityType(), resourceUrn.toString(), orPrivilegeGroups); } public static Boolean validateAddRemoveInput( diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/OwnerUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/OwnerUtils.java index ddb795189c0e3d..2f2b52f7ab5864 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/OwnerUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/OwnerUtils.java @@ -205,11 +205,7 @@ public static void validateAuthorizedToUpdateOwners( boolean authorized = AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - resourceUrn.getEntityType(), - resourceUrn.toString(), - orPrivilegeGroups); + context, resourceUrn.getEntityType(), resourceUrn.toString(), orPrivilegeGroups); if (!authorized) { throw new AuthorizationException( "Unauthorized to update owners. Please contact your DataHub administrator."); diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/operation/ReportOperationResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/operation/ReportOperationResolver.java index 6ef3222bc068f2..48f231fee5093a 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/operation/ReportOperationResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/operation/ReportOperationResolver.java @@ -137,10 +137,6 @@ private boolean isAuthorizedToReportOperationForResource( ImmutableList.of(PoliciesConfig.EDIT_ENTITY_OPERATIONS_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - resourceUrn.getEntityType(), - resourceUrn.toString(), - orPrivilegeGroups); + context, resourceUrn.getEntityType(), resourceUrn.toString(), orPrivilegeGroups); } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/policy/PolicyAuthUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/policy/PolicyAuthUtils.java index 7babe63745f727..775a4aaf6e090b 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/policy/PolicyAuthUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/policy/PolicyAuthUtils.java @@ -12,7 +12,7 @@ public class PolicyAuthUtils { static boolean canManagePolicies(@Nonnull QueryContext context) { return AuthUtil.isAuthorizedEntityType( - context.getActorUrn(), context.getAuthorizer(), MANAGE, List.of(POLICY_ENTITY_NAME)); + context.getOperationContext(), MANAGE, List.of(POLICY_ENTITY_NAME)); } private PolicyAuthUtils() {} diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/tag/SetTagColorResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/tag/SetTagColorResolver.java index 7a059ed9a1aeda..9a8e0fc3647274 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/tag/SetTagColorResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/tag/SetTagColorResolver.java @@ -101,10 +101,6 @@ public static boolean isAuthorizedToSetTagColor(@Nonnull QueryContext context, U ImmutableList.of(PoliciesConfig.EDIT_TAG_COLOR_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - entityUrn.getEntityType(), - entityUrn.toString(), - orPrivilegeGroups); + context, entityUrn.getEntityType(), entityUrn.toString(), orPrivilegeGroups); } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/test/TestUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/test/TestUtils.java index 020064ed643c88..80e5abd245b281 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/test/TestUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/test/TestUtils.java @@ -22,13 +22,13 @@ public class TestUtils { /** Returns true if the authenticated user is able to view tests. */ public static boolean canViewTests(@Nonnull QueryContext context) { return AuthUtil.isAuthorized( - context.getAuthorizer(), context.getActorUrn(), PoliciesConfig.VIEW_TESTS_PRIVILEGE); + context.getOperationContext(), PoliciesConfig.VIEW_TESTS_PRIVILEGE); } /** Returns true if the authenticated user is able to manage tests. */ public static boolean canManageTests(@Nonnull QueryContext context) { return AuthUtil.isAuthorized( - context.getAuthorizer(), context.getActorUrn(), PoliciesConfig.MANAGE_TESTS_PRIVILEGE); + context.getOperationContext(), PoliciesConfig.MANAGE_TESTS_PRIVILEGE); } public static TestDefinition mapDefinition(final TestDefinitionInput testDefInput) { diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/chart/ChartType.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/chart/ChartType.java index fe9b511f4a7dde..054dcec15af32d 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/chart/ChartType.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/chart/ChartType.java @@ -242,11 +242,7 @@ private boolean isAuthorized( // Decide whether the current principal should be allowed to update the Dataset. final DisjunctivePrivilegeGroup orPrivilegeGroups = getAuthorizedPrivileges(update); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - PoliciesConfig.CHART_PRIVILEGES.getResourceType(), - urn, - orPrivilegeGroups); + context, PoliciesConfig.CHART_PRIVILEGES.getResourceType(), urn, orPrivilegeGroups); } private DisjunctivePrivilegeGroup getAuthorizedPrivileges(final ChartUpdateInput updateInput) { diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/corpgroup/CorpGroupType.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/corpgroup/CorpGroupType.java index 16d2940a392447..27b97bfb2124f7 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/corpgroup/CorpGroupType.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/corpgroup/CorpGroupType.java @@ -188,11 +188,7 @@ private boolean isAuthorizedToUpdate( // Decide whether the current principal should be allowed to update the Dataset. final DisjunctivePrivilegeGroup orPrivilegeGroups = getAuthorizedPrivileges(input); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - PoliciesConfig.CORP_GROUP_PRIVILEGES.getResourceType(), - urn, - orPrivilegeGroups); + context, PoliciesConfig.CORP_GROUP_PRIVILEGES.getResourceType(), urn, orPrivilegeGroups); } private DisjunctivePrivilegeGroup getAuthorizedPrivileges( diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/corpuser/CorpUserType.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/corpuser/CorpUserType.java index 3c2bfd7225edf5..5a812daa264bbf 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/corpuser/CorpUserType.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/corpuser/CorpUserType.java @@ -183,8 +183,7 @@ private boolean isAuthorizedToUpdate( // information. return context.getActorUrn().equals(urn) || AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), + context, PoliciesConfig.CORP_GROUP_PRIVILEGES.getResourceType(), urn, orPrivilegeGroups); diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/dashboard/DashboardType.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/dashboard/DashboardType.java index 89a41732109964..6ad362e5905904 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/dashboard/DashboardType.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/dashboard/DashboardType.java @@ -241,11 +241,7 @@ private boolean isAuthorized( // Decide whether the current principal should be allowed to update the Dataset. final DisjunctivePrivilegeGroup orPrivilegeGroups = getAuthorizedPrivileges(update); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - PoliciesConfig.DASHBOARD_PRIVILEGES.getResourceType(), - urn, - orPrivilegeGroups); + context, PoliciesConfig.DASHBOARD_PRIVILEGES.getResourceType(), urn, orPrivilegeGroups); } private DisjunctivePrivilegeGroup getAuthorizedPrivileges( diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/dataflow/DataFlowType.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/dataflow/DataFlowType.java index f8248aedf289c0..3a697517bdecee 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/dataflow/DataFlowType.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/dataflow/DataFlowType.java @@ -228,11 +228,7 @@ private boolean isAuthorized( // Decide whether the current principal should be allowed to update the Dataset. final DisjunctivePrivilegeGroup orPrivilegeGroups = getAuthorizedPrivileges(update); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - PoliciesConfig.DATA_FLOW_PRIVILEGES.getResourceType(), - urn, - orPrivilegeGroups); + context, PoliciesConfig.DATA_FLOW_PRIVILEGES.getResourceType(), urn, orPrivilegeGroups); } private DisjunctivePrivilegeGroup getAuthorizedPrivileges(final DataFlowUpdateInput updateInput) { diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/datajob/DataJobType.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/datajob/DataJobType.java index 1e1de615b5911b..b32832a28d5d57 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/datajob/DataJobType.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/datajob/DataJobType.java @@ -229,11 +229,7 @@ private boolean isAuthorized( // Decide whether the current principal should be allowed to update the Dataset. final DisjunctivePrivilegeGroup orPrivilegeGroups = getAuthorizedPrivileges(update); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - PoliciesConfig.DATA_JOB_PRIVILEGES.getResourceType(), - urn, - orPrivilegeGroups); + context, PoliciesConfig.DATA_JOB_PRIVILEGES.getResourceType(), urn, orPrivilegeGroups); } private DisjunctivePrivilegeGroup getAuthorizedPrivileges(final DataJobUpdateInput updateInput) { diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/dataset/DatasetType.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/dataset/DatasetType.java index 46c810ac00d621..65b5d39e315692 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/dataset/DatasetType.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/dataset/DatasetType.java @@ -283,11 +283,7 @@ private boolean isAuthorized( // Decide whether the current principal should be allowed to update the Dataset. final DisjunctivePrivilegeGroup orPrivilegeGroups = getAuthorizedPrivileges(update); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - PoliciesConfig.DATASET_PRIVILEGES.getResourceType(), - urn, - orPrivilegeGroups); + context, PoliciesConfig.DATASET_PRIVILEGES.getResourceType(), urn, orPrivilegeGroups); } private DisjunctivePrivilegeGroup getAuthorizedPrivileges(final DatasetUpdateInput updateInput) { diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/ermodelrelationship/ERModelRelationshipType.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/ermodelrelationship/ERModelRelationshipType.java index fd340aca119b59..ed52cc5486e92f 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/ermodelrelationship/ERModelRelationshipType.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/ermodelrelationship/ERModelRelationshipType.java @@ -211,11 +211,7 @@ public static boolean canUpdateERModelRelation( new DisjunctivePrivilegeGroup( ImmutableList.of(editPrivilegesGroup, specificPrivilegeGroup)); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - resourceUrn.getEntityType(), - resourceUrn.toString(), - orPrivilegeGroups); + context, resourceUrn.getEntityType(), resourceUrn.toString(), orPrivilegeGroups); } public static boolean canCreateERModelRelation( @@ -232,18 +228,10 @@ public static boolean canCreateERModelRelation( new DisjunctivePrivilegeGroup(ImmutableList.of(editPrivilegesGroup, createPrivilegesGroup)); boolean sourcePrivilege = AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - sourceUrn.getEntityType(), - sourceUrn.toString(), - orPrivilegeGroups); + context, sourceUrn.getEntityType(), sourceUrn.toString(), orPrivilegeGroups); boolean destinationPrivilege = AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - destinationUrn.getEntityType(), - destinationUrn.toString(), - orPrivilegeGroups); + context, destinationUrn.getEntityType(), destinationUrn.toString(), orPrivilegeGroups); return sourcePrivilege && destinationPrivilege; } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/notebook/NotebookType.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/notebook/NotebookType.java index 8eeda9331ad8ff..a6f29c1917397f 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/notebook/NotebookType.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/notebook/NotebookType.java @@ -226,11 +226,7 @@ private boolean isAuthorized( // Decide whether the current principal should be allowed to update the Dataset. final DisjunctivePrivilegeGroup orPrivilegeGroups = getAuthorizedPrivileges(update); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), - PoliciesConfig.NOTEBOOK_PRIVILEGES.getResourceType(), - urn, - orPrivilegeGroups); + context, PoliciesConfig.NOTEBOOK_PRIVILEGES.getResourceType(), urn, orPrivilegeGroups); } private DisjunctivePrivilegeGroup getAuthorizedPrivileges(final NotebookUpdateInput updateInput) { diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/tag/TagType.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/tag/TagType.java index 3c07b242e9d813..9a5d0f1f41b8cc 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/tag/TagType.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/tag/TagType.java @@ -164,8 +164,7 @@ private boolean isAuthorized(@Nonnull TagUpdateInput update, @Nonnull QueryConte // Decide whether the current principal should be allowed to update the Dataset. final DisjunctivePrivilegeGroup orPrivilegeGroups = getAuthorizedPrivileges(update); return AuthorizationUtils.isAuthorized( - context.getAuthorizer(), - context.getActorUrn(), + context, PoliciesConfig.TAG_PRIVILEGES.getResourceType(), update.getUrn(), orPrivilegeGroups); diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/dashboard/DashboardStatsSummaryTest.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/dashboard/DashboardStatsSummaryTest.java index 837dec2f528ed3..76879addc5e6f3 100644 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/dashboard/DashboardStatsSummaryTest.java +++ b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/dashboard/DashboardStatsSummaryTest.java @@ -7,7 +7,6 @@ import com.datahub.authentication.Authentication; import com.datahub.authorization.AuthorizationResult; -import com.datahub.plugins.auth.authorization.Authorizer; import com.google.common.collect.ImmutableList; import com.linkedin.common.urn.UrnUtils; import com.linkedin.dashboard.DashboardUsageStatistics; @@ -55,13 +54,10 @@ public void testGetSuccess() throws Exception { // Execute resolver DashboardStatsSummaryResolver resolver = new DashboardStatsSummaryResolver(mockClient); QueryContext mockContext = Mockito.mock(QueryContext.class); - Authorizer mockAuthorizor = mock(Authorizer.class); - when(mockAuthorizor.authorize(any())) - .thenAnswer( - args -> - new AuthorizationResult(args.getArgument(0), AuthorizationResult.Type.ALLOW, "")); - when(mockContext.getAuthorizer()).thenReturn(mockAuthorizor); - Mockito.when(mockContext.getAuthentication()).thenReturn(Mockito.mock(Authentication.class)); + when(mockContext.getOperationContext()).thenReturn(mock(OperationContext.class)); + when(mockContext.getOperationContext().authorize(any(), any())) + .thenReturn(new AuthorizationResult(null, AuthorizationResult.Type.ALLOW, "")); + DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class); Mockito.when(mockEnv.getSource()).thenReturn(TEST_SOURCE); Mockito.when(mockEnv.getContext()).thenReturn(mockContext); diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/dataset/DatasetStatsSummaryResolverTest.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/dataset/DatasetStatsSummaryResolverTest.java index f8a7e4fc6a13c8..57dd5ebc86e86a 100644 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/dataset/DatasetStatsSummaryResolverTest.java +++ b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/dataset/DatasetStatsSummaryResolverTest.java @@ -4,7 +4,6 @@ import com.datahub.authentication.Authentication; import com.datahub.authorization.AuthorizationResult; -import com.datahub.plugins.auth.authorization.Authorizer; import com.google.common.collect.ImmutableList; import com.linkedin.common.urn.UrnUtils; import com.linkedin.datahub.graphql.QueryContext; @@ -66,14 +65,15 @@ public void testGetSuccess() throws Exception { DatasetStatsSummaryResolver resolver = new DatasetStatsSummaryResolver(mockClient); QueryContext mockContext = Mockito.mock(QueryContext.class); Mockito.when(mockContext.getActorUrn()).thenReturn("urn:li:corpuser:test"); - Authorizer mockAuthorizer = Mockito.mock(Authorizer.class); + AuthorizationResult mockAuthorizerResult = Mockito.mock(AuthorizationResult.class); Mockito.when(mockAuthorizerResult.getType()).thenReturn(AuthorizationResult.Type.ALLOW); - Mockito.when(mockAuthorizer.authorize(any())).thenReturn(mockAuthorizerResult); - Mockito.when(mockContext.getAuthorizer()).thenReturn(mockAuthorizer); - Mockito.when(mockContext.getAuthentication()).thenReturn(Mockito.mock(Authentication.class)); + Mockito.when(mockContext.getOperationContext()) .thenReturn(Mockito.mock(OperationContext.class)); + Mockito.when(mockContext.getOperationContext().authorize(any(), any())) + .thenReturn(mockAuthorizerResult); + DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class); Mockito.when(mockEnv.getSource()).thenReturn(TEST_SOURCE); Mockito.when(mockEnv.getContext()).thenReturn(mockContext); diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/glossary/GlossaryUtilsTest.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/glossary/GlossaryUtilsTest.java index 448c3420625929..25d48ddec74069 100644 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/glossary/GlossaryUtilsTest.java +++ b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/glossary/GlossaryUtilsTest.java @@ -2,13 +2,13 @@ import static com.linkedin.metadata.Constants.GLOSSARY_NODE_INFO_ASPECT_NAME; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.testng.Assert.*; -import com.datahub.authentication.Authentication; -import com.datahub.authorization.AuthorizationRequest; import com.datahub.authorization.AuthorizationResult; import com.datahub.authorization.EntitySpec; -import com.datahub.plugins.auth.authorization.Authorizer; import com.google.common.collect.ImmutableSet; import com.linkedin.common.urn.GlossaryNodeUrn; import com.linkedin.common.urn.Urn; @@ -22,27 +22,27 @@ import com.linkedin.entity.client.EntityClient; import com.linkedin.glossary.GlossaryNodeInfo; import com.linkedin.metadata.Constants; +import io.datahubproject.metadata.context.OperationContext; import java.util.HashMap; import java.util.Map; -import java.util.Optional; import org.mockito.Mockito; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class GlossaryUtilsTest { private final String userUrn = "urn:li:corpuser:authorized"; private final QueryContext mockContext = Mockito.mock(QueryContext.class); - private final Authorizer mockAuthorizer = Mockito.mock(Authorizer.class); private final EntityClient mockClient = Mockito.mock(EntityClient.class); private final Urn parentNodeUrn = UrnUtils.getUrn("urn:li:glossaryNode:parent_node"); private final Urn parentNodeUrn1 = UrnUtils.getUrn("urn:li:glossaryNode:parent_node1"); private final Urn parentNodeUrn2 = UrnUtils.getUrn("urn:li:glossaryNode:parent_node2"); private final Urn parentNodeUrn3 = UrnUtils.getUrn("urn:li:glossaryNode:parent_node3"); + @BeforeMethod private void setUpTests() throws Exception { - Mockito.when(mockContext.getAuthorizer()).thenReturn(mockAuthorizer); Mockito.when(mockContext.getActorUrn()).thenReturn(userUrn); - Mockito.when(mockContext.getAuthentication()).thenReturn(Mockito.mock(Authentication.class)); + when(mockContext.getOperationContext()).thenReturn(mock(OperationContext.class)); GlossaryNodeInfo parentNode1 = new GlossaryNodeInfo() @@ -84,25 +84,25 @@ private void setUpTests() throws Exception { Mockito.when( mockClient.getV2( any(), - Mockito.eq(Constants.GLOSSARY_NODE_ENTITY_NAME), - Mockito.eq(parentNodeUrn1), - Mockito.eq(ImmutableSet.of(GLOSSARY_NODE_INFO_ASPECT_NAME)))) + eq(Constants.GLOSSARY_NODE_ENTITY_NAME), + eq(parentNodeUrn1), + eq(ImmutableSet.of(GLOSSARY_NODE_INFO_ASPECT_NAME)))) .thenReturn(new EntityResponse().setAspects(new EnvelopedAspectMap(parentNode1Aspects))); Mockito.when( mockClient.getV2( any(), - Mockito.eq(Constants.GLOSSARY_NODE_ENTITY_NAME), - Mockito.eq(parentNodeUrn2), - Mockito.eq(ImmutableSet.of(GLOSSARY_NODE_INFO_ASPECT_NAME)))) + eq(Constants.GLOSSARY_NODE_ENTITY_NAME), + eq(parentNodeUrn2), + eq(ImmutableSet.of(GLOSSARY_NODE_INFO_ASPECT_NAME)))) .thenReturn(new EntityResponse().setAspects(new EnvelopedAspectMap(parentNode2Aspects))); Mockito.when( mockClient.getV2( any(), - Mockito.eq(Constants.GLOSSARY_NODE_ENTITY_NAME), - Mockito.eq(parentNodeUrn3), - Mockito.eq(ImmutableSet.of(GLOSSARY_NODE_INFO_ASPECT_NAME)))) + eq(Constants.GLOSSARY_NODE_ENTITY_NAME), + eq(parentNodeUrn3), + eq(ImmutableSet.of(GLOSSARY_NODE_INFO_ASPECT_NAME)))) .thenReturn(new EntityResponse().setAspects(new EnvelopedAspectMap(parentNode3Aspects))); final EntitySpec resourceSpec3 = @@ -120,19 +120,14 @@ private void setUpTests() throws Exception { private void mockAuthRequest( String privilege, AuthorizationResult.Type allowOrDeny, EntitySpec resourceSpec) { - final AuthorizationRequest authorizationRequest = - new AuthorizationRequest( - userUrn, - privilege, - resourceSpec != null ? Optional.of(resourceSpec) : Optional.empty()); AuthorizationResult result = Mockito.mock(AuthorizationResult.class); Mockito.when(result.getType()).thenReturn(allowOrDeny); - Mockito.when(mockAuthorizer.authorize(Mockito.eq(authorizationRequest))).thenReturn(result); + when(mockContext.getOperationContext().authorize(eq(privilege), eq(resourceSpec))) + .thenReturn(result); } @Test public void testCanManageGlossariesAuthorized() throws Exception { - setUpTests(); mockAuthRequest("MANAGE_GLOSSARIES", AuthorizationResult.Type.ALLOW, null); assertTrue(GlossaryUtils.canManageGlossaries(mockContext)); @@ -140,7 +135,6 @@ public void testCanManageGlossariesAuthorized() throws Exception { @Test public void testCanManageGlossariesUnauthorized() throws Exception { - setUpTests(); mockAuthRequest("MANAGE_GLOSSARIES", AuthorizationResult.Type.DENY, null); assertFalse(GlossaryUtils.canManageGlossaries(mockContext)); @@ -148,7 +142,6 @@ public void testCanManageGlossariesUnauthorized() throws Exception { @Test public void testCanManageChildrenEntitiesWithManageGlossaries() throws Exception { - setUpTests(); // they have the MANAGE_GLOSSARIES platform privilege mockAuthRequest("MANAGE_GLOSSARIES", AuthorizationResult.Type.ALLOW, null); @@ -157,7 +150,6 @@ public void testCanManageChildrenEntitiesWithManageGlossaries() throws Exception @Test public void testCanManageChildrenEntitiesNoParentNode() throws Exception { - setUpTests(); // they do NOT have the MANAGE_GLOSSARIES platform privilege mockAuthRequest("MANAGE_GLOSSARIES", AuthorizationResult.Type.DENY, null); @@ -166,7 +158,6 @@ public void testCanManageChildrenEntitiesNoParentNode() throws Exception { @Test public void testCanManageChildrenEntitiesAuthorized() throws Exception { - setUpTests(); // they do NOT have the MANAGE_GLOSSARIES platform privilege mockAuthRequest("MANAGE_GLOSSARIES", AuthorizationResult.Type.DENY, null); @@ -179,7 +170,6 @@ public void testCanManageChildrenEntitiesAuthorized() throws Exception { @Test public void testCanManageChildrenEntitiesUnauthorized() throws Exception { - setUpTests(); // they do NOT have the MANAGE_GLOSSARIES platform privilege mockAuthRequest("MANAGE_GLOSSARIES", AuthorizationResult.Type.DENY, null); @@ -193,7 +183,6 @@ public void testCanManageChildrenEntitiesUnauthorized() throws Exception { @Test public void testCanManageChildrenRecursivelyEntitiesAuthorized() throws Exception { - setUpTests(); // they do NOT have the MANAGE_GLOSSARIES platform privilege mockAuthRequest("MANAGE_GLOSSARIES", AuthorizationResult.Type.DENY, null); @@ -214,7 +203,6 @@ public void testCanManageChildrenRecursivelyEntitiesAuthorized() throws Exceptio @Test public void testCanManageChildrenRecursivelyEntitiesUnauthorized() throws Exception { - setUpTests(); // they do NOT have the MANAGE_GLOSSARIES platform privilege mockAuthRequest("MANAGE_GLOSSARIES", AuthorizationResult.Type.DENY, null); @@ -235,7 +223,6 @@ public void testCanManageChildrenRecursivelyEntitiesUnauthorized() throws Except @Test public void testCanManageChildrenRecursivelyEntitiesAuthorizedLevel2() throws Exception { - setUpTests(); // they do NOT have the MANAGE_GLOSSARIES platform privilege mockAuthRequest("MANAGE_GLOSSARIES", AuthorizationResult.Type.DENY, null); @@ -252,7 +239,6 @@ public void testCanManageChildrenRecursivelyEntitiesAuthorizedLevel2() throws Ex @Test public void testCanManageChildrenRecursivelyEntitiesUnauthorizedLevel2() throws Exception { - setUpTests(); // they do NOT have the MANAGE_GLOSSARIES platform privilege mockAuthRequest("MANAGE_GLOSSARIES", AuthorizationResult.Type.DENY, null); @@ -269,7 +255,6 @@ public void testCanManageChildrenRecursivelyEntitiesUnauthorizedLevel2() throws @Test public void testCanManageChildrenRecursivelyEntitiesNoLevel2() throws Exception { - setUpTests(); // they do NOT have the MANAGE_GLOSSARIES platform privilege mockAuthRequest("MANAGE_GLOSSARIES", AuthorizationResult.Type.DENY, null); diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/ingest/IngestTestUtils.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/ingest/IngestTestUtils.java index e0555f5886b8bb..963bdf93bc9f1f 100644 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/ingest/IngestTestUtils.java +++ b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/ingest/IngestTestUtils.java @@ -1,11 +1,13 @@ package com.linkedin.datahub.graphql.resolvers.ingest; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.testng.Assert.*; -import com.datahub.authentication.Authentication; import com.datahub.authorization.AuthorizationResult; -import com.datahub.plugins.auth.authorization.Authorizer; +import com.datahub.authorization.EntitySpec; import com.google.common.collect.ImmutableMap; import com.linkedin.common.urn.Urn; import com.linkedin.data.template.StringMap; @@ -38,14 +40,9 @@ public static QueryContext getMockAllowContext() { QueryContext mockContext = Mockito.mock(QueryContext.class); Mockito.when(mockContext.getActorUrn()).thenReturn("urn:li:corpuser:test"); - Authorizer mockAuthorizer = Mockito.mock(Authorizer.class); - AuthorizationResult result = Mockito.mock(AuthorizationResult.class); - Mockito.when(result.getType()).thenReturn(AuthorizationResult.Type.ALLOW); - Mockito.when(mockAuthorizer.authorize(Mockito.any())).thenReturn(result); - - Mockito.when(mockContext.getAuthorizer()).thenReturn(mockAuthorizer); - Mockito.when(mockContext.getAuthentication()).thenReturn(Mockito.mock(Authentication.class)); - Mockito.when(mockContext.getOperationContext()).thenReturn(mock(OperationContext.class)); + when(mockContext.getOperationContext()).thenReturn(mock(OperationContext.class)); + when(mockContext.getOperationContext().authorize(any(), nullable(EntitySpec.class))) + .thenReturn(new AuthorizationResult(null, AuthorizationResult.Type.ALLOW, "")); return mockContext; } @@ -53,13 +50,9 @@ public static QueryContext getMockDenyContext() { QueryContext mockContext = Mockito.mock(QueryContext.class); Mockito.when(mockContext.getActorUrn()).thenReturn("urn:li:corpuser:test"); - Authorizer mockAuthorizer = Mockito.mock(Authorizer.class); - AuthorizationResult result = Mockito.mock(AuthorizationResult.class); - Mockito.when(result.getType()).thenReturn(AuthorizationResult.Type.DENY); - Mockito.when(mockAuthorizer.authorize(Mockito.any())).thenReturn(result); - - Mockito.when(mockContext.getAuthorizer()).thenReturn(mockAuthorizer); - Mockito.when(mockContext.getAuthentication()).thenReturn(Mockito.mock(Authentication.class)); + when(mockContext.getOperationContext()).thenReturn(mock(OperationContext.class)); + when(mockContext.getOperationContext().authorize(any(), nullable(EntitySpec.class))) + .thenReturn(new AuthorizationResult(null, AuthorizationResult.Type.DENY, "")); return mockContext; } diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/ingest/IngestionAuthUtilsTest.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/ingest/IngestionAuthUtilsTest.java index f3e27d91f39df0..ba7f80deec4b77 100644 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/ingest/IngestionAuthUtilsTest.java +++ b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/ingest/IngestionAuthUtilsTest.java @@ -1,13 +1,16 @@ package com.linkedin.datahub.graphql.resolvers.ingest; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.testng.Assert.*; import com.datahub.authorization.AuthorizationRequest; import com.datahub.authorization.AuthorizationResult; import com.datahub.authorization.EntitySpec; -import com.datahub.plugins.auth.authorization.Authorizer; import com.linkedin.datahub.graphql.QueryContext; import com.linkedin.metadata.Constants; +import io.datahubproject.metadata.context.OperationContext; import java.util.Optional; import org.mockito.Mockito; import org.testng.annotations.Test; @@ -17,19 +20,18 @@ public class IngestionAuthUtilsTest { @Test public void testCanManageIngestionAuthorized() throws Exception { QueryContext mockContext = Mockito.mock(QueryContext.class); - Authorizer mockAuthorizer = Mockito.mock(Authorizer.class); - - AuthorizationRequest request = - new AuthorizationRequest( - "urn:li:corpuser:authorized", - "MANAGE_INGESTION", - Optional.of(new EntitySpec(Constants.INGESTION_SOURCE_ENTITY_NAME, ""))); + when(mockContext.getOperationContext()).thenReturn(mock(OperationContext.class)); AuthorizationResult result = Mockito.mock(AuthorizationResult.class); Mockito.when(result.getType()).thenReturn(AuthorizationResult.Type.ALLOW); - Mockito.when(mockAuthorizer.authorize(Mockito.eq(request))).thenReturn(result); + Mockito.when( + mockContext + .getOperationContext() + .authorize( + eq("MANAGE_INGESTION"), + eq(new EntitySpec(Constants.INGESTION_SOURCE_ENTITY_NAME, "")))) + .thenReturn(result); - Mockito.when(mockContext.getAuthorizer()).thenReturn(mockAuthorizer); Mockito.when(mockContext.getActorUrn()).thenReturn("urn:li:corpuser:authorized"); assertTrue(IngestionAuthUtils.canManageIngestion(mockContext)); @@ -38,19 +40,18 @@ public void testCanManageIngestionAuthorized() throws Exception { @Test public void testCanManageIngestionUnauthorized() throws Exception { QueryContext mockContext = Mockito.mock(QueryContext.class); - Authorizer mockAuthorizer = Mockito.mock(Authorizer.class); - - AuthorizationRequest request = - new AuthorizationRequest( - "urn:li:corpuser:unauthorized", - "MANAGE_INGESTION", - Optional.of(new EntitySpec(Constants.INGESTION_SOURCE_ENTITY_NAME, ""))); + when(mockContext.getOperationContext()).thenReturn(mock(OperationContext.class)); AuthorizationResult result = Mockito.mock(AuthorizationResult.class); Mockito.when(result.getType()).thenReturn(AuthorizationResult.Type.DENY); - Mockito.when(mockAuthorizer.authorize(Mockito.eq(request))).thenReturn(result); + Mockito.when( + mockContext + .getOperationContext() + .authorize( + eq("MANAGE_INGESTION"), + eq(new EntitySpec(Constants.INGESTION_SOURCE_ENTITY_NAME, "")))) + .thenReturn(result); - Mockito.when(mockContext.getAuthorizer()).thenReturn(mockAuthorizer); Mockito.when(mockContext.getActorUrn()).thenReturn("urn:li:corpuser:unauthorized"); assertFalse(IngestionAuthUtils.canManageIngestion(mockContext)); @@ -59,19 +60,17 @@ public void testCanManageIngestionUnauthorized() throws Exception { @Test public void testCanManageSecretsAuthorized() throws Exception { QueryContext mockContext = Mockito.mock(QueryContext.class); - Authorizer mockAuthorizer = Mockito.mock(Authorizer.class); - - AuthorizationRequest request = - new AuthorizationRequest( - "urn:li:corpuser:authorized", - "MANAGE_SECRETS", - Optional.of(new EntitySpec(Constants.SECRETS_ENTITY_NAME, ""))); + when(mockContext.getOperationContext()).thenReturn(mock(OperationContext.class)); AuthorizationResult result = Mockito.mock(AuthorizationResult.class); Mockito.when(result.getType()).thenReturn(AuthorizationResult.Type.ALLOW); - Mockito.when(mockAuthorizer.authorize(Mockito.eq(request))).thenReturn(result); + Mockito.when( + mockContext + .getOperationContext() + .authorize( + eq("MANAGE_SECRETS"), eq(new EntitySpec(Constants.SECRETS_ENTITY_NAME, "")))) + .thenReturn(result); - Mockito.when(mockContext.getAuthorizer()).thenReturn(mockAuthorizer); Mockito.when(mockContext.getActorUrn()).thenReturn("urn:li:corpuser:authorized"); assertTrue(IngestionAuthUtils.canManageSecrets(mockContext)); @@ -80,7 +79,7 @@ public void testCanManageSecretsAuthorized() throws Exception { @Test public void testCanManageSecretsUnauthorized() throws Exception { QueryContext mockContext = Mockito.mock(QueryContext.class); - Authorizer mockAuthorizer = Mockito.mock(Authorizer.class); + when(mockContext.getOperationContext()).thenReturn(mock(OperationContext.class)); AuthorizationRequest request = new AuthorizationRequest( @@ -90,9 +89,13 @@ public void testCanManageSecretsUnauthorized() throws Exception { AuthorizationResult result = Mockito.mock(AuthorizationResult.class); Mockito.when(result.getType()).thenReturn(AuthorizationResult.Type.DENY); - Mockito.when(mockAuthorizer.authorize(Mockito.eq(request))).thenReturn(result); + Mockito.when( + mockContext + .getOperationContext() + .authorize( + eq("MANAGE_SECRETS"), eq(new EntitySpec(Constants.SECRETS_ENTITY_NAME, "")))) + .thenReturn(result); - Mockito.when(mockContext.getAuthorizer()).thenReturn(mockAuthorizer); Mockito.when(mockContext.getActorUrn()).thenReturn("urn:li:corpuser:unauthorized"); assertFalse(IngestionAuthUtils.canManageSecrets(mockContext)); diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/ingest/source/ListIngestionSourceResolverTest.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/ingest/source/ListIngestionSourceResolverTest.java index cd3b5c9dce47e8..05428788dc3c92 100644 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/ingest/source/ListIngestionSourceResolverTest.java +++ b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/ingest/source/ListIngestionSourceResolverTest.java @@ -2,7 +2,6 @@ import static com.linkedin.datahub.graphql.resolvers.ingest.IngestTestUtils.*; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; import static org.testng.Assert.*; import com.google.common.collect.ImmutableMap; @@ -22,7 +21,6 @@ import com.linkedin.metadata.search.SearchResult; import com.linkedin.r2.RemoteInvocationException; import graphql.schema.DataFetchingEnvironment; -import io.datahubproject.metadata.context.OperationContext; import java.util.HashSet; import org.mockito.Mockito; import org.testng.annotations.Test; @@ -85,7 +83,7 @@ public void testGetSuccess() throws Exception { // Execute resolver QueryContext mockContext = getMockAllowContext(); - Mockito.when(mockContext.getOperationContext()).thenReturn(mock(OperationContext.class)); + DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class); Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(TEST_INPUT); Mockito.when(mockEnv.getContext()).thenReturn(mockContext); diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/query/CreateQueryResolverTest.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/query/CreateQueryResolverTest.java index 034a8215c4a8ca..5617321c98e84f 100644 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/query/CreateQueryResolverTest.java +++ b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/query/CreateQueryResolverTest.java @@ -2,15 +2,15 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.testng.Assert.*; import com.datahub.authentication.Actor; import com.datahub.authentication.ActorType; import com.datahub.authentication.Authentication; -import com.datahub.authorization.AuthorizationRequest; import com.datahub.authorization.AuthorizationResult; import com.datahub.authorization.EntitySpec; -import com.datahub.plugins.auth.authorization.Authorizer; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.linkedin.common.AuditStamp; @@ -38,8 +38,10 @@ import com.linkedin.query.QuerySubject; import com.linkedin.query.QuerySubjectArray; import com.linkedin.query.QuerySubjects; +import com.linkedin.util.Pair; import graphql.schema.DataFetchingEnvironment; -import java.util.Optional; +import io.datahubproject.metadata.context.OperationContext; +import java.util.Map; import java.util.concurrent.CompletionException; import org.mockito.Mockito; import org.testng.annotations.Test; @@ -214,22 +216,7 @@ private QueryService initMockService() { private QueryContext getMockQueryContext(boolean allowEditEntityQueries) { QueryContext mockContext = Mockito.mock(QueryContext.class); Mockito.when(mockContext.getActorUrn()).thenReturn(TEST_ACTOR_URN.toString()); - - Authorizer mockAuthorizer = Mockito.mock(Authorizer.class); - - AuthorizationRequest editQueriesRequest = - new AuthorizationRequest( - TEST_ACTOR_URN.toString(), - PoliciesConfig.EDIT_QUERIES_PRIVILEGE.getType(), - Optional.of( - new EntitySpec(TEST_DATASET_URN.getEntityType(), TEST_DATASET_URN.toString()))); - - AuthorizationRequest editAllRequest = - new AuthorizationRequest( - TEST_ACTOR_URN.toString(), - PoliciesConfig.EDIT_ENTITY_PRIVILEGE.getType(), - Optional.of( - new EntitySpec(TEST_DATASET_URN.getEntityType(), TEST_DATASET_URN.toString()))); + when(mockContext.getOperationContext()).thenReturn(mock(OperationContext.class)); AuthorizationResult editQueriesResult = Mockito.mock(AuthorizationResult.class); Mockito.when(editQueriesResult.getType()) @@ -237,8 +224,6 @@ private QueryContext getMockQueryContext(boolean allowEditEntityQueries) { allowEditEntityQueries ? AuthorizationResult.Type.ALLOW : AuthorizationResult.Type.DENY); - Mockito.when(mockAuthorizer.authorize(Mockito.eq(editQueriesRequest))) - .thenReturn(editQueriesResult); AuthorizationResult editAllResult = Mockito.mock(AuthorizationResult.class); Mockito.when(editAllResult.getType()) @@ -246,9 +231,25 @@ private QueryContext getMockQueryContext(boolean allowEditEntityQueries) { allowEditEntityQueries ? AuthorizationResult.Type.ALLOW : AuthorizationResult.Type.DENY); - Mockito.when(mockAuthorizer.authorize(Mockito.eq(editAllRequest))).thenReturn(editAllResult); - Mockito.when(mockContext.getAuthorizer()).thenReturn(mockAuthorizer); + Map, AuthorizationResult> responses = + Map.of( + Pair.of( + PoliciesConfig.EDIT_QUERIES_PRIVILEGE.getType(), + new EntitySpec(TEST_DATASET_URN.getEntityType(), TEST_DATASET_URN.toString())), + editQueriesResult, + Pair.of( + PoliciesConfig.EDIT_ENTITY_PRIVILEGE.getType(), + new EntitySpec(TEST_DATASET_URN.getEntityType(), TEST_DATASET_URN.toString())), + editAllResult); + + when(mockContext.getOperationContext().authorize(any(), any())) + .thenAnswer( + args -> + responses.getOrDefault( + Pair.of(args.getArgument(0), args.getArgument(1)), + new AuthorizationResult(null, AuthorizationResult.Type.DENY, ""))); + Mockito.when(mockContext.getAuthentication()) .thenReturn(new Authentication(new Actor(ActorType.USER, TEST_ACTOR_URN.getId()), "creds")); return mockContext; diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/query/DeleteQueryResolverTest.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/query/DeleteQueryResolverTest.java index 491f06e800d709..2045e0ee52d683 100644 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/query/DeleteQueryResolverTest.java +++ b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/query/DeleteQueryResolverTest.java @@ -2,15 +2,15 @@ import static com.linkedin.datahub.graphql.TestUtils.*; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.testng.Assert.*; import com.datahub.authentication.Actor; import com.datahub.authentication.ActorType; import com.datahub.authentication.Authentication; -import com.datahub.authorization.AuthorizationRequest; import com.datahub.authorization.AuthorizationResult; import com.datahub.authorization.EntitySpec; -import com.datahub.plugins.auth.authorization.Authorizer; import com.google.common.collect.ImmutableList; import com.linkedin.common.urn.Urn; import com.linkedin.common.urn.UrnUtils; @@ -20,8 +20,10 @@ import com.linkedin.query.QuerySubject; import com.linkedin.query.QuerySubjectArray; import com.linkedin.query.QuerySubjects; +import com.linkedin.util.Pair; import graphql.schema.DataFetchingEnvironment; -import java.util.Optional; +import io.datahubproject.metadata.context.OperationContext; +import java.util.Map; import java.util.concurrent.CompletionException; import org.mockito.Mockito; import org.testng.annotations.Test; @@ -115,24 +117,7 @@ private QueryContext getMockQueryContext(boolean allowEditEntityQueries) { QueryContext mockContext = Mockito.mock(QueryContext.class); Mockito.when(mockContext.getActorUrn()) .thenReturn(DeleteQueryResolverTest.TEST_ACTOR_URN.toString()); - - Authorizer mockAuthorizer = Mockito.mock(Authorizer.class); - - AuthorizationRequest editQueriesRequest = - new AuthorizationRequest( - DeleteQueryResolverTest.TEST_ACTOR_URN.toString(), - PoliciesConfig.EDIT_QUERIES_PRIVILEGE.getType(), - Optional.of( - new EntitySpec( - DeleteQueryResolverTest.TEST_DATASET_URN.getEntityType(), - DeleteQueryResolverTest.TEST_DATASET_URN.toString()))); - - AuthorizationRequest editAllRequest = - new AuthorizationRequest( - TEST_ACTOR_URN.toString(), - PoliciesConfig.EDIT_ENTITY_PRIVILEGE.getType(), - Optional.of( - new EntitySpec(TEST_DATASET_URN.getEntityType(), TEST_DATASET_URN.toString()))); + when(mockContext.getOperationContext()).thenReturn(mock(OperationContext.class)); AuthorizationResult editQueriesResult = Mockito.mock(AuthorizationResult.class); Mockito.when(editQueriesResult.getType()) @@ -140,8 +125,6 @@ private QueryContext getMockQueryContext(boolean allowEditEntityQueries) { allowEditEntityQueries ? AuthorizationResult.Type.ALLOW : AuthorizationResult.Type.DENY); - Mockito.when(mockAuthorizer.authorize(Mockito.eq(editQueriesRequest))) - .thenReturn(editQueriesResult); AuthorizationResult editAllResult = Mockito.mock(AuthorizationResult.class); Mockito.when(editAllResult.getType()) @@ -149,9 +132,25 @@ private QueryContext getMockQueryContext(boolean allowEditEntityQueries) { allowEditEntityQueries ? AuthorizationResult.Type.ALLOW : AuthorizationResult.Type.DENY); - Mockito.when(mockAuthorizer.authorize(Mockito.eq(editAllRequest))).thenReturn(editAllResult); - Mockito.when(mockContext.getAuthorizer()).thenReturn(mockAuthorizer); + Map, AuthorizationResult> responses = + Map.of( + Pair.of( + PoliciesConfig.EDIT_QUERIES_PRIVILEGE.getType(), + new EntitySpec(TEST_DATASET_URN.getEntityType(), TEST_DATASET_URN.toString())), + editQueriesResult, + Pair.of( + PoliciesConfig.EDIT_ENTITY_PRIVILEGE.getType(), + new EntitySpec(TEST_DATASET_URN.getEntityType(), TEST_DATASET_URN.toString())), + editAllResult); + + when(mockContext.getOperationContext().authorize(any(), any())) + .thenAnswer( + args -> + responses.getOrDefault( + Pair.of(args.getArgument(0), args.getArgument(1)), + new AuthorizationResult(null, AuthorizationResult.Type.DENY, ""))); + Mockito.when(mockContext.getAuthentication()) .thenReturn(new Authentication(new Actor(ActorType.USER, TEST_ACTOR_URN.getId()), "creds")); return mockContext; diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/query/UpdateQueryResolverTest.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/query/UpdateQueryResolverTest.java index ce21ed99595660..8b81523b58d105 100644 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/query/UpdateQueryResolverTest.java +++ b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/query/UpdateQueryResolverTest.java @@ -2,15 +2,15 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.testng.Assert.*; import com.datahub.authentication.Actor; import com.datahub.authentication.ActorType; import com.datahub.authentication.Authentication; -import com.datahub.authorization.AuthorizationRequest; import com.datahub.authorization.AuthorizationResult; import com.datahub.authorization.EntitySpec; -import com.datahub.plugins.auth.authorization.Authorizer; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.linkedin.common.AuditStamp; @@ -38,8 +38,10 @@ import com.linkedin.query.QuerySubject; import com.linkedin.query.QuerySubjectArray; import com.linkedin.query.QuerySubjects; +import com.linkedin.util.Pair; import graphql.schema.DataFetchingEnvironment; -import java.util.Optional; +import io.datahubproject.metadata.context.OperationContext; +import java.util.Map; import java.util.concurrent.CompletionException; import org.mockito.Mockito; import org.testng.annotations.Test; @@ -212,36 +214,7 @@ private QueryService initMockService() { private QueryContext getMockQueryContext(boolean allowEditEntityQueries) { QueryContext mockContext = Mockito.mock(QueryContext.class); Mockito.when(mockContext.getActorUrn()).thenReturn(TEST_ACTOR_URN.toString()); - - Authorizer mockAuthorizer = Mockito.mock(Authorizer.class); - - AuthorizationRequest editQueriesRequest1 = - new AuthorizationRequest( - TEST_ACTOR_URN.toString(), - PoliciesConfig.EDIT_QUERIES_PRIVILEGE.getType(), - Optional.of( - new EntitySpec(TEST_DATASET_URN.getEntityType(), TEST_DATASET_URN.toString()))); - - AuthorizationRequest editAllRequest1 = - new AuthorizationRequest( - TEST_ACTOR_URN.toString(), - PoliciesConfig.EDIT_ENTITY_PRIVILEGE.getType(), - Optional.of( - new EntitySpec(TEST_DATASET_URN.getEntityType(), TEST_DATASET_URN.toString()))); - - AuthorizationRequest editQueriesRequest2 = - new AuthorizationRequest( - TEST_ACTOR_URN.toString(), - PoliciesConfig.EDIT_QUERIES_PRIVILEGE.getType(), - Optional.of( - new EntitySpec(TEST_DATASET_URN_2.getEntityType(), TEST_DATASET_URN_2.toString()))); - - AuthorizationRequest editAllRequest2 = - new AuthorizationRequest( - TEST_ACTOR_URN.toString(), - PoliciesConfig.EDIT_ENTITY_PRIVILEGE.getType(), - Optional.of( - new EntitySpec(TEST_DATASET_URN_2.getEntityType(), TEST_DATASET_URN_2.toString()))); + when(mockContext.getOperationContext()).thenReturn(mock(OperationContext.class)); AuthorizationResult editQueriesResult1 = Mockito.mock(AuthorizationResult.class); Mockito.when(editQueriesResult1.getType()) @@ -249,8 +222,6 @@ private QueryContext getMockQueryContext(boolean allowEditEntityQueries) { allowEditEntityQueries ? AuthorizationResult.Type.ALLOW : AuthorizationResult.Type.DENY); - Mockito.when(mockAuthorizer.authorize(Mockito.eq(editQueriesRequest1))) - .thenReturn(editQueriesResult1); AuthorizationResult editAllResult1 = Mockito.mock(AuthorizationResult.class); Mockito.when(editAllResult1.getType()) @@ -258,7 +229,6 @@ private QueryContext getMockQueryContext(boolean allowEditEntityQueries) { allowEditEntityQueries ? AuthorizationResult.Type.ALLOW : AuthorizationResult.Type.DENY); - Mockito.when(mockAuthorizer.authorize(Mockito.eq(editAllRequest1))).thenReturn(editAllResult1); AuthorizationResult editQueriesResult2 = Mockito.mock(AuthorizationResult.class); Mockito.when(editQueriesResult2.getType()) @@ -266,8 +236,6 @@ private QueryContext getMockQueryContext(boolean allowEditEntityQueries) { allowEditEntityQueries ? AuthorizationResult.Type.ALLOW : AuthorizationResult.Type.DENY); - Mockito.when(mockAuthorizer.authorize(Mockito.eq(editQueriesRequest2))) - .thenReturn(editQueriesResult2); AuthorizationResult editAllResult2 = Mockito.mock(AuthorizationResult.class); Mockito.when(editAllResult2.getType()) @@ -275,9 +243,35 @@ private QueryContext getMockQueryContext(boolean allowEditEntityQueries) { allowEditEntityQueries ? AuthorizationResult.Type.ALLOW : AuthorizationResult.Type.DENY); - Mockito.when(mockAuthorizer.authorize(Mockito.eq(editAllRequest2))).thenReturn(editAllResult2); - Mockito.when(mockContext.getAuthorizer()).thenReturn(mockAuthorizer); + Map, AuthorizationResult> responses = + Map.of( + Pair.of( + PoliciesConfig.EDIT_QUERIES_PRIVILEGE.getType(), + new EntitySpec(TEST_DATASET_URN.getEntityType(), TEST_DATASET_URN.toString())), + editQueriesResult1, + Pair.of( + PoliciesConfig.EDIT_ENTITY_PRIVILEGE.getType(), + new EntitySpec(TEST_DATASET_URN.getEntityType(), TEST_DATASET_URN.toString())), + editAllResult1, + Pair.of( + PoliciesConfig.EDIT_QUERIES_PRIVILEGE.getType(), + new EntitySpec( + TEST_DATASET_URN_2.getEntityType(), TEST_DATASET_URN_2.toString())), + editQueriesResult2, + Pair.of( + PoliciesConfig.EDIT_ENTITY_PRIVILEGE.getType(), + new EntitySpec( + TEST_DATASET_URN_2.getEntityType(), TEST_DATASET_URN_2.toString())), + editAllResult2); + + when(mockContext.getOperationContext().authorize(any(), any())) + .thenAnswer( + args -> + responses.getOrDefault( + Pair.of(args.getArgument(0), args.getArgument(1)), + new AuthorizationResult(null, AuthorizationResult.Type.DENY, ""))); + Mockito.when(mockContext.getAuthentication()) .thenReturn(new Authentication(new Actor(ActorType.USER, TEST_ACTOR_URN.getId()), "creds")); return mockContext; diff --git a/metadata-auth/auth-api/build.gradle b/metadata-auth/auth-api/build.gradle index 7303b79b0c5f0a..acc1af7e2e3ad6 100644 --- a/metadata-auth/auth-api/build.gradle +++ b/metadata-auth/auth-api/build.gradle @@ -31,6 +31,7 @@ dependencies() { testImplementation externalDependency.testng testImplementation externalDependency.mockito + testImplementation project(path: ':metadata-operation-context') testImplementation 'uk.org.webcompere:system-stubs-testng:2.1.6' } diff --git a/metadata-auth/auth-api/src/main/java/com/datahub/authorization/AuthUtil.java b/metadata-auth/auth-api/src/main/java/com/datahub/authorization/AuthUtil.java index d5bf22cab60406..62d8206e42565c 100644 --- a/metadata-auth/auth-api/src/main/java/com/datahub/authorization/AuthUtil.java +++ b/metadata-auth/auth-api/src/main/java/com/datahub/authorization/AuthUtil.java @@ -25,8 +25,6 @@ import static com.linkedin.metadata.authorization.PoliciesConfig.API_ENTITY_PRIVILEGE_MAP; import static com.linkedin.metadata.authorization.PoliciesConfig.API_PRIVILEGE_MAP; -import com.datahub.authentication.Authentication; -import com.datahub.plugins.auth.authorization.Authorizer; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSet; import com.linkedin.common.urn.Urn; @@ -50,7 +48,6 @@ import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nonnull; @@ -95,8 +92,7 @@ public class AuthUtil { /** OpenAPI/Rest.li Methods */ public static List> isAPIAuthorized( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, + @Nonnull final AuthorizationSession session, @Nonnull final ApiGroup apiGroup, @Nonnull final EntityRegistry entityRegistry, @Nonnull final Collection mcps) { @@ -117,8 +113,7 @@ public static List> isAPIAuthorized( Map, Integer> authorizationResult = isAPIAuthorizedUrns( - authentication, - authorizer, + session, apiGroup, changeUrnMCPs.stream().map(Pair::getFirst).collect(Collectors.toSet())); @@ -133,8 +128,7 @@ public static List> isAPIAuthorized( } public static Map, Integer> isAPIAuthorizedUrns( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, + @Nonnull final AuthorizationSession session, @Nonnull final ApiGroup apiGroup, @Nonnull final Collection> changeTypeUrns) { @@ -150,8 +144,7 @@ public static Map, Integer> isAPIAuthorizedUrns( case RESTATE: case PATCH: if (!isAPIAuthorized( - authentication, - authorizer, + session, lookupAPIPrivilege(apiGroup, UPDATE, urn.getEntityType()), new EntitySpec(urn.getEntityType(), urn.toString()))) { return Pair.of(changeTypePair, HttpStatus.SC_FORBIDDEN); @@ -159,8 +152,7 @@ public static Map, Integer> isAPIAuthorizedUrns( break; case CREATE_ENTITY: if (!isAPIAuthorized( - authentication, - authorizer, + session, lookupAPIPrivilege(apiGroup, CREATE, urn.getEntityType()), new EntitySpec(urn.getEntityType(), urn.toString()))) { return Pair.of(changeTypePair, HttpStatus.SC_FORBIDDEN); @@ -168,8 +160,7 @@ public static Map, Integer> isAPIAuthorizedUrns( break; case DELETE: if (!isAPIAuthorized( - authentication, - authorizer, + session, lookupAPIPrivilege(apiGroup, DELETE, urn.getEntityType()), new EntitySpec(urn.getEntityType(), urn.toString()))) { return Pair.of(changeTypePair, HttpStatus.SC_FORBIDDEN); @@ -184,58 +175,45 @@ public static Map, Integer> isAPIAuthorizedUrns( } public static boolean isAPIAuthorizedResult( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, - @Nonnull final SearchResult result) { + @Nonnull final AuthorizationSession session, @Nonnull final SearchResult result) { return isAPIAuthorizedEntityUrns( - authentication, - authorizer, + session, READ, result.getEntities().stream().map(SearchEntity::getEntity).collect(Collectors.toList())); } public static boolean isAPIAuthorizedResult( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, - @Nonnull final ScrollResult result) { + @Nonnull final AuthorizationSession session, @Nonnull final ScrollResult result) { return isAPIAuthorizedEntityUrns( - authentication, - authorizer, + session, READ, result.getEntities().stream().map(SearchEntity::getEntity).collect(Collectors.toList())); } public static boolean isAPIAuthorizedResult( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, - @Nonnull final AutoCompleteResult result) { + @Nonnull final AuthorizationSession session, @Nonnull final AutoCompleteResult result) { return isAPIAuthorizedEntityUrns( - authentication, - authorizer, + session, READ, result.getEntities().stream().map(AutoCompleteEntity::getUrn).collect(Collectors.toList())); } public static boolean isAPIAuthorizedResult( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, - @Nonnull final BrowseResult result) { + @Nonnull final AuthorizationSession session, @Nonnull final BrowseResult result) { return isAPIAuthorizedEntityUrns( - authentication, - authorizer, + session, READ, result.getEntities().stream().map(BrowseResultEntity::getUrn).collect(Collectors.toList())); } public static boolean isAPIAuthorizedUrns( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, + @Nonnull final AuthorizationSession session, @Nonnull final ApiGroup apiGroup, @Nonnull final ApiOperation apiOperation, @Nonnull final Collection urns) { if (ApiGroup.ENTITY.equals(apiGroup)) { - return isAPIAuthorizedEntityUrns(authentication, authorizer, apiOperation, urns); + return isAPIAuthorizedEntityUrns(session, apiOperation, urns); } List resourceSpecs = @@ -244,15 +222,11 @@ public static boolean isAPIAuthorizedUrns( .collect(Collectors.toList()); return isAPIAuthorized( - authentication, - authorizer, - lookupAPIPrivilege(apiGroup, apiOperation, null), - resourceSpecs); + session, lookupAPIPrivilege(apiGroup, apiOperation, null), resourceSpecs); } public static boolean isAPIAuthorizedEntityUrns( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, + @Nonnull final AuthorizationSession session, @Nonnull final ApiOperation apiOperation, @Nonnull final Collection urns) { @@ -265,43 +239,36 @@ public static boolean isAPIAuthorizedEntityUrns( .allMatch( entry -> isAPIAuthorized( - authentication, - authorizer, + session, lookupAPIPrivilege(ENTITY, apiOperation, entry.getKey()), entry.getValue())); } public static boolean isAPIAuthorizedEntityType( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, + @Nonnull final AuthorizationSession session, @Nonnull final ApiOperation apiOperation, @Nonnull final String entityType) { - return isAPIAuthorizedEntityType( - authentication, authorizer, ENTITY, apiOperation, List.of(entityType)); + return isAPIAuthorizedEntityType(session, ENTITY, apiOperation, List.of(entityType)); } public static boolean isAPIAuthorizedEntityType( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, + @Nonnull final AuthorizationSession session, @Nonnull final ApiGroup apiGroup, @Nonnull final ApiOperation apiOperation, @Nonnull final String entityType) { - return isAPIAuthorizedEntityType( - authentication, authorizer, apiGroup, apiOperation, List.of(entityType)); + return isAPIAuthorizedEntityType(session, apiGroup, apiOperation, List.of(entityType)); } public static boolean isAPIAuthorizedEntityType( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, + @Nonnull final AuthorizationSession session, @Nonnull final ApiOperation apiOperation, @Nonnull final Collection entityTypes) { - return isAPIAuthorizedEntityType(authentication, authorizer, ENTITY, apiOperation, entityTypes); + return isAPIAuthorizedEntityType(session, ENTITY, apiOperation, entityTypes); } public static boolean isAPIAuthorizedEntityType( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, + @Nonnull final AuthorizationSession session, @Nonnull final ApiGroup apiGroup, @Nonnull final ApiOperation apiOperation, @Nonnull final Collection entityTypes) { @@ -311,60 +278,45 @@ public static boolean isAPIAuthorizedEntityType( .allMatch( entityType -> isAPIAuthorized( - authentication, - authorizer, + session, lookupAPIPrivilege(apiGroup, apiOperation, entityType), new EntitySpec(entityType, ""))); } public static boolean isAPIAuthorized( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, + @Nonnull final AuthorizationSession session, @Nonnull final ApiGroup apiGroup, @Nonnull final ApiOperation apiOperation) { return isAPIAuthorized( - authentication, - authorizer, - lookupAPIPrivilege(apiGroup, apiOperation, null), - (EntitySpec) null); + session, lookupAPIPrivilege(apiGroup, apiOperation, null), (EntitySpec) null); } public static boolean isAPIAuthorized( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, + @Nonnull final AuthorizationSession session, @Nonnull final PoliciesConfig.Privilege privilege, @Nullable final EntitySpec resource) { - return isAPIAuthorized(authentication, authorizer, Disjunctive.disjoint(privilege), resource); + return isAPIAuthorized(session, Disjunctive.disjoint(privilege), resource); } public static boolean isAPIAuthorized( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, + @Nonnull final AuthorizationSession session, @Nonnull final PoliciesConfig.Privilege privilege) { - return isAPIAuthorized( - authentication, authorizer, Disjunctive.disjoint(privilege), (EntitySpec) null); + return isAPIAuthorized(session, Disjunctive.disjoint(privilege), (EntitySpec) null); } private static boolean isAPIAuthorized( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, + @Nonnull final AuthorizationSession session, @Nonnull final Disjunctive> privileges, @Nullable final EntitySpec resource) { - return isAPIAuthorized( - authentication, authorizer, privileges, resource != null ? List.of(resource) : List.of()); + return isAPIAuthorized(session, privileges, resource != null ? List.of(resource) : List.of()); } private static boolean isAPIAuthorized( - @Nonnull final Authentication authentication, - @Nonnull final Authorizer authorizer, + @Nonnull final AuthorizationSession session, @Nonnull final Disjunctive> privileges, @Nonnull final Collection resources) { if (Boolean.parseBoolean(System.getenv(REST_API_AUTHORIZATION_ENABLED_ENV))) { - return isAuthorized( - authorizer, - authentication.getActor().toUrnStr(), - buildDisjunctivePrivilegeGroup(privileges), - resources); + return isAuthorized(session, buildDisjunctivePrivilegeGroup(privileges), resources); } else { return true; } @@ -372,29 +324,25 @@ private static boolean isAPIAuthorized( /** GraphQL Methods */ public static boolean canViewEntity( - @Nonnull final String actor, @Nonnull Authorizer authorizer, @Nonnull Urn urn) { - return canViewEntity(actor, authorizer, List.of(urn)); + @Nonnull final AuthorizationSession session, @Nonnull Urn urn) { + return canViewEntity(session, List.of(urn)); } public static boolean canViewEntity( - @Nonnull final String actor, - @Nonnull final Authorizer authorizer, - @Nonnull final Collection urns) { + @Nonnull final AuthorizationSession session, @Nonnull final Collection urns) { - return isAuthorizedEntityUrns(authorizer, actor, READ, urns); + return isAuthorizedEntityUrns(session, READ, urns); } public static boolean isAuthorized( - @Nonnull final String actor, - @Nonnull final Authorizer authorizer, + @Nonnull final AuthorizationSession session, @Nonnull final ApiGroup apiGroup, @Nonnull final ApiOperation apiOperation) { - return isAuthorized(authorizer, actor, lookupAPIPrivilege(apiGroup, apiOperation, null), null); + return isAuthorized(session, lookupAPIPrivilege(apiGroup, apiOperation, null), null); } public static boolean isAuthorizedEntityType( - @Nonnull final String actor, - @Nonnull final Authorizer authorizer, + @Nonnull final AuthorizationSession session, @Nonnull final ApiOperation apiOperation, @Nonnull final Collection entityTypes) { @@ -403,23 +351,20 @@ public static boolean isAuthorizedEntityType( .allMatch( entityType -> isAuthorized( - authorizer, - actor, + session, lookupEntityAPIPrivilege(apiOperation, entityType), new EntitySpec(entityType, ""))); } public static boolean isAuthorizedEntityUrns( - @Nonnull final Authorizer authorizer, - @Nonnull final String actor, + @Nonnull final AuthorizationSession session, @Nonnull final ApiOperation apiOperation, @Nonnull final Collection urns) { - return isAuthorizedUrns(authorizer, actor, ENTITY, apiOperation, urns); + return isAuthorizedUrns(session, ENTITY, apiOperation, urns); } public static boolean isAuthorizedUrns( - @Nonnull final Authorizer authorizer, - @Nonnull final String actor, + @Nonnull final AuthorizationSession session, @Nonnull final ApiGroup apiGroup, @Nonnull final ApiOperation apiOperation, @Nonnull final Collection urns) { @@ -435,50 +380,41 @@ public static boolean isAuthorizedUrns( Disjunctive> privileges = lookupAPIPrivilege(apiGroup, apiOperation, entry.getKey()); return entry.getValue().stream() - .allMatch(entitySpec -> isAuthorized(authorizer, actor, privileges, entitySpec)); + .allMatch(entitySpec -> isAuthorized(session, privileges, entitySpec)); }); } public static boolean isAuthorized( - @Nonnull final Authorizer authorizer, - @Nonnull final String actor, + @Nonnull final AuthorizationSession session, @Nonnull final PoliciesConfig.Privilege privilege) { return isAuthorized( - authorizer, - actor, + session, buildDisjunctivePrivilegeGroup(Disjunctive.disjoint(privilege)), (EntitySpec) null); } public static boolean isAuthorized( - @Nonnull final Authorizer authorizer, - @Nonnull final String actor, + @Nonnull final AuthorizationSession session, @Nonnull final PoliciesConfig.Privilege privilege, @Nullable final EntitySpec entitySpec) { return isAuthorized( - authorizer, - actor, - buildDisjunctivePrivilegeGroup(Disjunctive.disjoint(privilege)), - entitySpec); + session, buildDisjunctivePrivilegeGroup(Disjunctive.disjoint(privilege)), entitySpec); } private static boolean isAuthorized( - @Nonnull final Authorizer authorizer, - @Nonnull final String actor, + @Nonnull final AuthorizationSession session, @Nonnull final Disjunctive> privileges, @Nullable EntitySpec maybeResourceSpec) { - return isAuthorized( - authorizer, actor, buildDisjunctivePrivilegeGroup(privileges), maybeResourceSpec); + return isAuthorized(session, buildDisjunctivePrivilegeGroup(privileges), maybeResourceSpec); } public static boolean isAuthorized( - @Nonnull final Authorizer authorizer, - @Nonnull final String actor, + @Nonnull final AuthorizationSession session, @Nonnull final DisjunctivePrivilegeGroup privilegeGroup, @Nullable final EntitySpec resourceSpec) { for (ConjunctivePrivilegeGroup conjunctive : privilegeGroup.getAuthorizedPrivilegeGroups()) { - if (isAuthorized(authorizer, actor, conjunctive, resourceSpec)) { + if (isAuthorized(session, conjunctive, resourceSpec)) { return true; } } @@ -487,8 +423,7 @@ public static boolean isAuthorized( } private static boolean isAuthorized( - @Nonnull final Authorizer authorizer, - @Nonnull final String actor, + @Nonnull final AuthorizationSession session, @Nonnull final ConjunctivePrivilegeGroup requiredPrivileges, @Nullable final EntitySpec resourceSpec) { @@ -500,7 +435,7 @@ private static boolean isAuthorized( // Each privilege in a group _must_ all be true to permit the operation. for (final String privilege : requiredPrivileges.getRequiredPrivileges()) { // Create and evaluate an Authorization request. - if (isDenied(authorizer, actor, privilege, resourceSpec)) { + if (isDenied(session, privilege, resourceSpec)) { // Short circuit. return false; } @@ -509,17 +444,15 @@ private static boolean isAuthorized( } private static boolean isAuthorized( - @Nonnull final Authorizer authorizer, - @Nonnull final String actor, + @Nonnull final AuthorizationSession session, @Nonnull final DisjunctivePrivilegeGroup privilegeGroup, @Nonnull final Collection resourceSpecs) { if (resourceSpecs.isEmpty()) { - return isAuthorized(authorizer, actor, privilegeGroup, (EntitySpec) null); + return isAuthorized(session, privilegeGroup, (EntitySpec) null); } - return resourceSpecs.stream() - .allMatch(spec -> isAuthorized(authorizer, actor, privilegeGroup, spec)); + return resourceSpecs.stream().allMatch(spec -> isAuthorized(session, privilegeGroup, spec)); } /** Common Methods */ @@ -618,14 +551,11 @@ static DisjunctivePrivilegeGroup buildDisjunctivePrivilegeGroup( } private static boolean isDenied( - @Nonnull final Authorizer authorizer, - @Nonnull final String actor, + @Nonnull final AuthorizationSession session, @Nonnull final String privilege, @Nullable final EntitySpec resourceSpec) { // Create and evaluate an Authorization request. - final AuthorizationRequest request = - new AuthorizationRequest(actor, privilege, Optional.ofNullable(resourceSpec)); - final AuthorizationResult result = authorizer.authorize(request); + final AuthorizationResult result = session.authorize(privilege, resourceSpec); return AuthorizationResult.Type.DENY.equals(result.getType()); } diff --git a/metadata-auth/auth-api/src/main/java/com/datahub/authorization/AuthorizationSession.java b/metadata-auth/auth-api/src/main/java/com/datahub/authorization/AuthorizationSession.java new file mode 100644 index 00000000000000..0ca972873e2f6c --- /dev/null +++ b/metadata-auth/auth-api/src/main/java/com/datahub/authorization/AuthorizationSession.java @@ -0,0 +1,10 @@ +package com.datahub.authorization; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** Combines a common interface for actor and authorizer which is cached per session */ +public interface AuthorizationSession { + AuthorizationResult authorize( + @Nonnull final String privilege, @Nullable final EntitySpec resourceSpec); +} diff --git a/metadata-auth/auth-api/src/test/java/com/datahub/authorization/AuthUtilTest.java b/metadata-auth/auth-api/src/test/java/com/datahub/authorization/AuthUtilTest.java index 199b0faa933aeb..6619bcd0f47dcb 100644 --- a/metadata-auth/auth-api/src/test/java/com/datahub/authorization/AuthUtilTest.java +++ b/metadata-auth/auth-api/src/test/java/com/datahub/authorization/AuthUtilTest.java @@ -25,6 +25,7 @@ import com.linkedin.metadata.authorization.ApiOperation; import com.linkedin.metadata.authorization.Conjunctive; import com.linkedin.util.Pair; +import io.datahubproject.test.metadata.context.TestAuthSession; import java.util.List; import java.util.Map; import java.util.Set; @@ -97,16 +98,14 @@ public void testIsAPIAuthorizedUrns() { // User A (Entity 1 & 2 Edit, View only Entity 3) assertTrue( AuthUtil.isAPIAuthorizedEntityUrns( - TEST_AUTH_A, - mockAuthorizer, + TestAuthSession.from(TEST_AUTH_A, mockAuthorizer), READ, List.of(TEST_ENTITY_1, TEST_ENTITY_2, TEST_ENTITY_3)), "Expected read allowed for all entities"); assertEquals( AuthUtil.isAPIAuthorizedUrns( - TEST_AUTH_A, - mockAuthorizer, + TestAuthSession.from(TEST_AUTH_A, mockAuthorizer), ENTITY, List.of( Pair.of(ChangeType.UPSERT, TEST_ENTITY_1), @@ -120,8 +119,7 @@ public void testIsAPIAuthorizedUrns() { assertEquals( AuthUtil.isAPIAuthorizedUrns( - TEST_AUTH_A, - mockAuthorizer, + TestAuthSession.from(TEST_AUTH_A, mockAuthorizer), ENTITY, List.of( Pair.of(ChangeType.DELETE, TEST_ENTITY_1), @@ -136,20 +134,20 @@ public void testIsAPIAuthorizedUrns() { // User B Entity 2 Denied, Read access 1 & 3 assertFalse( AuthUtil.isAPIAuthorizedEntityUrns( - TEST_AUTH_B, - mockAuthorizer, + TestAuthSession.from(TEST_AUTH_B, mockAuthorizer), READ, List.of(TEST_ENTITY_1, TEST_ENTITY_2, TEST_ENTITY_3)), "Expected read denied for based on entity 2"); assertTrue( AuthUtil.isAPIAuthorizedEntityUrns( - TEST_AUTH_B, mockAuthorizer, READ, List.of(TEST_ENTITY_1, TEST_ENTITY_3)), + TestAuthSession.from(TEST_AUTH_B, mockAuthorizer), + READ, + List.of(TEST_ENTITY_1, TEST_ENTITY_3)), "Expected read allowed due to exclusion of entity 2"); assertEquals( AuthUtil.isAPIAuthorizedUrns( - TEST_AUTH_B, - mockAuthorizer, + TestAuthSession.from(TEST_AUTH_B, mockAuthorizer), ENTITY, List.of( Pair.of(ChangeType.UPSERT, TEST_ENTITY_1), @@ -163,8 +161,7 @@ public void testIsAPIAuthorizedUrns() { assertEquals( AuthUtil.isAPIAuthorizedUrns( - TEST_AUTH_B, - mockAuthorizer, + TestAuthSession.from(TEST_AUTH_B, mockAuthorizer), ENTITY, List.of( Pair.of(ChangeType.DELETE, TEST_ENTITY_1), diff --git a/metadata-io/src/main/java/com/linkedin/metadata/search/LineageSearchService.java b/metadata-io/src/main/java/com/linkedin/metadata/search/LineageSearchService.java index 84fcc2c0a0f911..bae1d6ce92ece7 100644 --- a/metadata-io/src/main/java/com/linkedin/metadata/search/LineageSearchService.java +++ b/metadata-io/src/main/java/com/linkedin/metadata/search/LineageSearchService.java @@ -711,10 +711,7 @@ private LineageSearchEntity buildLineageSearchEntity( .getOperationContextConfig() .getViewAuthorizationConfiguration() .isEnabled()) { - return canViewEntity( - opContext.getSessionAuthentication().getActor().toUrnStr(), - opContext.getAuthorizerContext().getAuthorizer(), - urn); + return canViewEntity(opContext, urn); } return true; })) diff --git a/metadata-io/src/main/java/com/linkedin/metadata/search/utils/ESAccessControlUtil.java b/metadata-io/src/main/java/com/linkedin/metadata/search/utils/ESAccessControlUtil.java index 6f5dcee07a5aae..d1895de3055489 100644 --- a/metadata-io/src/main/java/com/linkedin/metadata/search/utils/ESAccessControlUtil.java +++ b/metadata-io/src/main/java/com/linkedin/metadata/search/utils/ESAccessControlUtil.java @@ -2,9 +2,7 @@ import static com.datahub.authorization.AuthUtil.VIEW_RESTRICTED_ENTITY_TYPES; -import com.datahub.authentication.Authentication; import com.datahub.authorization.AuthUtil; -import com.datahub.plugins.auth.authorization.Authorizer; import com.linkedin.common.urn.Urn; import com.linkedin.data.template.StringArray; import com.linkedin.metadata.models.registry.EntityRegistry; @@ -40,8 +38,6 @@ public static Collection restrictSearchResult( final EntityRegistry entityRegistry = Objects.requireNonNull(opContext.getEntityRegistry()); final RestrictedService restrictedService = Objects.requireNonNull(opContext.getServicesRegistryContext()).getRestrictedService(); - final Authentication auth = opContext.getSessionActorContext().getAuthentication(); - final Authorizer authorizer = opContext.getAuthorizerContext().getAuthorizer(); if (opContext.getSearchContext().isRestrictedSearch()) { for (SearchEntity searchEntity : searchEntities) { @@ -50,8 +46,7 @@ public static Collection restrictSearchResult( entityRegistry.getEntitySpec(entityType); if (VIEW_RESTRICTED_ENTITY_TYPES.contains(entityType) - && !AuthUtil.canViewEntity( - auth.getActor().toUrnStr(), authorizer, searchEntity.getEntity())) { + && !AuthUtil.canViewEntity(opContext, searchEntity.getEntity())) { // Not authorized && restricted response requested if (opContext.getSearchContext().isRestrictedSearch()) { @@ -72,9 +67,7 @@ public static Collection restrictSearchResult( public static boolean restrictUrn(@Nonnull OperationContext opContext, @Nonnull Urn urn) { if (opContext.getOperationContextConfig().getViewAuthorizationConfiguration().isEnabled() && !opContext.isSystemAuth()) { - final Authentication auth = opContext.getSessionActorContext().getAuthentication(); - final Authorizer authorizer = opContext.getAuthorizerContext().getAuthorizer(); - return !AuthUtil.canViewEntity(auth.getActor().toUrnStr(), authorizer, urn); + return !AuthUtil.canViewEntity(opContext, urn); } return false; } diff --git a/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/AuthorizationContext.java b/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/AuthorizationContext.java new file mode 100644 index 00000000000000..1390fd53a2b930 --- /dev/null +++ b/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/AuthorizationContext.java @@ -0,0 +1,62 @@ +package io.datahubproject.metadata.context; + +import com.datahub.authorization.AuthorizationRequest; +import com.datahub.authorization.AuthorizationResult; +import com.datahub.authorization.EntitySpec; +import com.datahub.plugins.auth.authorization.Authorizer; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class AuthorizationContext implements ContextInterface { + + public static final AuthorizationContext EMPTY = + AuthorizationContext.builder().authorizer(Authorizer.EMPTY).build(); + + @Nonnull private final Authorizer authorizer; + + @Builder.Default + private final ConcurrentHashMap + sessionAuthorizationCache = new ConcurrentHashMap<>(); + + /** + * Run authorization through the actor's session cache + * + * @param actorContext the actor context + * @param privilege privilege + * @param resourceSpec resource to access + * @return authorization result + */ + public AuthorizationResult authorize( + @Nonnull ActorContext actorContext, + @Nonnull final String privilege, + @Nullable final EntitySpec resourceSpec) { + final AuthorizationRequest request = + new AuthorizationRequest( + actorContext.getActorUrn().toString(), privilege, Optional.ofNullable(resourceSpec)); + // Graphql CompletableFutures causes a recursive exception, we avoid computeIfAbsent and do work + // outside a blocking function + AuthorizationResult result = sessionAuthorizationCache.get(request); + if (result == null) { + result = authorizer.authorize(request); + sessionAuthorizationCache.putIfAbsent(request, result); + } + return result; + } + + /** + * No need to consider the authorizer in the cache context since it is ultimately determined by + * the underlying search context and actor context + * + * @return + */ + @Override + public Optional getCacheKeyComponent() { + return Optional.empty(); + } +} diff --git a/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/AuthorizerContext.java b/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/AuthorizerContext.java deleted file mode 100644 index fdd84f6d64557d..00000000000000 --- a/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/AuthorizerContext.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.datahubproject.metadata.context; - -import com.datahub.plugins.auth.authorization.Authorizer; -import java.util.Optional; -import javax.annotation.Nonnull; -import lombok.Builder; -import lombok.Getter; - -@Builder -@Getter -public class AuthorizerContext implements ContextInterface { - - public static final AuthorizerContext EMPTY = - AuthorizerContext.builder().authorizer(Authorizer.EMPTY).build(); - - @Nonnull private final Authorizer authorizer; - - /** - * No need to consider the authorizer in the cache context since it is ultimately determined by - * the underlying search context - * - * @return - */ - @Override - public Optional getCacheKeyComponent() { - return Optional.empty(); - } -} diff --git a/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/OperationContext.java b/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/OperationContext.java index 9928318268a3ea..be5ac921fcb2d9 100644 --- a/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/OperationContext.java +++ b/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/OperationContext.java @@ -1,6 +1,9 @@ package io.datahubproject.metadata.context; import com.datahub.authentication.Authentication; +import com.datahub.authorization.AuthorizationResult; +import com.datahub.authorization.AuthorizationSession; +import com.datahub.authorization.EntitySpec; import com.datahub.plugins.auth.authorization.Authorizer; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableSet; @@ -36,7 +39,7 @@ */ @Builder(toBuilder = true) @Getter -public class OperationContext { +public class OperationContext implements AuthorizationSession { /** * This should be the primary entry point when a request is made to Rest.li, OpenAPI, Graphql or @@ -66,10 +69,8 @@ public static OperationContext asSession( systemOperationContext.getOperationContextConfig().toBuilder() .allowSystemAuthentication(allowSystemAuthentication) .build()) - .authorizerContext(AuthorizerContext.builder().authorizer(authorizer).build()) + .authorizationContext(AuthorizationContext.builder().authorizer(authorizer).build()) .requestContext(requestContext) - // Initialize view authorization for user viewable urn tracking - .viewAuthorizationContext(ViewAuthorizationContext.builder().build()) .build(sessionAuthentication); } @@ -157,7 +158,7 @@ public static OperationContext asSystem( .entityRegistryContext(EntityRegistryContext.builder().build(entityRegistry)) .servicesRegistryContext(servicesRegistryContext) // Authorizer.EMPTY doesn't actually apply to system auth - .authorizerContext(AuthorizerContext.builder().authorizer(Authorizer.EMPTY).build()) + .authorizationContext(AuthorizationContext.builder().authorizer(Authorizer.EMPTY).build()) .retrieverContext(retrieverContext) .objectMapperContext(objectMapperContext) .build(systemAuthentication); @@ -167,11 +168,10 @@ public static OperationContext asSystem( @Nonnull private final ActorContext sessionActorContext; @Nullable private final ActorContext systemActorContext; @Nonnull private final SearchContext searchContext; - @Nonnull private final AuthorizerContext authorizerContext; + @Nonnull private final AuthorizationContext authorizationContext; @Nonnull private final EntityRegistryContext entityRegistryContext; @Nullable private final ServicesRegistryContext servicesRegistryContext; @Nullable private final RequestContext requestContext; - @Nullable private final ViewAuthorizationContext viewAuthorizationContext; @Nullable private final RetrieverContext retrieverContext; @Nonnull private final ObjectMapperContext objectMapperContext; @@ -237,7 +237,7 @@ public ActorContext getActorContext() { * @return */ public Collection getActorPeers() { - return authorizerContext.getAuthorizer().getActorPeers(sessionActorContext.getActorUrn()); + return authorizationContext.getAuthorizer().getActorPeers(sessionActorContext.getActorUrn()); } /** @@ -278,10 +278,6 @@ public AuditStamp getAuditStamp() { return getAuditStamp(null); } - public Optional getViewAuthorizationContext() { - return Optional.ofNullable(viewAuthorizationContext); - } - public Optional getRetrieverContext() { return Optional.ofNullable(retrieverContext); } @@ -295,6 +291,19 @@ public Optional getAspectRetrieverOpt() { return getRetrieverContext().map(RetrieverContext::getAspectRetriever); } + /** + * Provides a cached authorizer interface in the context of the session user + * + * @param privilege the requested privilege + * @param resourceSpec the optional resource that is the target of the privilege + * @return authorization result + */ + @Override + public AuthorizationResult authorize( + @Nonnull String privilege, @Nullable EntitySpec resourceSpec) { + return authorizationContext.authorize(getSessionActorContext(), privilege, resourceSpec); + } + /** * Return a unique id for this context. Typically useful for building cache keys. We combine the * different context components to create a single string representation of the hashcode across @@ -309,7 +318,7 @@ public String getGlobalContextId() { return String.valueOf( ImmutableSet.builder() .add(getOperationContextConfig()) - .add(getAuthorizerContext()) + .add(getAuthorizationContext()) .add(getSessionActorContext()) .add(getSearchContext()) .add( @@ -321,10 +330,6 @@ public String getGlobalContextId() { ? EmptyContext.EMPTY : getServicesRegistryContext()) .add(getRequestContext() == null ? EmptyContext.EMPTY : getRequestContext()) - .add( - getViewAuthorizationContext().isPresent() - ? getViewAuthorizationContext().get() - : EmptyContext.EMPTY) .add( getRetrieverContext().isPresent() ? getRetrieverContext().get() @@ -411,8 +416,8 @@ public OperationContext build(@Nonnull Authentication sessionAuthentication) { .getAuthentication() .getActor() .equals(sessionAuthentication.getActor())) - .policyInfoSet(this.authorizerContext.getAuthorizer().getActorPolicies(actorUrn)) - .groupMembership(this.authorizerContext.getAuthorizer().getActorGroups(actorUrn)) + .policyInfoSet(this.authorizationContext.getAuthorizer().getActorPolicies(actorUrn)) + .groupMembership(this.authorizationContext.getAuthorizer().getActorGroups(actorUrn)) .build(); return build(sessionActor); } @@ -424,11 +429,10 @@ public OperationContext build(@Nonnull ActorContext sessionActor) { sessionActor, this.systemActorContext, Objects.requireNonNull(this.searchContext), - Objects.requireNonNull(this.authorizerContext), + Objects.requireNonNull(this.authorizationContext), this.entityRegistryContext, this.servicesRegistryContext, this.requestContext, - this.viewAuthorizationContext, this.retrieverContext, this.objectMapperContext != null ? this.objectMapperContext diff --git a/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/RequestContext.java b/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/RequestContext.java index 1eee0498f112a6..779c418a56142f 100644 --- a/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/RequestContext.java +++ b/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/RequestContext.java @@ -76,6 +76,11 @@ public RequestContext buildGraphql( return build(); } + public RequestContext buildRestli( + @Nonnull String actorUrn, @Nullable ResourceContext resourceContext, String action) { + return buildRestli(actorUrn, resourceContext, action, (String) null); + } + public RequestContext buildRestli( @Nonnull String actorUrn, @Nullable ResourceContext resourceContext, diff --git a/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/ViewAuthorizationContext.java b/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/ViewAuthorizationContext.java deleted file mode 100644 index 5204d7bf5f98f4..00000000000000 --- a/metadata-operation-context/src/main/java/io/datahubproject/metadata/context/ViewAuthorizationContext.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.datahubproject.metadata.context; - -import com.linkedin.common.urn.Urn; -import java.util.Collection; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import javax.annotation.Nonnull; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class ViewAuthorizationContext implements ContextInterface { - - /** - * Graphql has a lot of redundant `canView` authorization checks, to reduce the repeated checks - * for view authorization, maintain a list of urns that have already been identified as viewable - * for the request. - */ - @Nonnull @Builder.Default private Set viewableUrns = ConcurrentHashMap.newKeySet(); - - public boolean canView(@Nonnull Collection urns) { - if (urns.isEmpty()) { - return false; - } - return viewableUrns.containsAll(urns); - } - - public void addViewableUrns(@Nonnull Collection urns) { - viewableUrns.addAll(urns); - } - - @Override - public Optional getCacheKeyComponent() { - return Optional.empty(); - } -} diff --git a/metadata-operation-context/src/main/java/io/datahubproject/test/metadata/context/TestAuthSession.java b/metadata-operation-context/src/main/java/io/datahubproject/test/metadata/context/TestAuthSession.java new file mode 100644 index 00000000000000..d8be3e95efbc4b --- /dev/null +++ b/metadata-operation-context/src/main/java/io/datahubproject/test/metadata/context/TestAuthSession.java @@ -0,0 +1,44 @@ +package io.datahubproject.test.metadata.context; + +import com.datahub.authentication.Authentication; +import com.datahub.authorization.AuthorizationRequest; +import com.datahub.authorization.AuthorizationResult; +import com.datahub.authorization.AuthorizationSession; +import com.datahub.authorization.EntitySpec; +import com.datahub.plugins.auth.authorization.Authorizer; +import java.util.Optional; +import java.util.function.BiFunction; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TestAuthSession implements AuthorizationSession { + public static AuthorizationSession ALLOW_ALL = + from((priv, authorizer) -> new AuthorizationResult(null, AuthorizationResult.Type.ALLOW, "")); + + public static AuthorizationSession from(Authentication auth, Authorizer authorizer) { + return from( + (privilege, resourceSpec) -> { + final AuthorizationRequest request = + new AuthorizationRequest( + auth.getActor().toUrnStr(), privilege, Optional.ofNullable(resourceSpec)); + return authorizer.authorize(request); + }); + } + + public static AuthorizationSession from( + BiFunction authFunction) { + return new TestAuthSession(authFunction); + } + + private final BiFunction authFunction; + + public TestAuthSession(BiFunction authFunction) { + this.authFunction = authFunction; + } + + @Override + public AuthorizationResult authorize( + @Nonnull String privilege, @Nullable EntitySpec resourceSpec) { + return authFunction.apply(privilege, resourceSpec); + } +} diff --git a/metadata-service/configuration/src/main/resources/application.yaml b/metadata-service/configuration/src/main/resources/application.yaml index 12d0175bac854a..795fa10d33c8b6 100644 --- a/metadata-service/configuration/src/main/resources/application.yaml +++ b/metadata-service/configuration/src/main/resources/application.yaml @@ -485,6 +485,9 @@ cache: status: 20 corpUserCredentials: 20 corpUserSettings: 20 + roleMembership: 20 + groupMembership: 20 + nativeGroupMembership: 20 structuredProperty: status: 300 # 5 min propertyDefinition: 300 # 5 min diff --git a/metadata-service/openapi-analytics-servlet/src/main/java/io/datahubproject/openapi/delegates/DatahubUsageEventsImpl.java b/metadata-service/openapi-analytics-servlet/src/main/java/io/datahubproject/openapi/delegates/DatahubUsageEventsImpl.java index dc6d4a33f936ea..8a52c545dc80c2 100644 --- a/metadata-service/openapi-analytics-servlet/src/main/java/io/datahubproject/openapi/delegates/DatahubUsageEventsImpl.java +++ b/metadata-service/openapi-analytics-servlet/src/main/java/io/datahubproject/openapi/delegates/DatahubUsageEventsImpl.java @@ -15,6 +15,7 @@ import jakarta.servlet.http.HttpServletRequest; import java.util.List; import java.util.Objects; +import javax.annotation.Nonnull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.ResponseEntity; @@ -35,7 +36,6 @@ public class DatahubUsageEventsImpl implements DatahubUsageEventsApiDelegate { @Override public ResponseEntity raw(String body) { Authentication authentication = AuthenticationContext.getAuthentication(); - checkAnalyticsAuthorized(authentication); OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -44,15 +44,16 @@ public ResponseEntity raw(String body) { _authorizationChain, authentication, true); + checkAnalyticsAuthorized(opContext); return ResponseEntity.of( _searchService.raw(opContext, DATAHUB_USAGE_INDEX, body).map(Objects::toString)); } - private void checkAnalyticsAuthorized(Authentication authentication) { - if (!AuthUtil.isAPIAuthorized(authentication, _authorizationChain, ANALYTICS, READ)) { + private void checkAnalyticsAuthorized(@Nonnull OperationContext opContext) { + if (!AuthUtil.isAPIAuthorized(opContext, ANALYTICS, READ)) { throw new UnauthorizedException( - authentication.getActor().toUrnStr() + " is unauthorized to get analytics."); + opContext.getActorContext().getActorUrn() + " is unauthorized to get analytics."); } } } diff --git a/metadata-service/openapi-entity-servlet/src/main/java/io/datahubproject/openapi/v2/delegates/EntityApiDelegateImpl.java b/metadata-service/openapi-entity-servlet/src/main/java/io/datahubproject/openapi/v2/delegates/EntityApiDelegateImpl.java index c5f87c3f1dced4..84f783ea53e36e 100644 --- a/metadata-service/openapi-entity-servlet/src/main/java/io/datahubproject/openapi/v2/delegates/EntityApiDelegateImpl.java +++ b/metadata-service/openapi-entity-servlet/src/main/java/io/datahubproject/openapi/v2/delegates/EntityApiDelegateImpl.java @@ -178,11 +178,6 @@ public ResponseEntity head(String urn) { try { Urn entityUrn = Urn.createFromString(urn); final Authentication auth = AuthenticationContext.getAuthentication(); - if (!AuthUtil.isAPIAuthorizedEntityUrns( - auth, _authorizationChain, EXISTS, List.of(entityUrn))) { - throw new UnauthorizedException( - auth.getActor().toUrnStr() + " is unauthorized to check existence of entities."); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -193,6 +188,11 @@ public ResponseEntity head(String urn) { auth, true); + if (!AuthUtil.isAPIAuthorizedEntityUrns(opContext, EXISTS, List.of(entityUrn))) { + throw new UnauthorizedException( + auth.getActor().toUrnStr() + " is unauthorized to check existence of entities."); + } + if (_entityService.exists(opContext, entityUrn, true)) { return new ResponseEntity<>(HttpStatus.NO_CONTENT); } else { @@ -242,11 +242,6 @@ public ResponseEntity headAspect(String urn, String aspect) { Urn entityUrn = Urn.createFromString(urn); final Authentication auth = AuthenticationContext.getAuthentication(); - if (!AuthUtil.isAPIAuthorizedEntityUrns( - auth, _authorizationChain, EXISTS, List.of(entityUrn))) { - throw new UnauthorizedException( - auth.getActor().toUrnStr() + " is unauthorized to check existence of entities."); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -257,6 +252,11 @@ public ResponseEntity headAspect(String urn, String aspect) { auth, true); + if (!AuthUtil.isAPIAuthorizedEntityUrns(opContext, EXISTS, List.of(entityUrn))) { + throw new UnauthorizedException( + auth.getActor().toUrnStr() + " is unauthorized to check existence of entities."); + } + if (_entityService.exists(opContext, entityUrn, aspect, true)) { return new ResponseEntity<>(HttpStatus.NO_CONTENT); } else { @@ -612,13 +612,6 @@ public ResponseEntity scroll( OpenApiEntitiesUtil.responseClassToEntitySpec(_entityRegistry, _respClazz); Authentication authentication = AuthenticationContext.getAuthentication(); - - if (!AuthUtil.isAPIAuthorizedEntityType( - authentication, _authorizationChain, READ, entitySpec.getName())) { - throw new UnauthorizedException( - authentication.getActor().toUrnStr() + " is unauthorized to search entities."); - } - OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -629,6 +622,11 @@ public ResponseEntity scroll( authentication, true); + if (!AuthUtil.isAPIAuthorizedEntityType(opContext, READ, entitySpec.getName())) { + throw new UnauthorizedException( + authentication.getActor().toUrnStr() + " is unauthorized to search entities."); + } + List sortCriteria = Optional.ofNullable(sort).orElse(Collections.singletonList("urn")).stream() .map( @@ -653,7 +651,7 @@ public ResponseEntity scroll( null, count); - if (!AuthUtil.isAPIAuthorizedResult(authentication, _authorizationChain, result)) { + if (!AuthUtil.isAPIAuthorizedResult(opContext, result)) { throw new UnauthorizedException( authentication.getActor().toUrnStr() + " is unauthorized to " + READ + " entities."); } diff --git a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/controller/GenericEntitiesController.java b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/controller/GenericEntitiesController.java index 8d89417b292155..cb78565a4347de 100644 --- a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/controller/GenericEntitiesController.java +++ b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/controller/GenericEntitiesController.java @@ -181,11 +181,6 @@ public ResponseEntity getEntities( EntitySpec entitySpec = entityRegistry.getEntitySpec(entityName); Authentication authentication = AuthenticationContext.getAuthentication(); - if (!AuthUtil.isAPIAuthorizedEntityType(authentication, authorizationChain, READ, entityName)) { - throw new UnauthorizedException( - authentication.getActor().toUrnStr() + " is unauthorized to " + READ + " entities."); - } - OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -196,6 +191,11 @@ public ResponseEntity getEntities( authentication, true); + if (!AuthUtil.isAPIAuthorizedEntityType(opContext, READ, entityName)) { + throw new UnauthorizedException( + authentication.getActor().toUrnStr() + " is unauthorized to " + READ + " entities."); + } + List sortCriteria; if (!CollectionUtils.isEmpty(sortFields)) { sortCriteria = new ArrayList<>(); @@ -220,7 +220,7 @@ public ResponseEntity getEntities( null, count); - if (!AuthUtil.isAPIAuthorizedResult(authentication, authorizationChain, result)) { + if (!AuthUtil.isAPIAuthorizedResult(opContext, result)) { throw new UnauthorizedException( authentication.getActor().toUrnStr() + " is unauthorized to " + READ + " entities."); } @@ -250,11 +250,6 @@ public ResponseEntity getEntity( Urn urn = validatedUrn(entityUrn); Authentication authentication = AuthenticationContext.getAuthentication(); - if (!AuthUtil.isAPIAuthorizedEntityUrns( - authentication, authorizationChain, READ, List.of(urn))) { - throw new UnauthorizedException( - authentication.getActor().toUrnStr() + " is unauthorized to " + READ + " entities."); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -265,6 +260,11 @@ public ResponseEntity getEntity( authentication, true); + if (!AuthUtil.isAPIAuthorizedEntityUrns(opContext, READ, List.of(urn))) { + throw new UnauthorizedException( + authentication.getActor().toUrnStr() + " is unauthorized to " + READ + " entities."); + } + return buildEntityList( opContext, List.of(urn), @@ -290,11 +290,6 @@ public ResponseEntity headEntity( Urn urn = validatedUrn(entityUrn); Authentication authentication = AuthenticationContext.getAuthentication(); - if (!AuthUtil.isAPIAuthorizedEntityUrns( - authentication, authorizationChain, EXISTS, List.of(urn))) { - throw new UnauthorizedException( - authentication.getActor().toUrnStr() + " is unauthorized to " + EXISTS + " entities."); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -305,6 +300,11 @@ public ResponseEntity headEntity( authentication, true); + if (!AuthUtil.isAPIAuthorizedEntityUrns(opContext, EXISTS, List.of(urn))) { + throw new UnauthorizedException( + authentication.getActor().toUrnStr() + " is unauthorized to " + EXISTS + " entities."); + } + return exists(opContext, urn, null, includeSoftDelete) ? ResponseEntity.noContent().build() : ResponseEntity.notFound().build(); @@ -327,11 +327,6 @@ public ResponseEntity getAspect( Urn urn = validatedUrn(entityUrn); Authentication authentication = AuthenticationContext.getAuthentication(); - if (!AuthUtil.isAPIAuthorizedEntityUrns( - authentication, authorizationChain, READ, List.of(urn))) { - throw new UnauthorizedException( - authentication.getActor().toUrnStr() + " is unauthorized to " + READ + " entities."); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -342,6 +337,11 @@ public ResponseEntity getAspect( authentication, true); + if (!AuthUtil.isAPIAuthorizedEntityUrns(opContext, READ, List.of(urn))) { + throw new UnauthorizedException( + authentication.getActor().toUrnStr() + " is unauthorized to " + READ + " entities."); + } + final List resultList; if (version == 0) { resultList = buildEntityList(opContext, List.of(urn), Set.of(aspectName), withSystemMetadata); @@ -380,11 +380,6 @@ public ResponseEntity headAspect( Urn urn = validatedUrn(entityUrn); Authentication authentication = AuthenticationContext.getAuthentication(); - if (!AuthUtil.isAPIAuthorizedEntityUrns( - authentication, authorizationChain, EXISTS, List.of(urn))) { - throw new UnauthorizedException( - authentication.getActor().toUrnStr() + " is unauthorized to " + EXISTS + " entities."); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -395,6 +390,11 @@ public ResponseEntity headAspect( authentication, true); + if (!AuthUtil.isAPIAuthorizedEntityUrns(opContext, EXISTS, List.of(urn))) { + throw new UnauthorizedException( + authentication.getActor().toUrnStr() + " is unauthorized to " + EXISTS + " entities."); + } + return exists(opContext, urn, lookupAspectSpec(urn, aspectName).getName(), includeSoftDelete) ? ResponseEntity.noContent().build() : ResponseEntity.notFound().build(); @@ -412,11 +412,6 @@ public void deleteEntity( EntitySpec entitySpec = entityRegistry.getEntitySpec(entityName); Urn urn = validatedUrn(entityUrn); Authentication authentication = AuthenticationContext.getAuthentication(); - if (!AuthUtil.isAPIAuthorizedEntityUrns( - authentication, authorizationChain, DELETE, List.of(urn))) { - throw new UnauthorizedException( - authentication.getActor().toUrnStr() + " is unauthorized to " + DELETE + " entities."); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -427,6 +422,11 @@ public void deleteEntity( authentication, true); + if (!AuthUtil.isAPIAuthorizedEntityUrns(opContext, DELETE, List.of(urn))) { + throw new UnauthorizedException( + authentication.getActor().toUrnStr() + " is unauthorized to " + DELETE + " entities."); + } + entityService.deleteUrn(opContext, urn); } @@ -443,13 +443,6 @@ public ResponseEntity> createEntity( throws InvalidUrnException, JsonProcessingException { Authentication authentication = AuthenticationContext.getAuthentication(); - - if (!AuthUtil.isAPIAuthorizedEntityType( - authentication, authorizationChain, CREATE, entityName)) { - throw new UnauthorizedException( - authentication.getActor().toUrnStr() + " is unauthorized to " + CREATE + " entities."); - } - OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -460,6 +453,11 @@ public ResponseEntity> createEntity( authentication, true); + if (!AuthUtil.isAPIAuthorizedEntityType(opContext, CREATE, entityName)) { + throw new UnauthorizedException( + authentication.getActor().toUrnStr() + " is unauthorized to " + CREATE + " entities."); + } + AspectsBatch batch = toMCPBatch(opContext, jsonEntityList, authentication.getActor()); Set results = entityService.ingestProposal(opContext, batch, async); @@ -482,11 +480,6 @@ public void deleteAspect( Urn urn = validatedUrn(entityUrn); Authentication authentication = AuthenticationContext.getAuthentication(); - if (!AuthUtil.isAPIAuthorizedEntityUrns( - authentication, authorizationChain, DELETE, List.of(urn))) { - throw new UnauthorizedException( - authentication.getActor().toUrnStr() + " is unauthorized to " + DELETE + " entities."); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -497,6 +490,11 @@ public void deleteAspect( authentication, true); + if (!AuthUtil.isAPIAuthorizedEntityUrns(opContext, DELETE, List.of(urn))) { + throw new UnauthorizedException( + authentication.getActor().toUrnStr() + " is unauthorized to " + DELETE + " entities."); + } + entityService.deleteAspect( opContext, entityUrn, lookupAspectSpec(urn, aspectName).getName(), Map.of(), true); } @@ -521,12 +519,6 @@ public ResponseEntity createAspect( Urn urn = validatedUrn(entityUrn); EntitySpec entitySpec = entityRegistry.getEntitySpec(entityName); Authentication authentication = AuthenticationContext.getAuthentication(); - - if (!AuthUtil.isAPIAuthorizedEntityUrns( - authentication, authorizationChain, CREATE, List.of(urn))) { - throw new UnauthorizedException( - authentication.getActor().toUrnStr() + " is unauthorized to " + CREATE + " entities."); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -537,6 +529,11 @@ public ResponseEntity createAspect( authentication, true); + if (!AuthUtil.isAPIAuthorizedEntityUrns(opContext, CREATE, List.of(urn))) { + throw new UnauthorizedException( + authentication.getActor().toUrnStr() + " is unauthorized to " + CREATE + " entities."); + } + AspectSpec aspectSpec = lookupAspectSpec(entitySpec, aspectName); ChangeMCP upsert = toUpsertItem( @@ -586,11 +583,6 @@ public ResponseEntity patchAspect( Urn urn = validatedUrn(entityUrn); EntitySpec entitySpec = entityRegistry.getEntitySpec(entityName); Authentication authentication = AuthenticationContext.getAuthentication(); - if (!AuthUtil.isAPIAuthorizedEntityUrns( - authentication, authorizationChain, UPDATE, List.of(urn))) { - throw new UnauthorizedException( - authentication.getActor().toUrnStr() + " is unauthorized to " + UPDATE + " entities."); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -601,6 +593,11 @@ public ResponseEntity patchAspect( authentication, true); + if (!AuthUtil.isAPIAuthorizedEntityUrns(opContext, UPDATE, List.of(urn))) { + throw new UnauthorizedException( + authentication.getActor().toUrnStr() + " is unauthorized to " + UPDATE + " entities."); + } + AspectSpec aspectSpec = lookupAspectSpec(entitySpec, aspectName); RecordTemplate currentValue = entityService.getAspect(opContext, urn, aspectSpec.getName(), 0); diff --git a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/controller/GenericRelationshipController.java b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/controller/GenericRelationshipController.java index efc3d9375e09e7..44f2f8ea03643a 100644 --- a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/controller/GenericRelationshipController.java +++ b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/controller/GenericRelationshipController.java @@ -17,15 +17,19 @@ import com.linkedin.metadata.query.filter.RelationshipDirection; import com.linkedin.metadata.query.filter.RelationshipFilter; import com.linkedin.metadata.search.utils.QueryUtils; +import io.datahubproject.metadata.context.OperationContext; +import io.datahubproject.metadata.context.RequestContext; import io.datahubproject.openapi.exception.UnauthorizedException; import io.datahubproject.openapi.models.GenericScrollResult; import io.datahubproject.openapi.v2.models.GenericRelationship; import io.swagger.v3.oas.annotations.Operation; +import jakarta.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -36,6 +40,10 @@ public abstract class GenericRelationshipController { @Autowired private ElasticSearchGraphService graphService; @Autowired private AuthorizerChain authorizationChain; + @Qualifier("systemOperationContext") + @Autowired + protected OperationContext systemOperationContext; + /** * Returns relationship edges by type * @@ -47,12 +55,26 @@ public abstract class GenericRelationshipController { @GetMapping(value = "/{relationshipType}", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Scroll relationships of the given type.") public ResponseEntity> getRelationshipsByType( + HttpServletRequest request, @PathVariable("relationshipType") String relationshipType, @RequestParam(value = "count", defaultValue = "10") Integer count, @RequestParam(value = "scrollId", required = false) String scrollId) { Authentication authentication = AuthenticationContext.getAuthentication(); - if (!AuthUtil.isAPIAuthorized(authentication, authorizationChain, RELATIONSHIP, READ)) { + OperationContext opContext = + OperationContext.asSession( + systemOperationContext, + RequestContext.builder() + .buildOpenapi( + authentication.getActor().toUrnStr(), + request, + "getRelationshipsByType", + List.of()), + authorizationChain, + authentication, + true); + + if (!AuthUtil.isAPIAuthorized(opContext, RELATIONSHIP, READ)) { throw new UnauthorizedException( authentication.getActor().toUrnStr() + " is unauthorized to " @@ -76,8 +98,7 @@ public ResponseEntity> getRelationships null); if (!AuthUtil.isAPIAuthorizedUrns( - authentication, - authorizationChain, + opContext, RELATIONSHIP, READ, result.getEntities().stream() @@ -114,6 +135,7 @@ public ResponseEntity> getRelationships @GetMapping(value = "/{entityName}/{entityUrn}", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Scroll relationships from a given entity.") public ResponseEntity> getRelationshipsByEntity( + HttpServletRequest request, @PathVariable("entityName") String entityName, @PathVariable("entityUrn") String entityUrn, @RequestParam(value = "relationshipType[]", required = false, defaultValue = "*") @@ -125,12 +147,21 @@ public ResponseEntity> getRelationships final RelatedEntitiesScrollResult result; Authentication authentication = AuthenticationContext.getAuthentication(); + OperationContext opContext = + OperationContext.asSession( + systemOperationContext, + RequestContext.builder() + .buildOpenapi( + authentication.getActor().toUrnStr(), + request, + "getRelationshipsByEntity", + List.of()), + authorizationChain, + authentication, + true); + if (!AuthUtil.isAPIAuthorizedUrns( - authentication, - authorizationChain, - RELATIONSHIP, - READ, - List.of(UrnUtils.getUrn(entityUrn)))) { + opContext, RELATIONSHIP, READ, List.of(UrnUtils.getUrn(entityUrn)))) { throw new UnauthorizedException( authentication.getActor().toUrnStr() + " is unauthorized to " @@ -178,8 +209,7 @@ public ResponseEntity> getRelationships } if (!AuthUtil.isAPIAuthorizedUrns( - authentication, - authorizationChain, + opContext, RELATIONSHIP, READ, result.getEntities().stream() diff --git a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/operations/elastic/OperationsController.java b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/operations/elastic/OperationsController.java index ea72bac73edf38..083e515d055f5d 100644 --- a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/operations/elastic/OperationsController.java +++ b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/operations/elastic/OperationsController.java @@ -99,12 +99,19 @@ public void initBinder(WebDataBinder binder) { @Tag(name = "ElasticSearchOperations") @GetMapping(path = "/getTaskStatus", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Get Task Status") - public ResponseEntity getTaskStatus(String task) { + public ResponseEntity getTaskStatus(HttpServletRequest request, String task) { Authentication authentication = AuthenticationContext.getAuthentication(); String actorUrnStr = authentication.getActor().toUrnStr(); - if (!AuthUtil.isAPIAuthorized( - authentication, authorizerChain, PoliciesConfig.GET_ES_TASK_STATUS_PRIVILEGE)) { + OperationContext opContext = + OperationContext.asSession( + systemOperationContext, + RequestContext.builder().buildOpenapi(actorUrnStr, request, "getTaskStatus", List.of()), + authorizerChain, + authentication, + true); + + if (!AuthUtil.isAPIAuthorized(opContext, PoliciesConfig.GET_ES_TASK_STATUS_PRIVILEGE)) { return ResponseEntity.status(HttpStatus.FORBIDDEN) .body(String.format(actorUrnStr + " is not authorized to get ElasticSearch task status")); } @@ -139,11 +146,6 @@ public ResponseEntity getIndexSizes(HttpServletRequest request) { Authentication authentication = AuthenticationContext.getAuthentication(); String actorUrnStr = authentication.getActor().toUrnStr(); - if (!AuthUtil.isAPIAuthorized( - authentication, authorizerChain, PoliciesConfig.GET_TIMESERIES_INDEX_SIZES_PRIVILEGE)) { - return ResponseEntity.status(HttpStatus.FORBIDDEN) - .body(String.format(actorUrnStr + " is not authorized to get timeseries index sizes")); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -152,6 +154,11 @@ public ResponseEntity getIndexSizes(HttpServletRequest request) { authentication, true); + if (!AuthUtil.isAPIAuthorized(opContext, PoliciesConfig.GET_TIMESERIES_INDEX_SIZES_PRIVILEGE)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN) + .body(String.format(actorUrnStr + " is not authorized to get timeseries index sizes")); + } + List indexSizeResults = timeseriesAspectService.getIndexSizes(opContext); JSONObject j = new JSONObject(); @@ -230,12 +237,6 @@ public ResponseEntity explainSearchQuery( Authentication authentication = AuthenticationContext.getAuthentication(); String actorUrnStr = authentication.getActor().toUrnStr(); - - if (!AuthUtil.isAPIAuthorized( - authentication, authorizerChain, PoliciesConfig.ES_EXPLAIN_QUERY_PRIVILEGE)) { - log.error("{} is not authorized to get explain queries", actorUrnStr); - return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null); - } OperationContext opContext = systemOperationContext .asSession( @@ -254,6 +255,11 @@ public ResponseEntity explainSearchQuery( } }); + if (!AuthUtil.isAPIAuthorized(opContext, PoliciesConfig.ES_EXPLAIN_QUERY_PRIVILEGE)) { + log.error("{} is not authorized to get explain queries", actorUrnStr); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null); + } + ExplainResponse response = searchService.explain( opContext, @@ -339,11 +345,6 @@ public ResponseEntity explainSearchQueryDiff( Authentication authentication = AuthenticationContext.getAuthentication(); String actorUrnStr = authentication.getActor().toUrnStr(); - if (!AuthUtil.isAPIAuthorized( - authentication, authorizerChain, PoliciesConfig.ES_EXPLAIN_QUERY_PRIVILEGE)) { - log.error("{} is not authorized to get explain queries", actorUrnStr); - return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null); - } OperationContext opContext = systemOperationContext .asSession( @@ -362,6 +363,11 @@ public ResponseEntity explainSearchQueryDiff( } }); + if (!AuthUtil.isAPIAuthorized(opContext, PoliciesConfig.ES_EXPLAIN_QUERY_PRIVILEGE)) { + log.error("{} is not authorized to get explain queries", actorUrnStr); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null); + } + ExplainResponse responseA = searchService.explain( opContext, @@ -433,10 +439,6 @@ public ResponseEntity> restoreIndices( @RequestParam(required = false, name = "lePitEpochMs") @Nullable Long lePitEpochMs) { Authentication authentication = AuthenticationContext.getAuthentication(); - if (!AuthUtil.isAPIAuthorized( - authentication, authorizerChain, PoliciesConfig.RESTORE_INDICES_PRIVILEGE)) { - return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -447,6 +449,10 @@ public ResponseEntity> restoreIndices( authentication, true); + if (!AuthUtil.isAPIAuthorized(opContext, PoliciesConfig.RESTORE_INDICES_PRIVILEGE)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + RestoreIndicesArgs args = new RestoreIndicesArgs() .aspectName(aspectName) @@ -476,10 +482,6 @@ public ResponseEntity> restoreIndices( throws RemoteInvocationException, URISyntaxException { Authentication authentication = AuthenticationContext.getAuthentication(); - if (!AuthUtil.isAPIAuthorized( - authentication, authorizerChain, PoliciesConfig.RESTORE_INDICES_PRIVILEGE)) { - return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -490,6 +492,10 @@ public ResponseEntity> restoreIndices( authentication, true); + if (!AuthUtil.isAPIAuthorized(opContext, PoliciesConfig.RESTORE_INDICES_PRIVILEGE)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + return ResponseEntity.of( Optional.of( entityService.restoreIndices( diff --git a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v1/entities/EntitiesController.java b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v1/entities/EntitiesController.java index 99eede15629d20..03050868efdcab 100644 --- a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v1/entities/EntitiesController.java +++ b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v1/entities/EntitiesController.java @@ -115,10 +115,6 @@ public ResponseEntity getEntities( log.debug("GET ENTITIES {}", entityUrns); Authentication authentication = AuthenticationContext.getAuthentication(); String actorUrnStr = authentication.getActor().toUrnStr(); - - if (!AuthUtil.isAPIAuthorizedEntityUrns(authentication, _authorizerChain, READ, entityUrns)) { - throw new UnauthorizedException(actorUrnStr + " is unauthorized to get entities."); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -135,6 +131,10 @@ public ResponseEntity getEntities( authentication, true); + if (!AuthUtil.isAPIAuthorizedEntityUrns(opContext, READ, entityUrns)) { + throw new UnauthorizedException(actorUrnStr + " is unauthorized to get entities."); + } + if (entityUrns.size() <= 0) { return ResponseEntity.ok(UrnResponseMap.builder().responses(Collections.emptyMap()).build()); } @@ -209,9 +209,7 @@ public ResponseEntity> postEntities( Ingest Authorization Checks */ List> exceptions = - isAPIAuthorized( - authentication, _authorizerChain, ENTITY, opContext.getEntityRegistry(), proposals) - .stream() + isAPIAuthorized(opContext, ENTITY, opContext.getEntityRegistry(), proposals).stream() .filter(p -> p.getSecond() != com.linkedin.restli.common.HttpStatus.S_200_OK.getCode()) .collect(Collectors.toList()); if (!exceptions.isEmpty()) { @@ -277,10 +275,6 @@ public ResponseEntity> deleteEntities( .map(UrnUtils::getUrn) .collect(Collectors.toSet()); - if (!AuthUtil.isAPIAuthorizedEntityUrns( - authentication, _authorizerChain, DELETE, entityUrns)) { - throw new UnauthorizedException(actorUrnStr + " is unauthorized to delete entities."); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -294,6 +288,10 @@ public ResponseEntity> deleteEntities( authentication, true); + if (!AuthUtil.isAPIAuthorizedEntityUrns(opContext, DELETE, entityUrns)) { + throw new UnauthorizedException(actorUrnStr + " is unauthorized to delete entities."); + } + if (!soft) { return ResponseEntity.ok( entityUrns.stream() diff --git a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v1/relationships/RelationshipsController.java b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v1/relationships/RelationshipsController.java index f91ebb61123f0c..d7baac3e10561a 100644 --- a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v1/relationships/RelationshipsController.java +++ b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v1/relationships/RelationshipsController.java @@ -16,6 +16,8 @@ import com.linkedin.metadata.graph.RelatedEntitiesResult; import com.linkedin.metadata.search.utils.QueryUtils; import com.linkedin.metadata.utils.metrics.MetricUtils; +import io.datahubproject.metadata.context.OperationContext; +import io.datahubproject.metadata.context.RequestContext; import io.datahubproject.openapi.exception.UnauthorizedException; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -23,6 +25,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; import java.net.URLDecoder; import java.nio.charset.Charset; import java.util.Arrays; @@ -58,6 +61,7 @@ public enum RelationshipDirection { } private static final int MAX_DOWNSTREAM_CNT = 200; + private final OperationContext systemOperationContext; private final GraphService _graphService; private final AuthorizerChain _authorizerChain; @@ -114,6 +118,7 @@ private RelatedEntitiesResult getRelatedEntities( content = @Content(schema = @Schema(implementation = RelatedEntitiesResult.class))) }) public ResponseEntity getRelationships( + HttpServletRequest request, @Parameter( name = "urn", required = true, @@ -158,8 +163,16 @@ public ResponseEntity getRelationships( Authentication authentication = AuthenticationContext.getAuthentication(); String actorUrnStr = authentication.getActor().toUrnStr(); - if (!AuthUtil.isAPIAuthorizedUrns( - authentication, _authorizerChain, RELATIONSHIP, READ, List.of(entityUrn))) { + OperationContext opContext = + OperationContext.asSession( + systemOperationContext, + RequestContext.builder() + .buildOpenapi(actorUrnStr, request, "getRelationships", entityUrn.getEntityType()), + _authorizerChain, + authentication, + true); + + if (!AuthUtil.isAPIAuthorizedUrns(opContext, RELATIONSHIP, READ, List.of(entityUrn))) { throw new UnauthorizedException(actorUrnStr + " is unauthorized to get relationships."); } diff --git a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v1/timeline/TimelineControllerV1.java b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v1/timeline/TimelineControllerV1.java index 9456843a3d8105..30cdb632d54773 100644 --- a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v1/timeline/TimelineControllerV1.java +++ b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v1/timeline/TimelineControllerV1.java @@ -14,8 +14,11 @@ import com.linkedin.metadata.timeline.TimelineService; import com.linkedin.metadata.timeline.data.ChangeCategory; import com.linkedin.metadata.timeline.data.ChangeTransaction; +import io.datahubproject.metadata.context.OperationContext; +import io.datahubproject.metadata.context.RequestContext; import io.datahubproject.openapi.exception.UnauthorizedException; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; import java.net.URISyntaxException; import java.util.List; import java.util.Set; @@ -42,6 +45,7 @@ "An API for retrieving historical updates to entities and their related documentation.") public class TimelineControllerV1 { + private final OperationContext systemOperationContext; private final TimelineService _timelineService; private final AuthorizerChain _authorizerChain; @@ -60,6 +64,7 @@ public class TimelineControllerV1 { */ @GetMapping(path = "/{urn}", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> getTimeline( + HttpServletRequest request, @PathVariable("urn") String rawUrn, @RequestParam(name = "startTime", defaultValue = "-1") long startTime, @RequestParam(name = "endTime", defaultValue = "0") long endTime, @@ -72,14 +77,23 @@ public ResponseEntity> getTimeline( Urn urn = Urn.createFromString(rawUrn); Authentication authentication = AuthenticationContext.getAuthentication(); String actorUrnStr = authentication.getActor().toUrnStr(); + + OperationContext opContext = + OperationContext.asSession( + systemOperationContext, + RequestContext.builder() + .buildOpenapi(actorUrnStr, request, "getTimeline", urn.getEntityType()), + _authorizerChain, + authentication, + true); + EntitySpec resourceSpec = new EntitySpec(urn.getEntityType(), rawUrn); DisjunctivePrivilegeGroup orGroup = new DisjunctivePrivilegeGroup( ImmutableList.of( new ConjunctivePrivilegeGroup( ImmutableList.of(PoliciesConfig.GET_TIMELINE_PRIVILEGE.getType())))); - if (restApiAuthorizationEnabled - && !AuthUtil.isAuthorized(_authorizerChain, actorUrnStr, orGroup, resourceSpec)) { + if (restApiAuthorizationEnabled && !AuthUtil.isAuthorized(opContext, orGroup, resourceSpec)) { throw new UnauthorizedException(actorUrnStr + " is unauthorized to edit entities."); } return ResponseEntity.ok( diff --git a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v2/controller/EntityController.java b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v2/controller/EntityController.java index 1207eb331b795e..d20acbee79b227 100644 --- a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v2/controller/EntityController.java +++ b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v2/controller/EntityController.java @@ -87,10 +87,6 @@ public ResponseEntity> g List urns = request.getUrns().stream().map(UrnUtils::getUrn).collect(Collectors.toList()); Authentication authentication = AuthenticationContext.getAuthentication(); - if (!AuthUtil.isAPIAuthorizedEntityUrns(authentication, authorizationChain, READ, urns)) { - throw new UnauthorizedException( - authentication.getActor().toUrnStr() + " is unauthorized to " + READ + " entities."); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -104,6 +100,11 @@ public ResponseEntity> g authentication, true); + if (!AuthUtil.isAPIAuthorizedEntityUrns(opContext, READ, urns)) { + throw new UnauthorizedException( + authentication.getActor().toUrnStr() + " is unauthorized to " + READ + " entities."); + } + return ResponseEntity.of( Optional.of( BatchGetUrnResponseV2.builder() diff --git a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v2/controller/PlatformEntitiesController.java b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v2/controller/PlatformEntitiesController.java index 87c72064ad7a77..6c99d972dde03f 100644 --- a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v2/controller/PlatformEntitiesController.java +++ b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v2/controller/PlatformEntitiesController.java @@ -91,9 +91,7 @@ public ResponseEntity> postEntities( Ingest Authorization Checks */ List> exceptions = - isAPIAuthorized( - authentication, _authorizerChain, ENTITY, opContext.getEntityRegistry(), proposals) - .stream() + isAPIAuthorized(opContext, ENTITY, opContext.getEntityRegistry(), proposals).stream() .filter(p -> p.getSecond() != com.linkedin.restli.common.HttpStatus.S_200_OK.getCode()) .collect(Collectors.toList()); if (!exceptions.isEmpty()) { diff --git a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v2/controller/TimelineControllerV2.java b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v2/controller/TimelineControllerV2.java index 29b7de6ae5e8f7..f3d0d5188b1e7e 100644 --- a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v2/controller/TimelineControllerV2.java +++ b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v2/controller/TimelineControllerV2.java @@ -14,8 +14,11 @@ import com.linkedin.metadata.timeline.TimelineService; import com.linkedin.metadata.timeline.data.ChangeCategory; import com.linkedin.metadata.timeline.data.ChangeTransaction; +import io.datahubproject.metadata.context.OperationContext; +import io.datahubproject.metadata.context.RequestContext; import io.datahubproject.openapi.exception.UnauthorizedException; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; import java.net.URISyntaxException; import java.util.List; import java.util.Set; @@ -38,6 +41,7 @@ "An API for retrieving historical updates to entities and their related documentation.") public class TimelineControllerV2 { + private final OperationContext systemOperationContext; private final TimelineService _timelineService; private final AuthorizerChain _authorizerChain; @@ -56,6 +60,7 @@ public class TimelineControllerV2 { */ @GetMapping(path = "/{urn}", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> getTimeline( + HttpServletRequest request, @PathVariable("urn") String rawUrn, @RequestParam(name = "startTime", defaultValue = "-1") long startTime, @RequestParam(name = "endTime", defaultValue = "0") long endTime, @@ -68,14 +73,23 @@ public ResponseEntity> getTimeline( Urn urn = Urn.createFromString(rawUrn); Authentication authentication = AuthenticationContext.getAuthentication(); String actorUrnStr = authentication.getActor().toUrnStr(); + + OperationContext opContext = + OperationContext.asSession( + systemOperationContext, + RequestContext.builder() + .buildOpenapi(actorUrnStr, request, "getTimeline", urn.getEntityType()), + _authorizerChain, + authentication, + true); + EntitySpec resourceSpec = new EntitySpec(urn.getEntityType(), rawUrn); DisjunctivePrivilegeGroup orGroup = new DisjunctivePrivilegeGroup( ImmutableList.of( new ConjunctivePrivilegeGroup( ImmutableList.of(PoliciesConfig.GET_TIMELINE_PRIVILEGE.getType())))); - if (restApiAuthorizationEnabled - && !AuthUtil.isAuthorized(_authorizerChain, actorUrnStr, orGroup, resourceSpec)) { + if (restApiAuthorizationEnabled && !AuthUtil.isAuthorized(opContext, orGroup, resourceSpec)) { throw new UnauthorizedException(actorUrnStr + " is unauthorized to edit entities."); } return ResponseEntity.ok( diff --git a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v2/controller/TimeseriesController.java b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v2/controller/TimeseriesController.java index 4e8c0abcb0c227..3b896dc5000822 100644 --- a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v2/controller/TimeseriesController.java +++ b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v2/controller/TimeseriesController.java @@ -69,12 +69,6 @@ public ResponseEntity> getAspects( throws URISyntaxException { Authentication authentication = AuthenticationContext.getAuthentication(); - if (!AuthUtil.isAPIAuthorized(authentication, authorizationChain, TIMESERIES, READ) - || !AuthUtil.isAPIAuthorizedEntityType( - authentication, authorizationChain, READ, entityName)) { - throw new UnauthorizedException( - authentication.getActor().toUrnStr() + " is unauthorized to " + READ + " " + TIMESERIES); - } OperationContext opContext = OperationContext.asSession( systemOperationContext, @@ -85,6 +79,12 @@ public ResponseEntity> getAspects( authentication, true); + if (!AuthUtil.isAPIAuthorized(opContext, TIMESERIES, READ) + || !AuthUtil.isAPIAuthorizedEntityType(opContext, READ, entityName)) { + throw new UnauthorizedException( + authentication.getActor().toUrnStr() + " is unauthorized to " + READ + " " + TIMESERIES); + } + AspectSpec aspectSpec = entityRegistry.getEntitySpec(entityName).getAspectSpec(aspectName); if (!aspectSpec.isTimeseries()) { throw new IllegalArgumentException("Only timeseries aspects are supported."); @@ -108,8 +108,7 @@ public ResponseEntity> getAspects( endTimeMillis); if (!AuthUtil.isAPIAuthorizedUrns( - authentication, - authorizationChain, + opContext, TIMESERIES, READ, result.getDocuments().stream() diff --git a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v3/controller/EntityController.java b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v3/controller/EntityController.java index fbc9bf2956cfd3..d7694f3aed9334 100644 --- a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v3/controller/EntityController.java +++ b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/v3/controller/EntityController.java @@ -103,8 +103,7 @@ public ResponseEntity> getEntityBatch( authentication, true); - if (!AuthUtil.isAPIAuthorizedEntityUrns( - authentication, authorizationChain, READ, requestMap.keySet())) { + if (!AuthUtil.isAPIAuthorizedEntityUrns(opContext, READ, requestMap.keySet())) { throw new UnauthorizedException( authentication.getActor().toUrnStr() + " is unauthorized to " + READ + " entities."); } diff --git a/metadata-service/restli-client-api/src/main/java/com/linkedin/entity/client/EntityClientCache.java b/metadata-service/restli-client-api/src/main/java/com/linkedin/entity/client/EntityClientCache.java index 959bee565acfc6..340632a2fde9dd 100644 --- a/metadata-service/restli-client-api/src/main/java/com/linkedin/entity/client/EntityClientCache.java +++ b/metadata-service/restli-client-api/src/main/java/com/linkedin/entity/client/EntityClientCache.java @@ -4,6 +4,7 @@ import com.github.benmanes.caffeine.cache.LoadingCache; import com.github.benmanes.caffeine.cache.Weigher; +import com.google.common.annotations.VisibleForTesting; import com.linkedin.common.client.ClientCache; import com.linkedin.common.urn.Urn; import com.linkedin.entity.EntityResponse; @@ -80,6 +81,8 @@ public Map batchGetV2( Set responses = envelopedAspects.entrySet().stream() + // Exclude cached nulls + .filter(entry -> !(entry.getValue() instanceof NullEnvelopedAspect)) .map(entry -> Pair.of(entry.getKey().getUrn(), entry.getValue())) .collect( Collectors.groupingBy( @@ -104,6 +107,11 @@ public Map batchGetV2( return response; } + @VisibleForTesting + ClientCache getCache() { + return this.cache; + } + private static EntityResponse toEntityResponse( Urn urn, Collection envelopedAspects) { final EntityResponse response = new EntityResponse(); @@ -130,9 +138,13 @@ private EntityClientCacheBuilder loadFunction( public EntityClientCache build( @Nonnull final Function> fetchFunction, Class metricClazz) { + // estimate size Weigher weighByEstimatedSize = - (key, value) -> value.getValue().data().toString().getBytes().length; + (key, value) -> + value instanceof NullEnvelopedAspect + ? key.getUrn().toString().getBytes().length + : value.getValue().data().toString().getBytes().length; // batch loads data from entity client (restli or java) Function, Map> loader = @@ -192,37 +204,53 @@ private static Map loadByEntity( String contextId, Map> keysByEntity, Function> loadFunction) { - return keysByEntity.entrySet().stream() - .flatMap( - entry -> { - Set urns = - entry.getValue().stream().map(Key::getUrn).collect(Collectors.toSet()); - Set aspects = - entry.getValue().stream().map(Key::getAspectName).collect(Collectors.toSet()); - return loadFunction - .apply( - CollectionKey.builder() - .contextId(contextId) - .urns(urns) - .aspectNames(aspects) - .build()) - .entrySet() - .stream(); - }) - .flatMap( - resp -> - resp.getValue().getAspects().values().stream() - .map( - envAspect -> { - Key key = - Key.builder() - .contextId(contextId) - .urn(resp.getKey()) - .aspectName(envAspect.getName()) - .build(); - return Map.entry(key, envAspect); - })) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + Map result = + keysByEntity.entrySet().stream() + .flatMap( + entry -> { + Set urns = + entry.getValue().stream().map(Key::getUrn).collect(Collectors.toSet()); + Set aspects = + entry.getValue().stream().map(Key::getAspectName).collect(Collectors.toSet()); + return loadFunction + .apply( + CollectionKey.builder() + .contextId(contextId) + .urns(urns) + .aspectNames(aspects) + .build()) + .entrySet() + .stream(); + }) + .flatMap( + resp -> + resp.getValue().getAspects().values().stream() + .map( + envAspect -> { + Key key = + Key.builder() + .contextId(contextId) + .urn(resp.getKey()) + .aspectName(envAspect.getName()) + .build(); + return Map.entry(key, envAspect); + })) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + /* + * Traditionally responses from the API omit non-existent aspects. For the cache, + * we re-introduce the missing keys. + */ + Map missingAspects = + keysByEntity.values().stream() + .flatMap(Set::stream) + .filter(key -> !result.containsKey(key)) + .map(missingKey -> Map.entry(missingKey, NullEnvelopedAspect.NULL)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + result.putAll(missingAspects); + + return result; } @Data @@ -244,4 +272,10 @@ public static class CollectionKey { private final Set urns; private final Set aspectNames; } + + /** Represents a cached null aspect */ + @VisibleForTesting + static class NullEnvelopedAspect extends EnvelopedAspect { + private static final NullEnvelopedAspect NULL = new NullEnvelopedAspect(); + } } diff --git a/metadata-service/restli-client/src/test/java/com/linkedin/entity/client/SystemRestliEntityClientTest.java b/metadata-service/restli-client/src/test/java/com/linkedin/entity/client/SystemRestliEntityClientTest.java index e6d53fc98e2e37..817b76f74268c8 100644 --- a/metadata-service/restli-client/src/test/java/com/linkedin/entity/client/SystemRestliEntityClientTest.java +++ b/metadata-service/restli-client/src/test/java/com/linkedin/entity/client/SystemRestliEntityClientTest.java @@ -1,5 +1,7 @@ package com.linkedin.entity.client; +import static com.linkedin.metadata.Constants.DATASET_PROPERTIES_ASPECT_NAME; +import static com.linkedin.metadata.Constants.STATUS_ASPECT_NAME; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; @@ -14,7 +16,6 @@ import com.linkedin.entity.Aspect; import com.linkedin.entity.EnvelopedAspect; import com.linkedin.entity.EnvelopedAspectMap; -import com.linkedin.metadata.Constants; import com.linkedin.metadata.config.cache.client.EntityClientCacheConfig; import com.linkedin.parseq.retry.backoff.ConstantBackoff; import com.linkedin.r2.RemoteInvocationException; @@ -57,7 +58,7 @@ public void testCache() throws RemoteInvocationException, URISyntaxException { TestOperationContexts.systemContextNoSearchAuthorization(), TEST_URN.getEntityType(), TEST_URN, - Set.of(Constants.STATUS_ASPECT_NAME)), + Set.of(STATUS_ASPECT_NAME)), responseStatusTrue, "Expected un-cached Status.removed=true result"); @@ -67,7 +68,7 @@ public void testCache() throws RemoteInvocationException, URISyntaxException { TestOperationContexts.systemContextNoSearchAuthorization(), TEST_URN.getEntityType(), TEST_URN, - Set.of(Constants.STATUS_ASPECT_NAME)), + Set.of(STATUS_ASPECT_NAME)), responseStatusFalse, "Expected un-cached Status.removed=false result"); @@ -81,7 +82,7 @@ public void testCache() throws RemoteInvocationException, URISyntaxException { cacheConfig.setEnabled(true); cacheConfig.setMaxBytes(100); cacheConfig.setEntityAspectTTLSeconds( - Map.of(TEST_URN.getEntityType(), Map.of(Constants.STATUS_ASPECT_NAME, 60))); + Map.of(TEST_URN.getEntityType(), Map.of(STATUS_ASPECT_NAME, 60))); SystemRestliEntityClient cacheTest = new SystemRestliEntityClient( @@ -93,7 +94,7 @@ public void testCache() throws RemoteInvocationException, URISyntaxException { TestOperationContexts.systemContextNoSearchAuthorization(), TEST_URN.getEntityType(), TEST_URN, - Set.of(Constants.STATUS_ASPECT_NAME)), + Set.of(STATUS_ASPECT_NAME)), responseStatusTrue, "Expected initial un-cached Status.removed=true result"); @@ -103,7 +104,7 @@ public void testCache() throws RemoteInvocationException, URISyntaxException { TestOperationContexts.systemContextNoSearchAuthorization(), TEST_URN.getEntityType(), TEST_URN, - Set.of(Constants.STATUS_ASPECT_NAME)), + Set.of(STATUS_ASPECT_NAME)), responseStatusTrue, "Expected CACHED Status.removed=true result"); @@ -131,7 +132,7 @@ public void testBatchCache() throws RemoteInvocationException, URISyntaxExceptio TestOperationContexts.systemContextNoSearchAuthorization(), TEST_URN.getEntityType(), Set.of(TEST_URN), - Set.of(Constants.STATUS_ASPECT_NAME)), + Set.of(STATUS_ASPECT_NAME)), Map.of(TEST_URN, responseStatusTrue), "Expected un-cached Status.removed=true result"); @@ -141,7 +142,7 @@ public void testBatchCache() throws RemoteInvocationException, URISyntaxExceptio TestOperationContexts.systemContextNoSearchAuthorization(), TEST_URN.getEntityType(), Set.of(TEST_URN), - Set.of(Constants.STATUS_ASPECT_NAME)), + Set.of(STATUS_ASPECT_NAME)), Map.of(TEST_URN, responseStatusFalse), "Expected un-cached Status.removed=false result"); @@ -155,7 +156,7 @@ public void testBatchCache() throws RemoteInvocationException, URISyntaxExceptio cacheConfig.setEnabled(true); cacheConfig.setMaxBytes(100); cacheConfig.setEntityAspectTTLSeconds( - Map.of(TEST_URN.getEntityType(), Map.of(Constants.STATUS_ASPECT_NAME, 60))); + Map.of(TEST_URN.getEntityType(), Map.of(STATUS_ASPECT_NAME, 60))); SystemRestliEntityClient cacheTest = new SystemRestliEntityClient( @@ -167,7 +168,7 @@ public void testBatchCache() throws RemoteInvocationException, URISyntaxExceptio TestOperationContexts.systemContextNoSearchAuthorization(), TEST_URN.getEntityType(), Set.of(TEST_URN), - Set.of(Constants.STATUS_ASPECT_NAME)), + Set.of(STATUS_ASPECT_NAME)), Map.of(TEST_URN, responseStatusTrue), "Expected initial un-cached Status.removed=true result"); @@ -177,19 +178,111 @@ public void testBatchCache() throws RemoteInvocationException, URISyntaxExceptio TestOperationContexts.systemContextNoSearchAuthorization(), TEST_URN.getEntityType(), Set.of(TEST_URN), - Set.of(Constants.STATUS_ASPECT_NAME)), + Set.of(STATUS_ASPECT_NAME)), Map.of(TEST_URN, responseStatusTrue), "Expected CACHED Status.removed=true result"); verify(mockRestliClient, times(1)).sendRequest(any(Request.class)); } + @Test + public void testCacheNullValue() throws RemoteInvocationException, URISyntaxException { + Client mockRestliClient = mock(Client.class); + + // Test No Cache Config + EntityClientCacheConfig noCacheConfig = new EntityClientCacheConfig(); + noCacheConfig.setEnabled(true); + + SystemRestliEntityClient noCacheTest = + new SystemRestliEntityClient( + mockRestliClient, new ConstantBackoff(0), 0, noCacheConfig, 1, 2); + + com.linkedin.entity.EntityResponse responseStatusTrue = buildStatusResponse(true); + com.linkedin.entity.EntityResponse responseStatusFalse = buildStatusResponse(false); + + mockResponse(mockRestliClient, responseStatusTrue); + assertEquals( + noCacheTest.getV2( + TestOperationContexts.systemContextNoSearchAuthorization(), + TEST_URN.getEntityType(), + TEST_URN, + Set.of(STATUS_ASPECT_NAME)), + responseStatusTrue, + "Expected un-cached Status.removed=true result"); + + mockResponse(mockRestliClient, responseStatusFalse); + assertEquals( + noCacheTest.getV2( + TestOperationContexts.systemContextNoSearchAuthorization(), + TEST_URN.getEntityType(), + TEST_URN, + Set.of(STATUS_ASPECT_NAME)), + responseStatusFalse, + "Expected un-cached Status.removed=false result"); + + verify(mockRestliClient, times(2)).sendRequest(any(Request.class)); + + // Test Cache Config + reset(mockRestliClient); + + // Enable caching for MULTIPLE entity/aspect + EntityClientCacheConfig cacheConfig = new EntityClientCacheConfig(); + cacheConfig.setEnabled(true); + cacheConfig.setMaxBytes(100); + cacheConfig.setEntityAspectTTLSeconds( + Map.of( + TEST_URN.getEntityType(), + Map.of( + STATUS_ASPECT_NAME, 60, + DATASET_PROPERTIES_ASPECT_NAME, 60))); + + SystemRestliEntityClient cacheTest = + new SystemRestliEntityClient( + mockRestliClient, new ConstantBackoff(0), 0, cacheConfig, 1, 2); + + mockResponse(mockRestliClient, responseStatusTrue); + assertEquals( + cacheTest.getV2( + TestOperationContexts.systemContextNoSearchAuthorization(), + TEST_URN.getEntityType(), + TEST_URN, + Set.of(STATUS_ASPECT_NAME, DATASET_PROPERTIES_ASPECT_NAME)), + responseStatusTrue, + "Expected initial un-cached Status.removed=true result with no DatasetProperties (since it doesn't exist in this scenario)"); + + mockResponse(mockRestliClient, responseStatusFalse); + assertEquals( + cacheTest.getV2( + TestOperationContexts.systemContextNoSearchAuthorization(), + TEST_URN.getEntityType(), + TEST_URN, + Set.of(STATUS_ASPECT_NAME)), + responseStatusTrue, + "Expected CACHED Status.removed=true result with no DatasetProperties (since it doesn't exist in this scenario)"); + + verify(mockRestliClient, times(1)).sendRequest(any(Request.class)); + + // However in this scenario we DO expect a cached null + assertEquals( + cacheTest + .getEntityClientCache() + .getCache() + .get( + EntityClientCache.Key.builder() + .urn(TEST_URN) + .aspectName(DATASET_PROPERTIES_ASPECT_NAME) + .contextId("1379821641") + .build()), + new EntityClientCache.NullEnvelopedAspect(), + "Expected null object for the non-existent cache entry"); + } + private static com.linkedin.entity.EntityResponse buildStatusResponse(boolean value) { EnvelopedAspectMap aspects = new EnvelopedAspectMap(); aspects.put( - Constants.STATUS_ASPECT_NAME, + STATUS_ASPECT_NAME, new EnvelopedAspect() - .setName(Constants.STATUS_ASPECT_NAME) + .setName(STATUS_ASPECT_NAME) .setValue(new Aspect(new Status().setRemoved(value).data()))); return new com.linkedin.entity.EntityResponse() .setUrn(TEST_URN) diff --git a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/analytics/Analytics.java b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/analytics/Analytics.java index 753dd9b807fd12..9bbe1bb35fc654 100644 --- a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/analytics/Analytics.java +++ b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/analytics/Analytics.java @@ -73,17 +73,17 @@ public Task getTimeseriesStats( return RestliUtils.toTask( () -> { final Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_GET_TIMESERIES_STATS, entityName), authorizer, auth, true); + if (!AuthUtil.isAPIAuthorizedEntityType( - auth, - authorizer, + opContext, TIMESERIES, READ, entityName)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to get entity " + entityName); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), - ACTION_GET_TIMESERIES_STATS, entityName), authorizer, auth, true); log.info("Attempting to query timeseries stats"); GetTimeseriesAggregatedStatsResponse resp = new GetTimeseriesAggregatedStatsResponse(); diff --git a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/AspectResource.java b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/AspectResource.java index cbca464d569a83..69bfe288da7a7a 100644 --- a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/AspectResource.java +++ b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/AspectResource.java @@ -137,16 +137,17 @@ public Task get( () -> { Authentication auth = AuthenticationContext.getAuthentication(); - if (!isAPIAuthorizedEntityUrns( - auth, - _authorizer, + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + "authorizerChain", urn.getEntityType()), _authorizer, auth, true); + + if (!isAPIAuthorizedEntityUrns( + opContext, READ, List.of(urn))) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to get aspect for " + urn); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), "authorizerChain", urn.getEntityType()), _authorizer, auth, true); final VersionedAspect aspect = _entityService.getVersionedAspect(opContext, urn, aspectName, version); @@ -186,17 +187,18 @@ public Task getTimeseriesAspectValues( () -> { Authentication auth = AuthenticationContext.getAuthentication(); - if (!isAPIAuthorizedUrns( - auth, - _authorizer, + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_GET_TIMESERIES_ASPECT, urn.getEntityType()), _authorizer, auth, true); + + if (!isAPIAuthorizedUrns( + opContext, TIMESERIES, READ, List.of(urn))) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to get timeseries aspect for " + urn); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_GET_TIMESERIES_ASPECT, urn.getEntityType()), _authorizer, auth, true); GetTimeseriesAspectValuesResponse response = new GetTimeseriesAspectValuesResponse(); response.setEntityName(entityName); @@ -277,10 +279,11 @@ private Task ingestProposals( .map(MetadataChangeProposal::getEntityType) .collect(Collectors.toSet()); final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(authentication.getActor().toUrnStr(), getContext(), ACTION_INGEST_PROPOSAL, entityTypes), _authorizer, authentication, true); + systemOperationContext, RequestContext.builder().buildRestli(authentication.getActor().toUrnStr(), getContext(), + ACTION_INGEST_PROPOSAL, entityTypes), _authorizer, authentication, true); // Ingest Authorization Checks - List> exceptions = isAPIAuthorized(authentication, _authorizer, ENTITY, + List> exceptions = isAPIAuthorized(opContext, ENTITY, opContext.getEntityRegistry(), metadataChangeProposals) .stream().filter(p -> p.getSecond() != HttpStatus.S_200_OK.getCode()) .collect(Collectors.toList()); @@ -333,15 +336,16 @@ public Task getCount( () -> { Authentication authentication = AuthenticationContext.getAuthentication(); - if (!isAPIAuthorized( - authentication, - _authorizer, + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(authentication.getActor().toUrnStr(), + getContext(), ACTION_GET_COUNT), _authorizer, authentication, true); + + if (!isAPIAuthorized( + opContext, COUNTS, READ)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to get aspect counts."); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(authentication.getActor().toUrnStr(), getContext(), ACTION_GET_COUNT, List.of()), _authorizer, authentication, true); return _entityService.getCountAspect(opContext, aspectName, urnLike); }, @@ -362,9 +366,14 @@ public Task restoreIndices( @ActionParam("lePitEpochMs") @Optional @Nullable Long lePitEpochMs) { return RestliUtil.toTask( () -> { + + Authentication authentication = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(authentication.getActor().toUrnStr(), + getContext(), ACTION_RESTORE_INDICES), _authorizer, authentication, true); + if (!isAPIAuthorized( - AuthenticationContext.getAuthentication(), - _authorizer, + opContext, PoliciesConfig.RESTORE_INDICES_PRIVILEGE)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to update entities."); diff --git a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/BatchIngestionRunResource.java b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/BatchIngestionRunResource.java index 599bbf9ce4df60..69c789ceb2a3cd 100644 --- a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/BatchIngestionRunResource.java +++ b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/BatchIngestionRunResource.java @@ -87,15 +87,16 @@ public Task rollback( throws Exception { Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), "rollback", List.of()), authorizer, auth, true); + + if (!AuthUtil.isAPIAuthorized( - auth, - authorizer, + opContext, ENTITY, MANAGE)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to update entity"); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), "rollback", List.of()), authorizer, auth, true); log.info("ROLLBACK RUN runId: {} dry run: {}", runId, dryRun); @@ -163,15 +164,16 @@ public Task describe( () -> { Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + "describe", List.of()), authorizer, auth, true); + if (!AuthUtil.isAPIAuthorized( - auth, - authorizer, + opContext, ENTITY, READ)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to get entity"); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), "describe", List.of()), authorizer, auth, true); List summaries = systemMetadataService.findByRunId( diff --git a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/EntityResource.java b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/EntityResource.java index 8a5473da95ba2a..a8f127e52ee791 100644 --- a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/EntityResource.java +++ b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/EntityResource.java @@ -193,16 +193,17 @@ public Task get( final Urn urn = Urn.createFromString(urnStr); Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + "restrictedService", urn.getEntityType()), authorizer, auth, true); + if (!isAPIAuthorizedEntityUrns( - auth, - authorizer, + opContext, READ, List.of(urn))) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to get entity " + urn); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), "restrictedService", urn.getEntityType()), authorizer, auth, true); return RestliUtil.toTask( () -> { @@ -233,16 +234,17 @@ public Task> batchGet( } Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + "batchGet", urnStrs), authorizer, auth, true); + if (!isAPIAuthorizedEntityUrns( - auth, - authorizer, + opContext, READ, urns)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to get entities: " + urnStrs); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), "batchGet", urnStrs), authorizer, auth, true); return RestliUtil.toTask( () -> { @@ -269,16 +271,17 @@ public Task ingest( Authentication authentication = AuthenticationContext.getAuthentication(); String actorUrnStr = authentication.getActor().toUrnStr(); final Urn urn = com.datahub.util.ModelUtils.getUrnFromSnapshotUnion(entity.getValue()); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(authentication.getActor().toUrnStr(), getContext(), + ACTION_INGEST, urn.getEntityType()), authorizer, authentication, true); + if (!isAPIAuthorizedEntityUrns( - authentication, - authorizer, + opContext, CREATE, List.of(urn))) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to edit entity " + urn); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(authentication.getActor().toUrnStr(), getContext(), ACTION_INGEST, urn.getEntityType()), authorizer, authentication, true); try { validateOrThrow(entity); @@ -311,20 +314,20 @@ public Task batchIngest( Authentication authentication = AuthenticationContext.getAuthentication(); String actorUrnStr = authentication.getActor().toUrnStr(); - List urns = Arrays.stream(entities) .map(Entity::getValue) .map(com.datahub.util.ModelUtils::getUrnFromSnapshotUnion).collect(Collectors.toList()); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(authentication.getActor().toUrnStr(), + getContext(), ACTION_BATCH_INGEST, urns.stream().map(Urn::getEntityType).collect(Collectors.toList())), + authorizer, authentication, true); + if (!isAPIAuthorizedEntityUrns( - authentication, - authorizer, + opContext, CREATE, urns)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to edit entities."); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(authentication.getActor().toUrnStr(), getContext(), ACTION_BATCH_INGEST, urns.stream() - .map(Urn::getEntityType).collect(Collectors.toList())), authorizer, authentication, true); for (Entity entity : entities) { try { @@ -374,19 +377,19 @@ public Task search( @Optional @Nullable @ActionParam(PARAM_SEARCH_FLAGS) SearchFlags searchFlags) { final Authentication auth = AuthenticationContext.getAuthentication(); + OperationContext opContext = OperationContext.asSession(systemOperationContext, + RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_SEARCH, entityName), authorizer, auth, true) + .withSearchFlags(flags -> searchFlags != null ? searchFlags : new SearchFlags().setFulltext(Boolean.TRUE.equals(fulltext))); + + if (!AuthUtil.isAPIAuthorizedEntityType( - auth, - authorizer, + opContext, READ, entityName)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to search."); } - OperationContext opContext = OperationContext.asSession(systemOperationContext, - RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_SEARCH, entityName), authorizer, auth, true) - .withSearchFlags(flags -> searchFlags != null ? searchFlags : new SearchFlags().setFulltext(Boolean.TRUE.equals(fulltext))); - List sortCriterionList = getSortCriteria(sortCriteria, sortCriterion); log.info("GET SEARCH RESULTS for {} with query {}", entityName, input); @@ -400,8 +403,7 @@ public Task search( List.of(entityName), input, filter, sortCriterionList, start, count); if (!isAPIAuthorizedResult( - auth, - authorizer, + opContext, result)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized get entity."); @@ -427,13 +429,13 @@ public Task searchAcrossEntities( final Authentication auth = AuthenticationContext.getAuthentication(); OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_SEARCH_ACROSS_ENTITIES, entities), authorizer, auth, true) + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_SEARCH_ACROSS_ENTITIES, entities), authorizer, auth, true) .withSearchFlags(flags -> searchFlags != null ? searchFlags : new SearchFlags().setFulltext(true)); List entityList = searchService.getEntitiesToSearch(opContext, entities == null ? Collections.emptyList() : Arrays.asList(entities), count); if (!isAPIAuthorizedEntityType( - auth, - authorizer, + opContext, READ, entityList)) { throw new RestLiServiceException( @@ -447,8 +449,7 @@ public Task searchAcrossEntities( () -> { SearchResult result = searchService.searchAcrossEntities(opContext, entityList, input, filter, sortCriterionList, start, count); if (!isAPIAuthorizedResult( - auth, - authorizer, + opContext, result)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized get entity."); @@ -486,13 +487,13 @@ public Task scrollAcrossEntities( final Authentication auth = AuthenticationContext.getAuthentication(); OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_SCROLL_ACROSS_ENTITIES, entities), authorizer, auth, true) + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_SCROLL_ACROSS_ENTITIES, entities), authorizer, auth, true) .withSearchFlags(flags -> searchFlags != null ? searchFlags : new SearchFlags().setFulltext(true)); List entityList = searchService.getEntitiesToSearch(opContext, entities == null ? Collections.emptyList() : Arrays.asList(entities), count); if (!isAPIAuthorizedEntityType( - auth, - authorizer, + opContext, READ, entityList)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to search."); @@ -518,8 +519,7 @@ public Task scrollAcrossEntities( keepAlive, count); if (!isAPIAuthorizedResult( - auth, - authorizer, + opContext, result)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized get entity."); @@ -549,9 +549,16 @@ public Task searchAcrossLineage( @Optional @Nullable @ActionParam(PARAM_SEARCH_FLAGS) SearchFlags searchFlags) throws URISyntaxException { final Authentication auth = AuthenticationContext.getAuthentication(); + OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_SEARCH_ACROSS_LINEAGE, entities), authorizer, auth, true) + .withSearchFlags(flags -> (searchFlags != null ? searchFlags : new SearchFlags().setFulltext(true)) + .setIncludeRestricted(true)) + .withLineageFlags(flags -> flags.setStartTimeMillis(startTimeMillis, SetMode.REMOVE_IF_NULL) + .setEndTimeMillis(endTimeMillis, SetMode.REMOVE_IF_NULL)); + if (!isAPIAuthorized( - auth, - authorizer, + opContext, LINEAGE, READ)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to search."); @@ -559,13 +566,6 @@ public Task searchAcrossLineage( List sortCriterionList = getSortCriteria(sortCriteria, sortCriterion); - OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_SEARCH_ACROSS_LINEAGE, entities), authorizer, auth, true) - .withSearchFlags(flags -> (searchFlags != null ? searchFlags : new SearchFlags().setFulltext(true)) - .setIncludeRestricted(true)) - .withLineageFlags(flags -> flags.setStartTimeMillis(startTimeMillis, SetMode.REMOVE_IF_NULL) - .setEndTimeMillis(endTimeMillis, SetMode.REMOVE_IF_NULL)); - Urn urn = Urn.createFromString(urnStr); List entityList = entities == null ? Collections.emptyList() : Arrays.asList(entities); log.info( @@ -611,22 +611,21 @@ public Task scrollAcrossLineage( throws URISyntaxException { final Authentication auth = AuthenticationContext.getAuthentication(); - if (!isAPIAuthorized( - auth, - authorizer, - LINEAGE, READ)) { - throw new RestLiServiceException( - HttpStatus.S_403_FORBIDDEN, "User is unauthorized to search."); - } - OperationContext opContext = OperationContext.asSession( systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_SCROLL_ACROSS_LINEAGE, entities), - authorizer, auth, true) + authorizer, auth, true) .withSearchFlags(flags -> (searchFlags != null ? searchFlags : new SearchFlags().setSkipCache(true)) .setIncludeRestricted(true)) .withLineageFlags(flags -> flags.setStartTimeMillis(startTimeMillis, SetMode.REMOVE_IF_NULL) .setEndTimeMillis(endTimeMillis, SetMode.REMOVE_IF_NULL)); + if (!isAPIAuthorized( + opContext, + LINEAGE, READ)) { + throw new RestLiServiceException( + HttpStatus.S_403_FORBIDDEN, "User is unauthorized to search."); + } + Urn urn = Urn.createFromString(urnStr); List entityList = entities == null ? Collections.emptyList() : Arrays.asList(entities); log.info( @@ -669,18 +668,18 @@ public Task list( @ActionParam(PARAM_COUNT) int count) { final Authentication auth = AuthenticationContext.getAuthentication(); + OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_LIST, entityName), authorizer, auth, true) + .withSearchFlags(flags -> new SearchFlags().setFulltext(false)); + if (!AuthUtil.isAPIAuthorizedEntityType( - auth, - authorizer, + opContext, READ, entityName)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to search."); } - OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_LIST, entityName), authorizer, auth, true) - .withSearchFlags(flags -> new SearchFlags().setFulltext(false)); - List sortCriterionList = getSortCriteria(sortCriteria, sortCriterion); log.info("GET LIST RESULTS for {} with filter {}", entityName, filter); @@ -688,8 +687,7 @@ public Task list( () -> { SearchResult result = entitySearchService.filter(opContext, entityName, filter, sortCriterionList, start, count); if (!AuthUtil.isAPIAuthorizedResult( - auth, - authorizer, + opContext, result)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized get entity."); @@ -712,24 +710,23 @@ public Task autocomplete( @ActionParam(PARAM_SEARCH_FLAGS) @Optional @Nullable SearchFlags searchFlags) { final Authentication auth = AuthenticationContext.getAuthentication(); + OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_AUTOCOMPLETE, entityName), authorizer, auth, true) + .withSearchFlags(flags -> searchFlags != null ? searchFlags : flags); + if (!AuthUtil.isAPIAuthorizedEntityType( - auth, - authorizer, + opContext, READ, entityName)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to search."); } - OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_AUTOCOMPLETE, entityName), authorizer, auth, true) - .withSearchFlags(flags -> searchFlags != null ? searchFlags : flags); - return RestliUtil.toTask( () -> { AutoCompleteResult result = entitySearchService.autoComplete(opContext, entityName, query, field, filter, limit); if (!isAPIAuthorizedResult( - auth, - authorizer, + opContext, result)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized get entity."); @@ -750,25 +747,24 @@ public Task browse( @ActionParam(PARAM_SEARCH_FLAGS) @Optional @Nullable SearchFlags searchFlags) { final Authentication auth = AuthenticationContext.getAuthentication(); + OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_BROWSE, entityName), authorizer, auth, true) + .withSearchFlags(flags -> searchFlags != null ? searchFlags : flags); + if (!AuthUtil.isAPIAuthorizedEntityType( - auth, - authorizer, + opContext, READ, entityName)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to search."); } - OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_BROWSE, entityName), authorizer, auth, true) - .withSearchFlags(flags -> searchFlags != null ? searchFlags : flags); - log.info("GET BROWSE RESULTS for {} at path {}", entityName, path); return RestliUtil.toTask( () -> { BrowseResult result = entitySearchService.browse(opContext, entityName, path, filter, start, limit); if (!isAPIAuthorizedResult( - auth, - authorizer, + opContext, result)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized get entity."); @@ -787,9 +783,12 @@ public Task getBrowsePaths( @ActionParam(value = PARAM_URN, typeref = com.linkedin.common.Urn.class) @Nonnull Urn urn) { final Authentication auth = AuthenticationContext.getAuthentication(); + OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_GET_BROWSE_PATHS, urn.getEntityType()), authorizer, auth, true); + if (!isAPIAuthorizedEntityUrns( - auth, - authorizer, + opContext, READ, List.of(urn))) { throw new RestLiServiceException( @@ -797,9 +796,6 @@ public Task getBrowsePaths( } log.info("GET BROWSE PATHS for {}", urn); - OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_GET_BROWSE_PATHS, urn.getEntityType()), authorizer, auth, true); - return RestliUtil.toTask( () -> new StringArray(entitySearchService.getBrowsePaths(opContext, urnToEntityName(urn), urn)), MetricRegistry.name(this.getClass(), "getBrowsePaths")); @@ -858,16 +854,17 @@ public Task deleteEntities( .keySet(); final Authentication auth = AuthenticationContext.getAuthentication(); + OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + "deleteAll", urns), authorizer, auth, true); + if (!isAPIAuthorizedEntityUrns( - auth, - authorizer, + opContext, DELETE, urns.stream().map(UrnUtils::getUrn).collect(Collectors.toSet()))) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to delete entities."); } - OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), "deleteAll", urns), authorizer, auth, true); response.setEntitiesAffected(urns.size()); response.setEntitiesDeleted( @@ -909,16 +906,17 @@ public Task deleteEntity( Urn urn = Urn.createFromString(urnStr); final Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_DELETE, urn.getEntityType()), authorizer, auth, true); + if (!isAPIAuthorizedEntityUrns( - auth, - authorizer, + opContext, DELETE, List.of(urn))) { throw new RestLiServiceException( - HttpStatus.S_403_FORBIDDEN, "User is unauthorized to delete entity: " + urnStr); + HttpStatus.S_403_FORBIDDEN, "User is unauthorized to delete entity: " + urnStr); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_DELETE, urn.getEntityType()), authorizer, auth, true); return RestliUtil.toTask( () -> { @@ -971,16 +969,17 @@ private Long deleteTimeseriesAspects( long totalNumberOfDocsDeleted = 0; final Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + "deleteTimeseriesAspects", urn.getEntityType()), authorizer, auth, true); + if (!isAPIAuthorizedUrns( - auth, - authorizer, + opContext, TIMESERIES, DELETE, List.of(urn))) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to delete entity " + urn); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), "deleteTimeseriesAspects", urn.getEntityType()), authorizer, auth, true); // Construct the filter. List criteria = new ArrayList<>(); @@ -1027,16 +1026,17 @@ public Task deleteReferencesTo( Urn urn = Urn.createFromString(urnStr); final Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + "deleteReferences", urn.getEntityType()), authorizer, auth, true); + if (!isAPIAuthorizedEntityUrns( - auth, - authorizer, + opContext, DELETE, List.of(urn))) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to delete entity " + urnStr); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), "deleteReferences", urn.getEntityType()), authorizer, auth, true); return RestliUtil.toTask( () -> deleteEntityService.deleteReferencesTo(opContext, urn, dryRun), @@ -1052,9 +1052,13 @@ public Task deleteReferencesTo( public Task setWriteable( @ActionParam(PARAM_VALUE) @Optional("true") @Nonnull Boolean value) { + final Authentication auth = AuthenticationContext.getAuthentication(); + OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + "setWriteable"), authorizer, auth, true); + if (!isAPIAuthorized( - AuthenticationContext.getAuthentication(), - authorizer, + opContext, PoliciesConfig.SET_WRITEABLE_PRIVILEGE)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to enable and disable write mode."); @@ -1073,15 +1077,17 @@ public Task setWriteable( public Task getTotalEntityCount(@ActionParam(PARAM_ENTITY) @Nonnull String entityName) { final Authentication auth = AuthenticationContext.getAuthentication(); + OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + "getTotalEntityCount", entityName), authorizer, auth, true); + if (!isAPIAuthorized( - auth, - authorizer, + opContext, COUNTS, READ)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to get entity counts."); } - OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), "getTotalEntityCount", entityName), authorizer, auth, true); + return RestliUtil.toTask(() -> entitySearchService.docCount(opContext, entityName)); } @@ -1091,15 +1097,17 @@ public Task getTotalEntityCount(@ActionParam(PARAM_ENTITY) @Nonnull String public Task batchGetTotalEntityCount( @ActionParam(PARAM_ENTITIES) @Nonnull String[] entityNames) { final Authentication auth = AuthenticationContext.getAuthentication(); + OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + "batchGetTotalEntityCount", entityNames), authorizer, auth, true); + if (!isAPIAuthorized( - auth, - authorizer, + opContext, COUNTS, READ)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to get entity counts."); } - OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), "batchGetTotalEntityCount", entityNames), authorizer, auth, true); + return RestliUtil.toTask( () -> new LongMap(searchService.docCountPerEntity(opContext, Arrays.asList(entityNames)))); } @@ -1114,22 +1122,22 @@ public Task listUrns( throws URISyntaxException { final Authentication auth = AuthenticationContext.getAuthentication(); + OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_LIST_URNS, entityName), authorizer, auth, true); + if (!AuthUtil.isAPIAuthorizedEntityType( - auth, - authorizer, + opContext, READ, entityName)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to search."); } - OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_LIST_URNS, entityName), authorizer, auth, true); log.info("LIST URNS for {} with start {} and count {}", entityName, start, count); return RestliUtil.toTask(() -> { ListUrnsResult result = entityService.listUrns(opContext, entityName, start, count); if (!isAPIAuthorizedEntityUrns( - auth, - authorizer, + opContext, READ, result.getEntities())) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to get entity counts."); @@ -1155,16 +1163,17 @@ public Task applyRetention( } final Authentication auth = AuthenticationContext.getAuthentication(); + OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_APPLY_RETENTION, resourceSpec.getType()), authorizer, auth, true); + if (!isAPIAuthorized( - auth, - authorizer, + opContext, PoliciesConfig.APPLY_RETENTION_PRIVILEGE, resourceSpec)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to apply retention."); } - OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_APPLY_RETENTION, resourceSpec.getType()), authorizer, auth, true); return RestliUtil.toTask( () -> entityService.batchApplyRetention(opContext, start, count, attemptWithVersion, aspectName, urn), @@ -1183,24 +1192,25 @@ public Task filter( @ActionParam(PARAM_COUNT) int count) { final Authentication auth = AuthenticationContext.getAuthentication(); + OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_FILTER, entityName), authorizer, auth, true); + if (!AuthUtil.isAPIAuthorizedEntityType( - auth, - authorizer, + opContext, READ, entityName)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to search."); } - OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_FILTER, entityName), authorizer, auth, true); List sortCriterionList = getSortCriteria(sortCriteria, sortCriterion); log.info("FILTER RESULTS for {} with filter {}", entityName, filter); return RestliUtil.toTask( () -> { - SearchResult result = entitySearchService.filter(opContext.withSearchFlags(flags -> flags.setFulltext(true)), entityName, filter, sortCriterionList, start, count); + SearchResult result = entitySearchService.filter(opContext.withSearchFlags(flags -> flags.setFulltext(true)), + entityName, filter, sortCriterionList, start, count); if (!isAPIAuthorizedResult( - auth, - authorizer, + opContext, result)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to get entity counts."); @@ -1219,16 +1229,17 @@ public Task exists(@ActionParam(PARAM_URN) @Nonnull String urnStr, @Act Urn urn = UrnUtils.getUrn(urnStr); final Authentication auth = AuthenticationContext.getAuthentication(); + OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_EXISTS, urn.getEntityType()), authorizer, auth, true); if (!isAPIAuthorizedEntityUrns( - auth, - authorizer, + opContext, EXISTS, List.of(urn))) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized check entity existence: " + urnStr); } - OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_EXISTS, urn.getEntityType()), authorizer, auth, true); + log.info("EXISTS for {}", urnStr); final boolean includeRemoved = includeSoftDelete == null || includeSoftDelete; return RestliUtil.toTask( diff --git a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/EntityV2Resource.java b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/EntityV2Resource.java index 9052f0240266ad..1afe062ce5c5f4 100644 --- a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/EntityV2Resource.java +++ b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/EntityV2Resource.java @@ -70,16 +70,17 @@ public Task get( final Urn urn = Urn.createFromString(urnStr); final Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + "getEntityV2", urn.getEntityType()), _authorizer, auth, true); + if (!isAPIAuthorizedEntityUrns( - auth, - _authorizer, + opContext, READ, List.of(urn))) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to get entity " + urn); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), "getEntityV2", urn.getEntityType()), _authorizer, auth, true); return RestliUtil.toTask( () -> { @@ -114,16 +115,17 @@ public Task> batchGet( } final Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + "getEntityV2", urns.stream().map(Urn::getEntityType).collect(Collectors.toList())), _authorizer, auth, true); + if (!isAPIAuthorizedEntityUrns( - auth, - _authorizer, + opContext, READ, urns)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to get entities " + urnStrs); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), "getEntityV2", urns.stream().map(Urn::getEntityType).collect(Collectors.toList())), _authorizer, auth, true); if (urns.size() <= 0) { return Task.value(Collections.emptyMap()); diff --git a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/EntityVersionedV2Resource.java b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/EntityVersionedV2Resource.java index d6c91ba7dcaa35..d253ef69680339 100644 --- a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/EntityVersionedV2Resource.java +++ b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/entity/EntityVersionedV2Resource.java @@ -78,18 +78,19 @@ public Task> batchGetVersioned( .map(versionedUrn -> UrnUtils.getUrn(versionedUrn.getUrn())).collect(Collectors.toSet()); Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), "authorizerChain", urns.stream() + .map(Urn::getEntityType).collect(Collectors.toList())), _authorizer, auth, true); + if (!isAPIAuthorizedEntityUrns( - auth, - _authorizer, + opContext, READ, urns)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to get entities " + versionedUrnStrs); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), "authorizerChain", urns.stream() - .map(Urn::getEntityType).collect(Collectors.toList())), _authorizer, auth, true); + log.debug("BATCH GET VERSIONED V2 {}", versionedUrnStrs); if (versionedUrnStrs.size() <= 0) { diff --git a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/lineage/Relationships.java b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/lineage/Relationships.java index d04efcaa85e49f..ba8baacdd9c920 100644 --- a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/lineage/Relationships.java +++ b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/lineage/Relationships.java @@ -13,6 +13,7 @@ import static com.linkedin.metadata.search.utils.QueryUtils.newRelationshipFilter; import com.codahale.metrics.MetricRegistry; +import com.datahub.authentication.Authentication; import com.datahub.authentication.AuthenticationContext; import com.datahub.authorization.EntitySpec; import com.datahub.plugins.auth.authorization.Authorizer; @@ -40,6 +41,8 @@ import com.linkedin.restli.server.annotations.RestLiSimpleResource; import com.linkedin.restli.server.annotations.RestMethod; import com.linkedin.restli.server.resources.SimpleResourceTemplate; +import io.datahubproject.metadata.context.OperationContext; +import io.datahubproject.metadata.context.RequestContext; import io.opentelemetry.extension.annotations.WithSpan; import java.net.URISyntaxException; import java.util.Arrays; @@ -70,6 +73,10 @@ public final class Relationships extends SimpleResourceTemplate get( @QueryParam("count") @Optional @Nullable Integer count) { Urn urn = UrnUtils.getUrn(rawUrn); + final Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + "getRelationships", urn.getEntityType()), _authorizer, auth, true); + if (!isAPIAuthorizedUrns( - AuthenticationContext.getAuthentication(), - _authorizer, + opContext, LINEAGE, READ, List.of(urn))) { throw new RestLiServiceException( @@ -162,9 +173,13 @@ public Task get( public UpdateResponse delete(@QueryParam("urn") @Nonnull String rawUrn) throws Exception { Urn urn = Urn.createFromString(rawUrn); + final Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + "deleteRelationships", urn.getEntityType()), _authorizer, auth, true); + if (!isAPIAuthorizedUrns( - AuthenticationContext.getAuthentication(), - _authorizer, + opContext, LINEAGE, DELETE, List.of(urn))) { throw new RestLiServiceException( @@ -187,9 +202,13 @@ public Task getLineage( log.info("GET LINEAGE {} {} {} {} {}", urnStr, direction, start, count, maxHops); final Urn urn = Urn.createFromString(urnStr); + final Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + "getLineage", urn.getEntityType()), _authorizer, auth, true); + if (!isAPIAuthorizedUrns( - AuthenticationContext.getAuthentication(), - _authorizer, + opContext, LINEAGE, READ, List.of(urn))) { throw new RestLiServiceException( diff --git a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/operations/OperationsResource.java b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/operations/OperationsResource.java index 42d0bf11c505d8..5f4bb46ff626d1 100644 --- a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/operations/OperationsResource.java +++ b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/operations/OperationsResource.java @@ -91,7 +91,7 @@ public OperationsResource() {} OperationsResource(OperationContext systemOperationContext, TimeseriesAspectService timeseriesAspectService) { this._timeseriesAspectService = timeseriesAspectService; this.systemOperationContext = systemOperationContext; - this._authorizer = systemOperationContext.getAuthorizerContext().getAuthorizer(); + this._authorizer = systemOperationContext.getAuthorizationContext().getAuthorizer(); } @Action(name = ACTION_RESTORE_INDICES) @@ -134,9 +134,13 @@ public Task getTaskStatus( return RestliUtil.toTask( () -> { + final Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_GET_ES_TASK_STATUS), _authorizer, auth, true); + if (!isAPIAuthorized( - AuthenticationContext.getAuthentication(), - _authorizer, + opContext, PoliciesConfig.GET_ES_TASK_STATUS_PRIVILEGE)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to get ES task status"); @@ -194,15 +198,16 @@ public Task getIndexSizes() { () -> { final Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_GET_INDEX_SIZES, List.of()), _authorizer, auth, true); + if (!isAPIAuthorized( - AuthenticationContext.getAuthentication(), - _authorizer, + opContext, PoliciesConfig.GET_TIMESERIES_INDEX_SIZES_PRIVILEGE)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to get index sizes."); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_GET_INDEX_SIZES, List.of()), _authorizer, auth, true); TimeseriesIndicesSizesResult result = new TimeseriesIndicesSizesResult(); result.setIndexSizes( @@ -224,15 +229,16 @@ String executeTruncateTimeseriesAspect( @Nullable Boolean forceReindex) { final Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + "executeTruncateTimeseriesAspect", entityType), _authorizer, auth, true); + if (!isAPIAuthorized( - auth, - _authorizer, + opContext, PoliciesConfig.TRUNCATE_TIMESERIES_INDEX_PRIVILEGE)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to truncate timeseries index"); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), "executeTruncateTimeseriesAspect", entityType), _authorizer, auth, true); if (forceDeleteByQuery != null && forceDeleteByQuery.equals(forceReindex)) { return "please only set forceReindex OR forceDeleteByQuery flags"; diff --git a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/operations/Utils.java b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/operations/Utils.java index 54c1029edcab04..734fe0cd606d98 100644 --- a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/operations/Utils.java +++ b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/operations/Utils.java @@ -52,16 +52,17 @@ public static String restoreIndices( resourceSpec = new EntitySpec(resource.getEntityType(), resource.toString()); } final Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), resourceContext, + "restoreIndices", List.of()), authorizer, auth, true); + if (!isAPIAuthorized( - auth, - authorizer, + opContext, PoliciesConfig.RESTORE_INDICES_PRIVILEGE, resourceSpec)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to restore indices."); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), resourceContext, "restoreIndices", List.of()), authorizer, auth, true); RestoreIndicesArgs args = new RestoreIndicesArgs() diff --git a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/platform/PlatformResource.java b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/platform/PlatformResource.java index 5b2f19c661dabc..986783e6359f2f 100644 --- a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/platform/PlatformResource.java +++ b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/platform/PlatformResource.java @@ -2,8 +2,10 @@ import static com.datahub.authorization.AuthUtil.isAPIAuthorized; +import com.datahub.authentication.Authentication; import com.datahub.authentication.AuthenticationContext; import com.datahub.plugins.auth.authorization.Authorizer; +import com.linkedin.common.urn.Urn; import com.linkedin.entity.Entity; import com.linkedin.metadata.authorization.Disjunctive; import com.linkedin.metadata.authorization.PoliciesConfig; @@ -18,12 +20,16 @@ import com.linkedin.restli.server.annotations.Optional; import com.linkedin.restli.server.annotations.RestLiCollection; import com.linkedin.restli.server.resources.CollectionResourceTaskTemplate; +import io.datahubproject.metadata.context.OperationContext; +import io.datahubproject.metadata.context.RequestContext; import io.opentelemetry.extension.annotations.WithSpan; import javax.annotation.Nonnull; import javax.inject.Inject; import javax.inject.Named; import lombok.extern.slf4j.Slf4j; +import java.util.stream.Collectors; + /** DataHub Platform Actions */ @Slf4j @RestLiCollection(name = "platform", namespace = "com.linkedin.platform") @@ -39,6 +45,10 @@ public class PlatformResource extends CollectionResourceTaskTemplate producePlatformEvent( @ActionParam("key") @Optional String key, @ActionParam("event") @Nonnull PlatformEvent event) { + final Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_PRODUCE_PLATFORM_EVENT), _authorizer, + auth, true); + if (!isAPIAuthorized( - AuthenticationContext.getAuthentication(), - _authorizer, + opContext, PoliciesConfig.PRODUCE_PLATFORM_EVENT_PRIVILEGE)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to produce platform events."); diff --git a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/usage/UsageStats.java b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/usage/UsageStats.java index 1b003fec82e8b8..8cf1b07d971d93 100644 --- a/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/usage/UsageStats.java +++ b/metadata-service/restli-servlet-impl/src/main/java/com/linkedin/metadata/resources/usage/UsageStats.java @@ -105,17 +105,18 @@ public Task batchIngest(@ActionParam(PARAM_BUCKETS) @Nonnull UsageAggregat final Authentication auth = AuthenticationContext.getAuthentication(); Set urns = Arrays.stream(buckets).sequential().map(UsageAggregation::getResource).collect(Collectors.toSet()); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_BATCH_INGEST, urns.stream().map(Urn::getEntityType).collect(Collectors.toList())), _authorizer, + auth, true); + if (!isAPIAuthorizedEntityUrns( - auth, - _authorizer, + opContext, UPDATE, urns)) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to edit entities."); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_BATCH_INGEST, urns.stream() - .map(Urn::getEntityType).collect(Collectors.toList())), _authorizer, auth, true); for (UsageAggregation agg : buckets) { this.ingest(opContext, agg); @@ -144,16 +145,17 @@ public Task query( Urn resourceUrn = UrnUtils.getUrn(resource); final Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_QUERY, resourceUrn.getEntityType()), _authorizer, auth, true); + if (!isAPIAuthorized( - auth, - _authorizer, + opContext, PoliciesConfig.VIEW_DATASET_USAGE_PRIVILEGE, new EntitySpec(resourceUrn.getEntityType(), resourceUrn.toString()))) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to query usage."); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_QUERY, resourceUrn.getEntityType()), _authorizer, auth, true); return UsageServiceUtil.query(opContext, _timeseriesAspectService, resource, duration, startTime, endTime, maxBuckets); }, @@ -170,18 +172,19 @@ public Task queryRange( Urn resourceUrn = UrnUtils.getUrn(resource); final Authentication auth = AuthenticationContext.getAuthentication(); + final OperationContext opContext = OperationContext.asSession( + systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), + ACTION_QUERY_RANGE, resourceUrn.getEntityType()), _authorizer, auth, true); + + if (!isAPIAuthorized( - auth, - _authorizer, + opContext, PoliciesConfig.VIEW_DATASET_USAGE_PRIVILEGE, new EntitySpec(resourceUrn.getEntityType(), resourceUrn.toString()))) { throw new RestLiServiceException( HttpStatus.S_403_FORBIDDEN, "User is unauthorized to query usage."); } - final OperationContext opContext = OperationContext.asSession( - systemOperationContext, RequestContext.builder().buildRestli(auth.getActor().toUrnStr(), getContext(), ACTION_QUERY_RANGE, resourceUrn.getEntityType()), _authorizer, auth, true); - return RestliUtil.toTask( () -> UsageServiceUtil.queryRange(opContext, _timeseriesAspectService, resource, duration, range), MetricRegistry.name(this.getClass(), "queryRange")); } diff --git a/metadata-service/services/src/main/java/com/linkedin/metadata/service/RollbackService.java b/metadata-service/services/src/main/java/com/linkedin/metadata/service/RollbackService.java index 403665120c6868..7c5a17c91b95e8 100644 --- a/metadata-service/services/src/main/java/com/linkedin/metadata/service/RollbackService.java +++ b/metadata-service/services/src/main/java/com/linkedin/metadata/service/RollbackService.java @@ -3,7 +3,6 @@ import static com.linkedin.metadata.Constants.DEFAULT_RUN_ID; import static com.linkedin.metadata.authorization.ApiOperation.DELETE; -import com.datahub.authentication.Authentication; import com.datahub.authentication.AuthenticationException; import com.datahub.authorization.AuthUtil; import com.datahub.plugins.auth.authorization.Authorizer; @@ -78,7 +77,7 @@ public RollbackResponse rollbackIngestion( } List aspectRowsToDelete = rollbackTargetAspects(runId, hardDelete); - if (!isAuthorized(authorizer, aspectRowsToDelete, opContext.getSessionAuthentication())) { + if (!isAuthorized(opContext, aspectRowsToDelete)) { throw new AuthenticationException("User is NOT unauthorized to delete entities."); } @@ -287,13 +286,10 @@ public void updateExecutionRequestStatus( } private boolean isAuthorized( - final Authorizer authorizer, - @Nonnull List rowSummaries, - @Nonnull Authentication authentication) { + @Nonnull OperationContext opContext, @Nonnull List rowSummaries) { return AuthUtil.isAPIAuthorizedEntityUrns( - authentication, - authorizer, + opContext, DELETE, rowSummaries.stream() .map(AspectRowSummary::getUrn)