fix(businessAttribute): add businessAttribute-dataset missing permission (#12650)

This commit is contained in:
Deepak Garg 2025-02-19 20:12:45 +05:30 committed by GitHub
parent bed7cfb298
commit b8987f2769
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 137 additions and 21 deletions

View File

@ -29,6 +29,7 @@ dependencies {
testImplementation externalDependency.mockito
testImplementation externalDependency.testng
testImplementation externalDependency.mockitoInline
}
graphqlCodegen {

View File

@ -2,6 +2,7 @@ package com.linkedin.datahub.graphql.resolvers.businessattribute;
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.bindArgument;
import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.buildMetadataChangeProposalWithUrn;
import static com.linkedin.datahub.graphql.resolvers.mutate.util.BusinessAttributeUtils.validateInputResources;
import static com.linkedin.metadata.Constants.BUSINESS_ATTRIBUTE_ASPECT;
import com.linkedin.businessattribute.BusinessAttributeAssociation;
@ -43,6 +44,7 @@ public class AddBusinessAttributeResolver implements DataFetcher<CompletableFutu
return GraphQLConcurrencyUtils.supplyAsync(
() -> {
try {
validateInputResources(resourceRefInputs, context);
addBusinessAttributeToResource(
context.getOperationContext(),
businessAttributeUrn,

View File

@ -1,11 +1,16 @@
package com.linkedin.datahub.graphql.resolvers.businessattribute;
import static com.linkedin.datahub.graphql.authorization.AuthorizationUtils.ALL_PRIVILEGES_GROUP;
import com.datahub.authorization.AuthUtil;
import com.datahub.authorization.ConjunctivePrivilegeGroup;
import com.datahub.authorization.DisjunctivePrivilegeGroup;
import com.google.common.collect.ImmutableList;
import com.linkedin.common.urn.Urn;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
import com.linkedin.metadata.authorization.PoliciesConfig;
import java.net.URISyntaxException;
import javax.annotation.Nonnull;
public class BusinessAttributeAuthorizationUtils {
@ -32,4 +37,20 @@ public class BusinessAttributeAuthorizationUtils {
PoliciesConfig.MANAGE_BUSINESS_ATTRIBUTE_PRIVILEGE.getType()))));
return AuthUtil.isAuthorized(context.getOperationContext(), orPrivilegeGroups, null);
}
public static boolean isAuthorizedToEditBusinessAttribute(
@Nonnull QueryContext context, String targetUrn) throws URISyntaxException {
Urn schemaFieldUrn = Urn.createFromString(targetUrn);
Urn datasetUrn = schemaFieldUrn.getIdAsUrn();
final DisjunctivePrivilegeGroup orPrivilegeGroups =
new DisjunctivePrivilegeGroup(
ImmutableList.of(
ALL_PRIVILEGES_GROUP,
new ConjunctivePrivilegeGroup(
ImmutableList.of(
PoliciesConfig.EDIT_DATASET_COL_BUSINESS_ATTRIBUTE_PRIVILEGE.getType()))));
return AuthorizationUtils.isAuthorized(
context, datasetUrn.getEntityType(), datasetUrn.toString(), orPrivilegeGroups);
}
}

View File

@ -2,6 +2,7 @@ package com.linkedin.datahub.graphql.resolvers.businessattribute;
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.bindArgument;
import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.buildMetadataChangeProposalWithUrn;
import static com.linkedin.datahub.graphql.resolvers.mutate.util.BusinessAttributeUtils.validateInputResources;
import static com.linkedin.metadata.Constants.BUSINESS_ATTRIBUTE_ASPECT;
import com.linkedin.businessattribute.BusinessAttributes;
@ -40,6 +41,7 @@ public class RemoveBusinessAttributeResolver implements DataFetcher<CompletableF
return GraphQLConcurrencyUtils.supplyAsync(
() -> {
try {
validateInputResources(resourceRefInputs, context);
removeBusinessAttribute(
context.getOperationContext(),
resourceRefInputs,

View File

@ -1,8 +1,11 @@
package com.linkedin.datahub.graphql.resolvers.mutate.util;
import static com.linkedin.datahub.graphql.resolvers.businessattribute.BusinessAttributeAuthorizationUtils.isAuthorizedToEditBusinessAttribute;
import static com.linkedin.metadata.utils.CriterionUtils.buildCriterion;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.query.filter.Condition;
@ -23,6 +26,8 @@ import com.linkedin.schema.NumberType;
import com.linkedin.schema.SchemaFieldDataType;
import com.linkedin.schema.StringType;
import com.linkedin.schema.TimeType;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Objects;
import javax.annotation.Nonnull;
import lombok.extern.slf4j.Slf4j;
@ -105,4 +110,14 @@ public class BusinessAttributeUtils {
return null;
}
}
public static void validateInputResources(List<ResourceRefInput> resources, QueryContext context)
throws URISyntaxException {
for (ResourceRefInput resource : resources) {
if (!isAuthorizedToEditBusinessAttribute(context, resource.getResourceUrn())) {
throw new AuthorizationException(
"Unauthorized to perform this action. Please contact your DataHub administrator.");
}
}
}
}

View File

@ -4,24 +4,30 @@ import static com.linkedin.datahub.graphql.TestUtils.getMockAllowContext;
import static com.linkedin.datahub.graphql.TestUtils.getMockEntityService;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mockStatic;
import static org.testng.Assert.assertThrows;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.expectThrows;
import com.google.common.collect.ImmutableList;
import com.linkedin.businessattribute.BusinessAttributeAssociation;
import com.linkedin.businessattribute.BusinessAttributes;
import com.linkedin.common.urn.BusinessAttributeUrn;
import com.linkedin.common.urn.Urn;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.generated.AddBusinessAttributeInput;
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
import com.linkedin.datahub.graphql.resolvers.mutate.util.BusinessAttributeUtils;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.entity.EntityService;
import com.linkedin.metadata.entity.ebean.batch.AspectsBatchImpl;
import graphql.schema.DataFetchingEnvironment;
import io.datahubproject.metadata.context.OperationContext;
import java.net.URISyntaxException;
import java.util.List;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class AddBusinessAttributeResolverTest {
@ -33,21 +39,16 @@ public class AddBusinessAttributeResolverTest {
private QueryContext mockContext;
private DataFetchingEnvironment mockEnv;
@BeforeMethod
private void init() {
mockService = getMockEntityService();
mockEnv = Mockito.mock(DataFetchingEnvironment.class);
}
private void setupAllowContext() {
mockContext = getMockAllowContext();
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
}
@Test
public void testSuccess() throws Exception {
init();
setupAllowContext();
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(addBusinessAttributeInput());
Mockito.when(
mockService.exists(
@ -74,9 +75,6 @@ public class AddBusinessAttributeResolverTest {
@Test
public void testBusinessAttributeAlreadyAdded() throws Exception {
init();
setupAllowContext();
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(addBusinessAttributeInput());
Mockito.when(
mockService.exists(
@ -102,9 +100,6 @@ public class AddBusinessAttributeResolverTest {
@Test
public void testBusinessAttributeNotExists() throws Exception {
init();
setupAllowContext();
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(addBusinessAttributeInput());
Mockito.when(
mockService.exists(
@ -129,6 +124,42 @@ public class AddBusinessAttributeResolverTest {
.ingestProposal(any(OperationContext.class), any(AspectsBatchImpl.class), eq(false));
}
@Test
public void testActorNotHavePermissionToAddBusinessAttribute() throws Exception {
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(addBusinessAttributeInput());
Mockito.when(
mockService.exists(
any(OperationContext.class),
eq(Urn.createFromString((BUSINESS_ATTRIBUTE_URN))),
eq(true)))
.thenReturn(true);
AddBusinessAttributeInput businessAttributeInput = addBusinessAttributeInput();
List<ResourceRefInput> resourceRefInputs = businessAttributeInput.getResourceUrn();
try (MockedStatic<BusinessAttributeUtils> mockedStatic =
mockStatic(BusinessAttributeUtils.class)) {
mockedStatic
.when(
() ->
BusinessAttributeUtils.validateInputResources(
resourceRefInputs, mockEnv.getContext()))
.thenThrow(
new AuthorizationException(
"Unauthorized to perform this action. Please contact your DataHub administrator."));
AddBusinessAttributeResolver addBusinessAttributeResolver =
new AddBusinessAttributeResolver(mockService);
addBusinessAttributeResolver.get(mockEnv).get();
assertThrows(
AuthorizationException.class,
() -> {
BusinessAttributeUtils.validateInputResources(resourceRefInputs, mockEnv.getContext());
});
}
}
public AddBusinessAttributeInput addBusinessAttributeInput() {
AddBusinessAttributeInput addBusinessAttributeInput = new AddBusinessAttributeInput();
addBusinessAttributeInput.setBusinessAttributeUrn(BUSINESS_ATTRIBUTE_URN);
@ -136,10 +167,10 @@ public class AddBusinessAttributeResolverTest {
return addBusinessAttributeInput;
}
private ImmutableList<ResourceRefInput> resourceRefInput() {
private List<ResourceRefInput> resourceRefInput() {
ResourceRefInput resourceRefInput = new ResourceRefInput();
resourceRefInput.setResourceUrn(RESOURCE_URN);
return ImmutableList.of(resourceRefInput);
return List.of(resourceRefInput);
}
private BusinessAttributes businessAttributes() throws URISyntaxException {

View File

@ -4,6 +4,8 @@ import static com.linkedin.datahub.graphql.TestUtils.getMockAllowContext;
import static com.linkedin.datahub.graphql.TestUtils.getMockEntityService;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mockStatic;
import static org.testng.Assert.assertThrows;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.expectThrows;
@ -13,15 +15,19 @@ import com.linkedin.businessattribute.BusinessAttributes;
import com.linkedin.common.urn.BusinessAttributeUrn;
import com.linkedin.common.urn.Urn;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.generated.AddBusinessAttributeInput;
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
import com.linkedin.datahub.graphql.resolvers.mutate.util.BusinessAttributeUtils;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.entity.EntityService;
import com.linkedin.metadata.entity.ebean.batch.AspectsBatchImpl;
import graphql.schema.DataFetchingEnvironment;
import io.datahubproject.metadata.context.OperationContext;
import java.net.URISyntaxException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@ -39,16 +45,12 @@ public class RemoveBusinessAttributeResolverTest {
private void init() {
mockService = getMockEntityService();
mockEnv = Mockito.mock(DataFetchingEnvironment.class);
}
private void setupAllowContext() {
mockContext = getMockAllowContext();
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
}
@Test
public void testSuccess() throws Exception {
setupAllowContext();
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(addBusinessAttributeInput());
@ -70,7 +72,6 @@ public class RemoveBusinessAttributeResolverTest {
@Test
public void testBusinessAttributeNotAdded() throws Exception {
setupAllowContext();
AddBusinessAttributeInput input = addBusinessAttributeInput();
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(input);
Mockito.when(
@ -98,6 +99,42 @@ public class RemoveBusinessAttributeResolverTest {
any(OperationContext.class), Mockito.any(AspectsBatchImpl.class), eq(false));
}
@Test
public void testActorNotHavePermissionToRemoveBusinessAttribute() throws Exception {
Mockito.when(mockEnv.getArgument(Mockito.eq("input"))).thenReturn(addBusinessAttributeInput());
Mockito.when(
mockService.exists(
any(OperationContext.class),
eq(Urn.createFromString((BUSINESS_ATTRIBUTE_URN))),
eq(true)))
.thenReturn(true);
AddBusinessAttributeInput businessAttributeInput = addBusinessAttributeInput();
List<ResourceRefInput> resourceRefInputs = businessAttributeInput.getResourceUrn();
try (MockedStatic<BusinessAttributeUtils> mockedStatic =
mockStatic(BusinessAttributeUtils.class)) {
mockedStatic
.when(
() ->
BusinessAttributeUtils.validateInputResources(
resourceRefInputs, mockEnv.getContext()))
.thenThrow(
new AuthorizationException(
"Unauthorized to perform this action. Please contact your DataHub administrator."));
AddBusinessAttributeResolver addBusinessAttributeResolver =
new AddBusinessAttributeResolver(mockService);
addBusinessAttributeResolver.get(mockEnv).get();
assertThrows(
AuthorizationException.class,
() -> {
BusinessAttributeUtils.validateInputResources(resourceRefInputs, mockEnv.getContext());
});
}
}
public AddBusinessAttributeInput addBusinessAttributeInput() {
AddBusinessAttributeInput addBusinessAttributeInput = new AddBusinessAttributeInput();
addBusinessAttributeInput.setBusinessAttributeUrn(BUSINESS_ATTRIBUTE_URN);

View File

@ -550,6 +550,12 @@ public class PoliciesConfig {
"Produce Platform Event API",
"The ability to produce Platform Events using the API.");
public static final Privilege EDIT_DATASET_COL_BUSINESS_ATTRIBUTE_PRIVILEGE =
Privilege.of(
"EDIT_DATASET_COL_BUSINESS_ATTRIBUTE_PRIVILEGE",
"Edit Dataset Column Business Attribute",
"The ability to edit the column (field) Business Attribute associated with a dataset schema.");
public static final ResourcePrivileges DATASET_PRIVILEGES =
ResourcePrivileges.of(
"dataset",
@ -570,7 +576,8 @@ public class PoliciesConfig {
EDIT_QUERIES_PRIVILEGE,
CREATE_ER_MODEL_RELATIONSHIP_PRIVILEGE,
DATA_READ_ONLY_PRIVILEGE,
DATA_READ_WRITE_PRIVILEGE))
DATA_READ_WRITE_PRIVILEGE,
EDIT_DATASET_COL_BUSINESS_ATTRIBUTE_PRIVILEGE))
.flatMap(Collection::stream)
.collect(Collectors.toList()));