mirror of
https://github.com/datahub-project/datahub.git
synced 2025-08-17 21:56:56 +00:00
feat(groups): Adding editable group properties in the backend (#4166)
This commit is contained in:
parent
8167cbd432
commit
93befda8cf
@ -61,7 +61,7 @@ import com.linkedin.datahub.graphql.resolvers.group.EntityCountsResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.group.ListGroupsResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.group.RemoveGroupMembersResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.group.RemoveGroupResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.group.UpdateUserStatusResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.user.UpdateUserStatusResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.ingest.execution.CancelIngestionExecutionRequestResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.ingest.execution.CreateIngestionExecutionRequestResolver;
|
||||
import com.linkedin.datahub.graphql.resolvers.ingest.secret.CreateSecretResolver;
|
||||
@ -595,7 +595,8 @@ public class GmsGraphQLEngine {
|
||||
.dataFetcher("updateDashboard", new AuthenticatedResolver<>(new MutableTypeResolver<>(dashboardType)))
|
||||
.dataFetcher("updateDataJob", new AuthenticatedResolver<>(new MutableTypeResolver<>(dataJobType)))
|
||||
.dataFetcher("updateDataFlow", new AuthenticatedResolver<>(new MutableTypeResolver<>(dataFlowType)))
|
||||
.dataFetcher("updateCorpUserProperties", new AuthenticatedResolver<>(new MutableTypeResolver<>(corpUserType)))
|
||||
.dataFetcher("updateCorpUserProperties", new MutableTypeResolver<>(corpUserType))
|
||||
.dataFetcher("updateCorpGroupProperties", new MutableTypeResolver<>(corpGroupType))
|
||||
.dataFetcher("addTag", new AuthenticatedResolver<>(new AddTagResolver(entityService)))
|
||||
.dataFetcher("removeTag", new AuthenticatedResolver<>(new RemoveTagResolver(entityService)))
|
||||
.dataFetcher("addTerm", new AuthenticatedResolver<>(new AddTermResolver(entityService)))
|
||||
|
@ -1,9 +1,12 @@
|
||||
package com.linkedin.datahub.graphql.resolvers.group;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.linkedin.common.UrnArray;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.datahub.graphql.QueryContext;
|
||||
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
||||
import com.linkedin.datahub.graphql.authorization.ConjunctivePrivilegeGroup;
|
||||
import com.linkedin.datahub.graphql.authorization.DisjunctivePrivilegeGroup;
|
||||
import com.linkedin.datahub.graphql.exception.AuthorizationException;
|
||||
import com.linkedin.datahub.graphql.exception.DataHubGraphQLErrorCode;
|
||||
import com.linkedin.datahub.graphql.exception.DataHubGraphQLException;
|
||||
@ -12,6 +15,7 @@ import com.linkedin.entity.EntityResponse;
|
||||
import com.linkedin.entity.client.EntityClient;
|
||||
import com.linkedin.events.metadata.ChangeType;
|
||||
import com.linkedin.identity.GroupMembership;
|
||||
import com.linkedin.metadata.authorization.PoliciesConfig;
|
||||
import com.linkedin.metadata.utils.GenericAspectUtils;
|
||||
import com.linkedin.mxe.MetadataChangeProposal;
|
||||
import graphql.schema.DataFetcher;
|
||||
@ -20,6 +24,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static com.linkedin.datahub.graphql.resolvers.AuthUtils.*;
|
||||
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*;
|
||||
import static com.linkedin.metadata.Constants.*;
|
||||
|
||||
@ -38,10 +43,10 @@ public class AddGroupMembersResolver implements DataFetcher<CompletableFuture<Bo
|
||||
@Override
|
||||
public CompletableFuture<Boolean> get(final DataFetchingEnvironment environment) throws Exception {
|
||||
|
||||
final AddGroupMembersInput input = bindArgument(environment.getArgument("input"), AddGroupMembersInput.class);
|
||||
final QueryContext context = environment.getContext();
|
||||
|
||||
if (AuthorizationUtils.canManageUsersAndGroups(context)) {
|
||||
final AddGroupMembersInput input = bindArgument(environment.getArgument("input"), AddGroupMembersInput.class);
|
||||
if (isAuthorized(input, context)) {
|
||||
final String groupUrnStr = input.getGroupUrn();
|
||||
final List<String> userUrnStrs = input.getUserUrns();
|
||||
|
||||
@ -60,6 +65,20 @@ public class AddGroupMembersResolver implements DataFetcher<CompletableFuture<Bo
|
||||
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator.");
|
||||
}
|
||||
|
||||
private boolean isAuthorized(AddGroupMembersInput input, QueryContext context) {
|
||||
final DisjunctivePrivilegeGroup orPrivilegeGroups = new DisjunctivePrivilegeGroup(ImmutableList.of(
|
||||
ALL_PRIVILEGES_GROUP,
|
||||
new ConjunctivePrivilegeGroup(ImmutableList.of(PoliciesConfig.EDIT_GROUP_MEMBERS_PRIVILEGE.getType()))
|
||||
));
|
||||
|
||||
return AuthorizationUtils.isAuthorized(
|
||||
context.getAuthorizer(),
|
||||
context.getActorUrn(),
|
||||
CORP_GROUP_ENTITY_NAME,
|
||||
input.getGroupUrn(),
|
||||
orPrivilegeGroups);
|
||||
}
|
||||
|
||||
private void addUserToGroup(final String userUrnStr, final String groupUrnStr, final QueryContext context) {
|
||||
try {
|
||||
// First, fetch user's group membership aspect.
|
||||
|
@ -1,15 +1,19 @@
|
||||
package com.linkedin.datahub.graphql.resolvers.group;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.common.urn.UrnUtils;
|
||||
import com.linkedin.datahub.graphql.QueryContext;
|
||||
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
||||
import com.linkedin.datahub.graphql.authorization.ConjunctivePrivilegeGroup;
|
||||
import com.linkedin.datahub.graphql.authorization.DisjunctivePrivilegeGroup;
|
||||
import com.linkedin.datahub.graphql.exception.AuthorizationException;
|
||||
import com.linkedin.datahub.graphql.generated.RemoveGroupMembersInput;
|
||||
import com.linkedin.entity.EntityResponse;
|
||||
import com.linkedin.entity.client.EntityClient;
|
||||
import com.linkedin.events.metadata.ChangeType;
|
||||
import com.linkedin.identity.GroupMembership;
|
||||
import com.linkedin.metadata.authorization.PoliciesConfig;
|
||||
import com.linkedin.metadata.utils.GenericAspectUtils;
|
||||
import com.linkedin.mxe.MetadataChangeProposal;
|
||||
import graphql.schema.DataFetcher;
|
||||
@ -20,6 +24,7 @@ import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.linkedin.datahub.graphql.resolvers.AuthUtils.*;
|
||||
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*;
|
||||
import static com.linkedin.metadata.Constants.*;
|
||||
|
||||
@ -35,10 +40,10 @@ public class RemoveGroupMembersResolver implements DataFetcher<CompletableFuture
|
||||
@Override
|
||||
public CompletableFuture<Boolean> get(final DataFetchingEnvironment environment) throws Exception {
|
||||
|
||||
final RemoveGroupMembersInput input = bindArgument(environment.getArgument("input"), RemoveGroupMembersInput.class);
|
||||
final QueryContext context = environment.getContext();
|
||||
|
||||
if (AuthorizationUtils.canManageUsersAndGroups(context)) {
|
||||
final RemoveGroupMembersInput input = bindArgument(environment.getArgument("input"), RemoveGroupMembersInput.class);
|
||||
if (isAuthorized(input, context)) {
|
||||
final Urn groupUrn = Urn.createFromString(input.getGroupUrn());
|
||||
final Set<Urn> userUrns = input.getUserUrns().stream().map(UrnUtils::getUrn).collect(Collectors.toSet());
|
||||
final Map<Urn, EntityResponse> entityResponseMap = _entityClient.batchGetV2(CORP_USER_ENTITY_NAME,
|
||||
@ -72,4 +77,18 @@ public class RemoveGroupMembersResolver implements DataFetcher<CompletableFuture
|
||||
}
|
||||
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator.");
|
||||
}
|
||||
|
||||
private boolean isAuthorized(RemoveGroupMembersInput input, QueryContext context) {
|
||||
final DisjunctivePrivilegeGroup orPrivilegeGroups = new DisjunctivePrivilegeGroup(ImmutableList.of(
|
||||
ALL_PRIVILEGES_GROUP,
|
||||
new ConjunctivePrivilegeGroup(ImmutableList.of(PoliciesConfig.EDIT_GROUP_MEMBERS_PRIVILEGE.getType()))
|
||||
));
|
||||
|
||||
return AuthorizationUtils.isAuthorized(
|
||||
context.getAuthorizer(),
|
||||
context.getActorUrn(),
|
||||
CORP_GROUP_ENTITY_NAME,
|
||||
input.getGroupUrn(),
|
||||
orPrivilegeGroups);
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import graphql.schema.DataFetchingEnvironment;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Resolver responsible for hard deleting a particular DataHub Corp User
|
||||
* Resolver responsible for hard deleting a particular DataHub Corp Group
|
||||
*/
|
||||
public class RemoveGroupResolver implements DataFetcher<CompletableFuture<Boolean>> {
|
||||
|
||||
@ -28,6 +28,7 @@ public class RemoveGroupResolver implements DataFetcher<CompletableFuture<Boolea
|
||||
final Urn urn = Urn.createFromString(groupUrn);
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// TODO: Remove all dangling references to this group.
|
||||
_entityClient.deleteEntity(urn, context.getAuthentication());
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
|
@ -11,6 +11,7 @@ import com.linkedin.datahub.graphql.authorization.DisjunctivePrivilegeGroup;
|
||||
import com.linkedin.datahub.graphql.generated.SubResourceType;
|
||||
import com.linkedin.domain.DomainProperties;
|
||||
import com.linkedin.glossary.GlossaryTermInfo;
|
||||
import com.linkedin.identity.CorpGroupEditableInfo;
|
||||
import com.linkedin.metadata.Constants;
|
||||
import com.linkedin.metadata.authorization.PoliciesConfig;
|
||||
import com.linkedin.metadata.entity.EntityService;
|
||||
@ -97,6 +98,19 @@ public class DescriptionUtils {
|
||||
persistAspect(resourceUrn, Constants.TAG_PROPERTIES_ASPECT_NAME, tagProperties, actor, entityService);
|
||||
}
|
||||
|
||||
public static void updateCorpGroupDescription(
|
||||
String newDescription,
|
||||
Urn resourceUrn,
|
||||
Urn actor,
|
||||
EntityService entityService
|
||||
) {
|
||||
CorpGroupEditableInfo corpGroupEditableInfo =
|
||||
(CorpGroupEditableInfo) getAspectFromEntity(
|
||||
resourceUrn.toString(), Constants.CORP_GROUP_EDITABLE_INFO_ASPECT_NAME, entityService, new CorpGroupEditableInfo());
|
||||
corpGroupEditableInfo.setDescription(newDescription);
|
||||
persistAspect(resourceUrn, Constants.CORP_GROUP_EDITABLE_INFO_ASPECT_NAME, corpGroupEditableInfo, actor, entityService);
|
||||
}
|
||||
|
||||
public static void updateGlossaryTermDescription(
|
||||
String newDescription,
|
||||
Urn resourceUrn,
|
||||
@ -161,6 +175,16 @@ public class DescriptionUtils {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Boolean validateCorpGroupInput(
|
||||
Urn corpUserUrn,
|
||||
EntityService entityService
|
||||
) {
|
||||
if (!entityService.exists(corpUserUrn)) {
|
||||
throw new IllegalArgumentException(String.format("Failed to update %s. %s does not exist.", corpUserUrn, corpUserUrn));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isAuthorizedToUpdateFieldDescription(@Nonnull QueryContext context, Urn targetUrn) {
|
||||
final DisjunctivePrivilegeGroup orPrivilegeGroups = new DisjunctivePrivilegeGroup(ImmutableList.of(
|
||||
ALL_PRIVILEGES_GROUP,
|
||||
|
@ -15,7 +15,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class UpdateDescriptionResolver implements DataFetcher<CompletableFuture<Boolean>> {
|
||||
@ -37,6 +36,8 @@ public class UpdateDescriptionResolver implements DataFetcher<CompletableFuture<
|
||||
return updateGlossaryTermDescription(targetUrn, input, environment.getContext());
|
||||
case Constants.TAG_ENTITY_NAME:
|
||||
return updateTagDescription(targetUrn, input, environment.getContext());
|
||||
case Constants.CORP_GROUP_ENTITY_NAME:
|
||||
return updateCorpGroupDescription(targetUrn, input, environment.getContext());
|
||||
default:
|
||||
throw new RuntimeException(
|
||||
String.format("Failed to update description. Unsupported resource type %s provided.", targetUrn));
|
||||
@ -167,4 +168,28 @@ public class UpdateDescriptionResolver implements DataFetcher<CompletableFuture<
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<Boolean> updateCorpGroupDescription(Urn targetUrn, DescriptionUpdateInput input, QueryContext context) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
|
||||
if (!DescriptionUtils.isAuthorizedToUpdateDescription(context, targetUrn)) {
|
||||
throw new AuthorizationException(
|
||||
"Unauthorized to perform this action. Please contact your DataHub administrator.");
|
||||
}
|
||||
DescriptionUtils.validateCorpGroupInput(targetUrn, _entityService);
|
||||
|
||||
try {
|
||||
Urn actor = CorpuserUrn.createFromString(context.getActorUrn());
|
||||
DescriptionUtils.updateCorpGroupDescription(
|
||||
input.getDescription(),
|
||||
targetUrn,
|
||||
actor,
|
||||
_entityService);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to perform update against input {}, {}", input.toString(), e.getMessage());
|
||||
throw new RuntimeException(String.format("Failed to perform update against input %s", input.toString()), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.linkedin.datahub.graphql.resolvers.group;
|
||||
package com.linkedin.datahub.graphql.resolvers.user;
|
||||
|
||||
import com.linkedin.common.AuditStamp;
|
||||
import com.linkedin.common.urn.Urn;
|
@ -1,21 +1,35 @@
|
||||
package com.linkedin.datahub.graphql.types.corpgroup;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.common.urn.UrnUtils;
|
||||
import com.linkedin.data.template.RecordTemplate;
|
||||
import com.linkedin.datahub.graphql.QueryContext;
|
||||
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
||||
import com.linkedin.datahub.graphql.authorization.ConjunctivePrivilegeGroup;
|
||||
import com.linkedin.datahub.graphql.authorization.DisjunctivePrivilegeGroup;
|
||||
import com.linkedin.datahub.graphql.exception.AuthorizationException;
|
||||
import com.linkedin.datahub.graphql.generated.AutoCompleteResults;
|
||||
import com.linkedin.datahub.graphql.generated.CorpGroup;
|
||||
import com.linkedin.datahub.graphql.generated.CorpGroupUpdateInput;
|
||||
import com.linkedin.datahub.graphql.generated.EntityType;
|
||||
import com.linkedin.datahub.graphql.generated.FacetFilterInput;
|
||||
import com.linkedin.datahub.graphql.generated.SearchResults;
|
||||
import com.linkedin.datahub.graphql.types.MutableType;
|
||||
import com.linkedin.datahub.graphql.types.SearchableEntityType;
|
||||
import com.linkedin.datahub.graphql.types.corpgroup.mappers.CorpGroupMapper;
|
||||
import com.linkedin.datahub.graphql.types.mappers.AutoCompleteResultsMapper;
|
||||
import com.linkedin.datahub.graphql.types.mappers.UrnSearchResultsMapper;
|
||||
import com.linkedin.entity.EntityResponse;
|
||||
import com.linkedin.entity.client.EntityClient;
|
||||
import com.linkedin.events.metadata.ChangeType;
|
||||
import com.linkedin.identity.CorpGroupEditableInfo;
|
||||
import com.linkedin.metadata.authorization.PoliciesConfig;
|
||||
import com.linkedin.metadata.query.AutoCompleteResult;
|
||||
import com.linkedin.metadata.search.SearchResult;
|
||||
import com.linkedin.metadata.utils.GenericAspectUtils;
|
||||
import com.linkedin.mxe.MetadataChangeProposal;
|
||||
import graphql.execution.DataFetcherResult;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@ -28,8 +42,7 @@ import javax.annotation.Nullable;
|
||||
|
||||
import static com.linkedin.metadata.Constants.*;
|
||||
|
||||
|
||||
public class CorpGroupType implements SearchableEntityType<CorpGroup> {
|
||||
public class CorpGroupType implements SearchableEntityType<CorpGroup>, MutableType<CorpGroupUpdateInput, CorpGroup> {
|
||||
|
||||
private final EntityClient _entityClient;
|
||||
|
||||
@ -42,6 +55,10 @@ public class CorpGroupType implements SearchableEntityType<CorpGroup> {
|
||||
return CorpGroup.class;
|
||||
}
|
||||
|
||||
public Class<CorpGroupUpdateInput> inputClass() {
|
||||
return CorpGroupUpdateInput.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityType type() {
|
||||
return EntityType.CORP_GROUP;
|
||||
@ -93,4 +110,84 @@ public class CorpGroupType implements SearchableEntityType<CorpGroup> {
|
||||
context.getAuthentication());
|
||||
return AutoCompleteResultsMapper.map(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CorpGroup update(@Nonnull String urn, @Nonnull CorpGroupUpdateInput input, @Nonnull QueryContext context) throws Exception {
|
||||
if (isAuthorizedToUpdate(urn, input, context)) {
|
||||
// Get existing editable info to merge with
|
||||
Urn groupUrn = Urn.createFromString(urn);
|
||||
Map<Urn, EntityResponse> gmsResponse =
|
||||
_entityClient.batchGetV2(CORP_GROUP_ENTITY_NAME, ImmutableSet.of(groupUrn), ImmutableSet.of(
|
||||
CORP_GROUP_EDITABLE_INFO_ASPECT_NAME),
|
||||
context.getAuthentication());
|
||||
|
||||
CorpGroupEditableInfo existingCorpGroupEditableInfo = null;
|
||||
if (gmsResponse.containsKey(groupUrn) && gmsResponse.get(groupUrn).getAspects().containsKey(CORP_GROUP_EDITABLE_INFO_ASPECT_NAME)) {
|
||||
existingCorpGroupEditableInfo = new CorpGroupEditableInfo(gmsResponse.get(groupUrn).getAspects()
|
||||
.get(CORP_GROUP_EDITABLE_INFO_ASPECT_NAME).getValue().data());
|
||||
}
|
||||
|
||||
// Create the MCP
|
||||
final MetadataChangeProposal proposal = new MetadataChangeProposal();
|
||||
proposal.setEntityUrn(Urn.createFromString(urn));
|
||||
proposal.setEntityType(CORP_GROUP_ENTITY_NAME);
|
||||
proposal.setAspectName(CORP_GROUP_EDITABLE_INFO_ASPECT_NAME);
|
||||
proposal.setAspect(
|
||||
GenericAspectUtils.serializeAspect(mapCorpGroupEditableInfo(input, existingCorpGroupEditableInfo)));
|
||||
proposal.setChangeType(ChangeType.UPSERT);
|
||||
_entityClient.ingestProposal(proposal, context.getAuthentication());
|
||||
|
||||
return load(urn, context).getData();
|
||||
}
|
||||
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator.");
|
||||
}
|
||||
|
||||
private boolean isAuthorizedToUpdate(String urn, CorpGroupUpdateInput input, QueryContext context) {
|
||||
// Decide whether the current principal should be allowed to update the Dataset.
|
||||
final DisjunctivePrivilegeGroup orPrivilegeGroups = getAuthorizedPrivileges(input);
|
||||
return AuthorizationUtils.isAuthorized(
|
||||
context.getAuthorizer(),
|
||||
context.getAuthentication().getActor().toUrnStr(),
|
||||
PoliciesConfig.CORP_GROUP_PRIVILEGES.getResourceType(),
|
||||
urn,
|
||||
orPrivilegeGroups);
|
||||
}
|
||||
|
||||
private DisjunctivePrivilegeGroup getAuthorizedPrivileges(final CorpGroupUpdateInput updateInput) {
|
||||
final ConjunctivePrivilegeGroup allPrivilegesGroup = new ConjunctivePrivilegeGroup(ImmutableList.of(
|
||||
PoliciesConfig.EDIT_ENTITY_PRIVILEGE.getType()
|
||||
));
|
||||
|
||||
List<String> specificPrivileges = new ArrayList<>();
|
||||
if (updateInput.getDescription() != null) {
|
||||
// Requires the Update Docs privilege.
|
||||
specificPrivileges.add(PoliciesConfig.EDIT_ENTITY_DOCS_PRIVILEGE.getType());
|
||||
} else if (updateInput.getSlack() != null || updateInput.getEmail() != null) {
|
||||
// Requires the Update Contact info privilege.
|
||||
specificPrivileges.add(PoliciesConfig.EDIT_CONTACT_INFO_PRIVILEGE.getType());
|
||||
}
|
||||
|
||||
final ConjunctivePrivilegeGroup specificPrivilegeGroup = new ConjunctivePrivilegeGroup(specificPrivileges);
|
||||
|
||||
// If you either have all entity privileges, or have the specific privileges required, you are authorized.
|
||||
return new DisjunctivePrivilegeGroup(ImmutableList.of(
|
||||
allPrivilegesGroup,
|
||||
specificPrivilegeGroup
|
||||
));
|
||||
}
|
||||
|
||||
private RecordTemplate mapCorpGroupEditableInfo(CorpGroupUpdateInput input, @Nullable CorpGroupEditableInfo existing) {
|
||||
CorpGroupEditableInfo result = existing != null ? existing : new CorpGroupEditableInfo();
|
||||
|
||||
if (input.getDescription() != null) {
|
||||
result.setDescription(input.getDescription());
|
||||
}
|
||||
if (input.getSlack() != null) {
|
||||
result.setSlack(input.getSlack());
|
||||
}
|
||||
if (input.getEmail() != null) {
|
||||
result.setEmail(input.getEmail());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
package com.linkedin.datahub.graphql.types.corpgroup.mappers;
|
||||
|
||||
import com.linkedin.data.template.GetMode;
|
||||
import com.linkedin.datahub.graphql.generated.CorpGroupEditableProperties;
|
||||
import com.linkedin.datahub.graphql.types.mappers.ModelMapper;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Maps Pegasus {@link RecordTemplate} objects to objects conforming to the GQL schema.
|
||||
*
|
||||
* To be replaced by auto-generated mappers implementations
|
||||
*/
|
||||
public class CorpGroupEditablePropertiesMapper implements ModelMapper<com.linkedin.identity.CorpGroupEditableInfo, CorpGroupEditableProperties> {
|
||||
|
||||
public static final CorpGroupEditablePropertiesMapper INSTANCE = new CorpGroupEditablePropertiesMapper();
|
||||
|
||||
public static CorpGroupEditableProperties map(@Nonnull final com.linkedin.identity.CorpGroupEditableInfo corpGroupEditableInfo) {
|
||||
return INSTANCE.apply(corpGroupEditableInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CorpGroupEditableProperties apply(@Nonnull final com.linkedin.identity.CorpGroupEditableInfo corpGroupEditableInfo) {
|
||||
final CorpGroupEditableProperties result = new CorpGroupEditableProperties();
|
||||
result.setDescription(corpGroupEditableInfo.getDescription(GetMode.DEFAULT));
|
||||
result.setSlack(corpGroupEditableInfo.getSlack(GetMode.DEFAULT));
|
||||
result.setEmail(corpGroupEditableInfo.getEmail(GetMode.DEFAULT));
|
||||
return result;
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import com.linkedin.datahub.graphql.types.common.mappers.util.MappingHelper;
|
||||
import com.linkedin.datahub.graphql.types.mappers.ModelMapper;
|
||||
import com.linkedin.entity.EntityResponse;
|
||||
import com.linkedin.entity.EnvelopedAspectMap;
|
||||
import com.linkedin.identity.CorpGroupEditableInfo;
|
||||
import com.linkedin.identity.CorpGroupInfo;
|
||||
import com.linkedin.metadata.key.CorpGroupKey;
|
||||
import javax.annotation.Nonnull;
|
||||
@ -36,7 +37,7 @@ public class CorpGroupMapper implements ModelMapper<EntityResponse, CorpGroup> {
|
||||
MappingHelper<CorpGroup> mappingHelper = new MappingHelper<>(aspectMap, result);
|
||||
mappingHelper.mapToResult(CORP_GROUP_KEY_ASPECT_NAME, this::mapCorpGroupKey);
|
||||
mappingHelper.mapToResult(CORP_GROUP_INFO_ASPECT_NAME, this::mapCorpGroupInfo);
|
||||
|
||||
mappingHelper.mapToResult(CORP_GROUP_EDITABLE_INFO_ASPECT_NAME, this::mapCorpGroupEditableInfo);
|
||||
return mappingHelper.getResult();
|
||||
}
|
||||
|
||||
@ -50,4 +51,8 @@ public class CorpGroupMapper implements ModelMapper<EntityResponse, CorpGroup> {
|
||||
corpGroup.setProperties(CorpGroupPropertiesMapper.map(corpGroupInfo));
|
||||
corpGroup.setInfo(CorpGroupInfoMapper.map(corpGroupInfo));
|
||||
}
|
||||
|
||||
private void mapCorpGroupEditableInfo(@Nonnull CorpGroup corpGroup, @Nonnull DataMap dataMap) {
|
||||
corpGroup.setEditableProperties(CorpGroupEditablePropertiesMapper.map(new CorpGroupEditableInfo(dataMap)));
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,16 @@
|
||||
package com.linkedin.datahub.graphql.types.corpuser;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.linkedin.common.url.Url;
|
||||
import com.linkedin.common.urn.CorpuserUrn;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.common.urn.UrnUtils;
|
||||
import com.linkedin.data.template.RecordTemplate;
|
||||
import com.linkedin.data.template.StringArray;
|
||||
import com.linkedin.datahub.graphql.QueryContext;
|
||||
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
||||
import com.linkedin.datahub.graphql.authorization.ConjunctivePrivilegeGroup;
|
||||
import com.linkedin.datahub.graphql.authorization.DisjunctivePrivilegeGroup;
|
||||
import com.linkedin.datahub.graphql.exception.AuthorizationException;
|
||||
import com.linkedin.datahub.graphql.generated.AutoCompleteResults;
|
||||
import com.linkedin.datahub.graphql.generated.CorpUser;
|
||||
import com.linkedin.datahub.graphql.generated.CorpUserUpdateInput;
|
||||
@ -23,12 +27,12 @@ import com.linkedin.entity.client.EntityClient;
|
||||
import com.linkedin.events.metadata.ChangeType;
|
||||
import com.linkedin.identity.CorpUserEditableInfo;
|
||||
import com.linkedin.metadata.Constants;
|
||||
import com.linkedin.metadata.authorization.PoliciesConfig;
|
||||
import com.linkedin.metadata.query.AutoCompleteResult;
|
||||
import com.linkedin.metadata.search.SearchResult;
|
||||
import com.linkedin.metadata.utils.GenericAspectUtils;
|
||||
import com.linkedin.mxe.MetadataChangeProposal;
|
||||
import graphql.execution.DataFetcherResult;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@ -106,37 +110,70 @@ public class CorpUserType implements SearchableEntityType<CorpUser>, MutableType
|
||||
return AutoCompleteResultsMapper.map(result);
|
||||
}
|
||||
|
||||
private CorpuserUrn getCorpUserUrn(final String urnStr) {
|
||||
try {
|
||||
return CorpuserUrn.createFromString(urnStr);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(String.format("Failed to retrieve user with urn %s, invalid urn", urnStr));
|
||||
}
|
||||
}
|
||||
|
||||
public Class<CorpUserUpdateInput> inputClass() {
|
||||
return CorpUserUpdateInput.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CorpUser update(@Nonnull String urn, @Nonnull CorpUserUpdateInput input, @Nonnull QueryContext context) throws Exception {
|
||||
final CorpuserUrn actor = CorpuserUrn.createFromString(context.getAuthentication().getActor().toUrnStr());
|
||||
if (isAuthorizedToUpdate(urn, input, context)) {
|
||||
// Get existing editable info to merge with
|
||||
Optional<CorpUserEditableInfo> existingCorpUserEditableInfo =
|
||||
_entityClient.getVersionedAspect(urn, Constants.CORP_USER_EDITABLE_INFO_NAME, 0L, CorpUserEditableInfo.class,
|
||||
context.getAuthentication());
|
||||
|
||||
// Get existing editable info to merge with
|
||||
Optional<CorpUserEditableInfo> existingCorpUserEditableInfo =
|
||||
_entityClient.getVersionedAspect(urn, Constants.CORP_USER_EDITABLE_INFO_NAME, 0L, CorpUserEditableInfo.class,
|
||||
context.getAuthentication());
|
||||
// Create the MCP
|
||||
final MetadataChangeProposal proposal = new MetadataChangeProposal();
|
||||
proposal.setEntityUrn(Urn.createFromString(urn));
|
||||
proposal.setEntityType(Constants.CORP_USER_ENTITY_NAME);
|
||||
proposal.setAspectName(Constants.CORP_USER_EDITABLE_INFO_NAME);
|
||||
proposal.setAspect(GenericAspectUtils.serializeAspect(mapCorpUserEditableInfo(input, existingCorpUserEditableInfo)));
|
||||
proposal.setChangeType(ChangeType.UPSERT);
|
||||
_entityClient.ingestProposal(proposal, context.getAuthentication());
|
||||
|
||||
// Create the MCP
|
||||
final MetadataChangeProposal proposal = new MetadataChangeProposal();
|
||||
proposal.setEntityUrn(Urn.createFromString(urn));
|
||||
proposal.setEntityType(Constants.CORP_USER_ENTITY_NAME);
|
||||
proposal.setAspectName(Constants.CORP_USER_EDITABLE_INFO_NAME);
|
||||
proposal.setAspect(GenericAspectUtils.serializeAspect(mapCorpUserEditableInfo(input, existingCorpUserEditableInfo)));
|
||||
proposal.setChangeType(ChangeType.UPSERT);
|
||||
_entityClient.ingestProposal(proposal, context.getAuthentication());
|
||||
return load(urn, context).getData();
|
||||
}
|
||||
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator.");
|
||||
}
|
||||
|
||||
return load(urn, context).getData();
|
||||
private boolean isAuthorizedToUpdate(String urn, CorpUserUpdateInput input, QueryContext context) {
|
||||
// Decide whether the current principal should be allowed to update the Dataset.
|
||||
final DisjunctivePrivilegeGroup orPrivilegeGroups = getAuthorizedPrivileges(input);
|
||||
|
||||
// Either the updating actor is the user, or the actor has privileges to update the user information.
|
||||
return context.getActorUrn().equals(urn) || AuthorizationUtils.isAuthorized(
|
||||
context.getAuthorizer(),
|
||||
context.getAuthentication().getActor().toUrnStr(),
|
||||
PoliciesConfig.CORP_GROUP_PRIVILEGES.getResourceType(),
|
||||
urn,
|
||||
orPrivilegeGroups);
|
||||
}
|
||||
|
||||
private DisjunctivePrivilegeGroup getAuthorizedPrivileges(final CorpUserUpdateInput updateInput) {
|
||||
final ConjunctivePrivilegeGroup allPrivilegesGroup = new ConjunctivePrivilegeGroup(ImmutableList.of(
|
||||
PoliciesConfig.EDIT_ENTITY_PRIVILEGE.getType()
|
||||
));
|
||||
|
||||
List<String> specificPrivileges = new ArrayList<>();
|
||||
if (updateInput.getSlack() != null
|
||||
|| updateInput.getEmail() != null
|
||||
|| updateInput.getPhone() != null) {
|
||||
specificPrivileges.add(PoliciesConfig.EDIT_CONTACT_INFO_PRIVILEGE.getType());
|
||||
} else if (updateInput.getAboutMe() != null
|
||||
|| updateInput.getDisplayName() != null
|
||||
|| updateInput.getPictureLink() != null
|
||||
|| updateInput.getTeams() != null
|
||||
|| updateInput.getTitle() != null) {
|
||||
specificPrivileges.add(PoliciesConfig.EDIT_USER_PROFILE_PRIVILEGE.getType());
|
||||
}
|
||||
|
||||
final ConjunctivePrivilegeGroup specificPrivilegeGroup = new ConjunctivePrivilegeGroup(specificPrivileges);
|
||||
|
||||
// If you either have all entity privileges, or have the specific privileges required, you are authorized.
|
||||
return new DisjunctivePrivilegeGroup(ImmutableList.of(
|
||||
allPrivilegesGroup,
|
||||
specificPrivilegeGroup
|
||||
));
|
||||
}
|
||||
|
||||
private RecordTemplate mapCorpUserEditableInfo(CorpUserUpdateInput input, Optional<CorpUserEditableInfo> existing) {
|
||||
@ -159,6 +196,9 @@ public class CorpUserType implements SearchableEntityType<CorpUser>, MutableType
|
||||
if (input.getTeams() != null) {
|
||||
result.setTeams(new StringArray(input.getTeams()));
|
||||
}
|
||||
if (input.getTitle() != null) {
|
||||
result.setTitle(input.getTitle());
|
||||
}
|
||||
if (input.getPhone() != null) {
|
||||
result.setPhone(input.getPhone());
|
||||
}
|
||||
@ -168,9 +208,6 @@ public class CorpUserType implements SearchableEntityType<CorpUser>, MutableType
|
||||
if (input.getEmail() != null) {
|
||||
result.setEmail(input.getEmail());
|
||||
}
|
||||
if (input.getTitle() != null) {
|
||||
result.setTitle(input.getTitle());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -277,6 +277,11 @@ type Mutation {
|
||||
Update a particular Corp User's editable properties
|
||||
"""
|
||||
updateCorpUserProperties(urn: String!, input: CorpUserUpdateInput!): CorpUser
|
||||
|
||||
"""
|
||||
Update a particular Corp Group's editable properties
|
||||
"""
|
||||
updateCorpGroupProperties(urn: String!, input: CorpGroupUpdateInput!): CorpGroup
|
||||
}
|
||||
|
||||
"""
|
||||
@ -2328,7 +2333,6 @@ type CorpUserEditableProperties {
|
||||
email: String
|
||||
}
|
||||
|
||||
|
||||
"""
|
||||
Arguments provided to update a CorpUser Entity
|
||||
"""
|
||||
@ -2403,6 +2407,11 @@ type CorpGroup implements Entity {
|
||||
"""
|
||||
properties: CorpGroupProperties
|
||||
|
||||
"""
|
||||
Additional read write properties about the group
|
||||
"""
|
||||
editableProperties: CorpGroupEditableProperties
|
||||
|
||||
"""
|
||||
Edges extending from this entity
|
||||
"""
|
||||
@ -2474,6 +2483,46 @@ type CorpGroupProperties {
|
||||
email: String
|
||||
}
|
||||
|
||||
"""
|
||||
Additional read write properties about a group
|
||||
"""
|
||||
type CorpGroupEditableProperties {
|
||||
"""
|
||||
DataHub description of the group
|
||||
"""
|
||||
description: String
|
||||
|
||||
"""
|
||||
Slack handle for the group
|
||||
"""
|
||||
slack: String
|
||||
|
||||
"""
|
||||
Email address for the group
|
||||
"""
|
||||
email: String
|
||||
}
|
||||
|
||||
"""
|
||||
Arguments provided to update a CorpGroup Entity
|
||||
"""
|
||||
input CorpGroupUpdateInput {
|
||||
"""
|
||||
DataHub description of the group
|
||||
"""
|
||||
description: String
|
||||
|
||||
"""
|
||||
Slack handle for the group
|
||||
"""
|
||||
slack: String
|
||||
|
||||
"""
|
||||
Email address for the group
|
||||
"""
|
||||
email: String
|
||||
}
|
||||
|
||||
"""
|
||||
An owner of a Metadata Entity, either a user or group
|
||||
"""
|
||||
|
@ -0,0 +1,35 @@
|
||||
namespace com.linkedin.identity
|
||||
|
||||
import com.linkedin.common.Url
|
||||
|
||||
/**
|
||||
* Group information that can be edited from UI
|
||||
*/
|
||||
@Aspect = {
|
||||
"name": "corpGroupEditableInfo"
|
||||
}
|
||||
record CorpGroupEditableInfo {
|
||||
/**
|
||||
* A description of the group
|
||||
*/
|
||||
@Searchable = {
|
||||
"fieldType": "TEXT",
|
||||
"fieldName": "editedDescription",
|
||||
}
|
||||
description: optional string
|
||||
|
||||
/**
|
||||
* A URL which points to a picture which user wants to set as the photo for the group
|
||||
*/
|
||||
pictureLink: Url = "https://raw.githubusercontent.com/linkedin/datahub/master/datahub-web-react/src/images/default_avatar.png"
|
||||
|
||||
/**
|
||||
* Slack handle for the group
|
||||
*/
|
||||
slack: optional string
|
||||
|
||||
/**
|
||||
* Email address to contact the group
|
||||
*/
|
||||
email: optional string
|
||||
}
|
@ -5,7 +5,7 @@ import com.linkedin.common.CorpuserUrn
|
||||
import com.linkedin.common.EmailAddress
|
||||
|
||||
/**
|
||||
* group of corpUser, it may contains nested group
|
||||
* Information about a Corp Group ingested from a third party source
|
||||
*/
|
||||
@Aspect = {
|
||||
"name": "corpGroupInfo"
|
||||
@ -14,7 +14,7 @@ import com.linkedin.common.EmailAddress
|
||||
record CorpGroupInfo {
|
||||
|
||||
/**
|
||||
* The name to use when displaying the group.
|
||||
* The name of the group.
|
||||
*/
|
||||
@Searchable = {
|
||||
"fieldType": "TEXT_PARTIAL"
|
||||
@ -28,6 +28,7 @@ record CorpGroupInfo {
|
||||
|
||||
/**
|
||||
* owners of this group
|
||||
* Deprecated! Replaced by Ownership aspect.
|
||||
*/
|
||||
@Relationship = {
|
||||
"/*": {
|
||||
@ -35,10 +36,12 @@ record CorpGroupInfo {
|
||||
"entityTypes": [ "corpUser" ]
|
||||
}
|
||||
}
|
||||
@deprecated
|
||||
admins: array[CorpuserUrn]
|
||||
|
||||
/**
|
||||
* List of ldap urn in this group.
|
||||
* Deprecated! Replaced by GroupMembership aspect.
|
||||
*/
|
||||
@Relationship = {
|
||||
"/*": {
|
||||
@ -46,10 +49,12 @@ record CorpGroupInfo {
|
||||
"entityTypes": [ "corpUser" ]
|
||||
}
|
||||
}
|
||||
@deprecated
|
||||
members: array[CorpuserUrn]
|
||||
|
||||
/**
|
||||
* List of groups in this group.
|
||||
* Deprecated! This field is unused.
|
||||
*/
|
||||
@Relationship = {
|
||||
"/*": {
|
||||
@ -57,6 +62,7 @@ record CorpGroupInfo {
|
||||
"entityTypes": [ "corpGroup" ]
|
||||
}
|
||||
}
|
||||
@deprecated
|
||||
groups: array[CorpGroupUrn]
|
||||
|
||||
/**
|
||||
|
@ -107,3 +107,9 @@ entities:
|
||||
aspects:
|
||||
- assertionInfo
|
||||
- dataPlatformInstance
|
||||
- name: corpGroup
|
||||
doc: CorpGroup represents an identity of a group of users in the enterprise.
|
||||
keyAspect: corpGroupKey
|
||||
aspects:
|
||||
- corpGroupEditableInfo
|
||||
- ownership
|
@ -246,5 +246,30 @@
|
||||
"type":"METADATA",
|
||||
"editable":false
|
||||
}
|
||||
},
|
||||
{
|
||||
"urn": "urn:li:dataHubPolicy:10",
|
||||
"info": {
|
||||
"actors":{
|
||||
"resourceOwners":false,
|
||||
"allUsers":false,
|
||||
"allGroups":false,
|
||||
"users":[
|
||||
"urn:li:corpuser:datahub"
|
||||
]
|
||||
},
|
||||
"privileges":[
|
||||
"EDIT_ENTITY"
|
||||
],
|
||||
"displayName":"Root User - Edit All Groups",
|
||||
"resources":{
|
||||
"type":"corpGroup",
|
||||
"allResources":true
|
||||
},
|
||||
"description":"Grants full edit privileges for Groups to root 'datahub' root user.",
|
||||
"state":"ACTIVE",
|
||||
"type":"METADATA",
|
||||
"editable":false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -63,6 +63,7 @@ public class Constants {
|
||||
// Group
|
||||
public static final String CORP_GROUP_KEY_ASPECT_NAME = "corpGroupKey";
|
||||
public static final String CORP_GROUP_INFO_ASPECT_NAME = "corpGroupInfo";
|
||||
public static final String CORP_GROUP_EDITABLE_INFO_ASPECT_NAME = "corpGroupEditableInfo";
|
||||
|
||||
// Dataset
|
||||
public static final String DATASET_KEY_ASPECT_NAME = "datasetKey";
|
||||
|
@ -84,17 +84,17 @@ public class PoliciesConfig {
|
||||
public static final Privilege EDIT_ENTITY_OWNERS_PRIVILEGE = Privilege.of(
|
||||
"EDIT_ENTITY_OWNERS",
|
||||
"Edit Owners",
|
||||
"The ability to add and remove owners of an asset.");
|
||||
"The ability to add and remove owners of an entity.");
|
||||
|
||||
public static final Privilege EDIT_ENTITY_DOCS_PRIVILEGE = Privilege.of(
|
||||
"EDIT_ENTITY_DOCS",
|
||||
"Edit Documentation",
|
||||
"The ability to edit documentation about an asset.");
|
||||
"Edit Description",
|
||||
"The ability to edit the description (documentation) of an entity.");
|
||||
|
||||
public static final Privilege EDIT_ENTITY_DOC_LINKS_PRIVILEGE = Privilege.of(
|
||||
"EDIT_ENTITY_DOC_LINKS",
|
||||
"Edit Links",
|
||||
"The ability to edit links associated with an asset.");
|
||||
"The ability to edit links associated with an entity.");
|
||||
|
||||
public static final Privilege EDIT_ENTITY_STATUS_PRIVILEGE = Privilege.of(
|
||||
"EDIT_ENTITY_STATUS",
|
||||
@ -114,7 +114,7 @@ public class PoliciesConfig {
|
||||
public static final Privilege EDIT_ENTITY_PRIVILEGE = Privilege.of(
|
||||
"EDIT_ENTITY",
|
||||
"Edit All",
|
||||
"The ability to edit any information about an asset. Super user privileges.");
|
||||
"The ability to edit any information about an entity. Super user privileges.");
|
||||
|
||||
public static final List<Privilege> COMMON_ENTITY_PRIVILEGES = ImmutableList.of(
|
||||
EDIT_ENTITY_TAGS_PRIVILEGE,
|
||||
@ -153,6 +153,24 @@ public class PoliciesConfig {
|
||||
"Edit Tag Color",
|
||||
"The ability to change the color of a Tag.");
|
||||
|
||||
// Group Privileges
|
||||
public static final Privilege EDIT_GROUP_MEMBERS_PRIVILEGE = Privilege.of(
|
||||
"EDIT_GROUP_MEMBERS",
|
||||
"Edit Group Members",
|
||||
"The ability to add and remove members to a group.");
|
||||
|
||||
// User Privileges
|
||||
public static final Privilege EDIT_USER_PROFILE_PRIVILEGE = Privilege.of(
|
||||
"EDIT_USER_PROFILE",
|
||||
"Edit User Profile",
|
||||
"The ability to change the user's profile including display name, bio, title, profile image, etc.");
|
||||
|
||||
// User + Group Privileges
|
||||
public static final Privilege EDIT_CONTACT_INFO_PRIVILEGE = Privilege.of(
|
||||
"EDIT_CONTACT_INFO",
|
||||
"Edit Contact Information",
|
||||
"The ability to change the contact information such as email & chat handles.");
|
||||
|
||||
public static final ResourcePrivileges DATASET_PRIVILEGES = ResourcePrivileges.of(
|
||||
"dataset",
|
||||
"Datasets",
|
||||
@ -232,6 +250,30 @@ public class PoliciesConfig {
|
||||
EDIT_ENTITY_PRIVILEGE)
|
||||
);
|
||||
|
||||
// Group Privileges
|
||||
public static final ResourcePrivileges CORP_GROUP_PRIVILEGES = ResourcePrivileges.of(
|
||||
"corpGroup",
|
||||
"Groups",
|
||||
"Groups on DataHub",
|
||||
ImmutableList.of(
|
||||
EDIT_ENTITY_OWNERS_PRIVILEGE,
|
||||
EDIT_GROUP_MEMBERS_PRIVILEGE,
|
||||
EDIT_CONTACT_INFO_PRIVILEGE,
|
||||
EDIT_ENTITY_DOCS_PRIVILEGE,
|
||||
EDIT_ENTITY_PRIVILEGE)
|
||||
);
|
||||
|
||||
// User Privileges
|
||||
public static final ResourcePrivileges CORP_USER_PRIVILEGES = ResourcePrivileges.of(
|
||||
"corpuser",
|
||||
"Users",
|
||||
"Users on DataHub",
|
||||
ImmutableList.of(
|
||||
EDIT_CONTACT_INFO_PRIVILEGE,
|
||||
EDIT_USER_PROFILE_PRIVILEGE,
|
||||
EDIT_ENTITY_PRIVILEGE)
|
||||
);
|
||||
|
||||
public static final List<ResourcePrivileges> RESOURCE_PRIVILEGES = ImmutableList.of(
|
||||
DATASET_PRIVILEGES,
|
||||
DASHBOARD_PRIVILEGES,
|
||||
@ -241,7 +283,9 @@ public class PoliciesConfig {
|
||||
TAG_PRIVILEGES,
|
||||
CONTAINER_PRIVILEGES,
|
||||
DOMAIN_PRIVILEGES,
|
||||
GLOSSARY_TERM_PRIVILEGES
|
||||
GLOSSARY_TERM_PRIVILEGES,
|
||||
CORP_GROUP_PRIVILEGES,
|
||||
CORP_USER_PRIVILEGES
|
||||
);
|
||||
|
||||
@Data
|
||||
|
@ -1073,6 +1073,144 @@ def test_add_remove_members_from_group(frontend_session):
|
||||
assert res_data["data"]["corpUser"]["relationships"]["total"] == 0
|
||||
|
||||
|
||||
@pytest.mark.dependency(
|
||||
depends=["test_healthchecks", "test_run_ingestion"]
|
||||
)
|
||||
def test_update_corp_group_properties(frontend_session):
|
||||
|
||||
group_urn = "urn:li:corpGroup:bfoo"
|
||||
|
||||
# Update Corp Group Description
|
||||
json = {
|
||||
"query": """mutation updateCorpGroupProperties($urn: String!, $input: CorpGroupUpdateInput!) {\n
|
||||
updateCorpGroupProperties(urn: $urn, input: $input) { urn } }""",
|
||||
"variables": {
|
||||
"urn": group_urn,
|
||||
"input": {
|
||||
"description": "My test description",
|
||||
"slack": "test_group_slack",
|
||||
"email": "test_group_email@email.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
response = frontend_session.post(f"{FRONTEND_ENDPOINT}/api/v2/graphql", json=json)
|
||||
response.raise_for_status()
|
||||
res_data = response.json()
|
||||
print(res_data)
|
||||
assert "error" not in res_data
|
||||
assert res_data["data"]["updateCorpGroupProperties"] is not None
|
||||
|
||||
# Verify the description has been updated
|
||||
json = {
|
||||
"query": """query corpGroup($urn: String!) {\n
|
||||
corpGroup(urn: $urn) {\n
|
||||
urn\n
|
||||
editableProperties {\n
|
||||
description\n
|
||||
slack\n
|
||||
email\n
|
||||
}\n
|
||||
}\n
|
||||
}""",
|
||||
"variables": {"urn": group_urn},
|
||||
}
|
||||
response = frontend_session.post(f"{FRONTEND_ENDPOINT}/api/v2/graphql", json=json)
|
||||
response.raise_for_status()
|
||||
res_data = response.json()
|
||||
|
||||
assert res_data
|
||||
assert "error" not in res_data
|
||||
assert res_data["data"]
|
||||
assert res_data["data"]["corpGroup"]
|
||||
assert res_data["data"]["corpGroup"]["editableProperties"]
|
||||
assert res_data["data"]["corpGroup"]["editableProperties"] == {
|
||||
"description": "My test description",
|
||||
"slack": "test_group_slack",
|
||||
"email": "test_group_email@email.com"
|
||||
}
|
||||
|
||||
# Reset the editable properties
|
||||
json = {
|
||||
"query": """mutation updateCorpGroupProperties($urn: String!, $input: UpdateCorpGroupPropertiesInput!) {\n
|
||||
updateCorpGroupProperties(urn: $urn, input: $input) }""",
|
||||
"variables": {
|
||||
"urn": group_urn,
|
||||
"input": {
|
||||
"description": "",
|
||||
"slack": "",
|
||||
"email": ""
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
response = frontend_session.post(f"{FRONTEND_ENDPOINT}/api/v2/graphql", json=json)
|
||||
response.raise_for_status()
|
||||
|
||||
@pytest.mark.dependency(
|
||||
depends=["test_healthchecks", "test_run_ingestion", "test_update_corp_group_properties"]
|
||||
)
|
||||
def test_update_corp_group_description(frontend_session):
|
||||
|
||||
group_urn = "urn:li:corpGroup:bfoo"
|
||||
|
||||
# Update Corp Group Description
|
||||
json = {
|
||||
"query": """mutation updateDescription($input: DescriptionUpdateInput!) {\n
|
||||
updateDescription(input: $input) }""",
|
||||
"variables": {
|
||||
"input": {
|
||||
"description": "My test description",
|
||||
"resourceUrn": group_urn
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
response = frontend_session.post(f"{FRONTEND_ENDPOINT}/api/v2/graphql", json=json)
|
||||
response.raise_for_status()
|
||||
res_data = response.json()
|
||||
print(res_data)
|
||||
assert "error" not in res_data
|
||||
assert res_data["data"]["updateDescription"] is True
|
||||
|
||||
# Verify the description has been updated
|
||||
json = {
|
||||
"query": """query corpGroup($urn: String!) {\n
|
||||
corpGroup(urn: $urn) {\n
|
||||
urn\n
|
||||
editableProperties {\n
|
||||
description\n
|
||||
}\n
|
||||
}\n
|
||||
}""",
|
||||
"variables": {"urn": group_urn},
|
||||
}
|
||||
response = frontend_session.post(f"{FRONTEND_ENDPOINT}/api/v2/graphql", json=json)
|
||||
response.raise_for_status()
|
||||
res_data = response.json()
|
||||
|
||||
assert res_data
|
||||
assert "error" not in res_data
|
||||
assert res_data["data"]
|
||||
assert res_data["data"]["corpGroup"]
|
||||
assert res_data["data"]["corpGroup"]["editableProperties"]
|
||||
assert res_data["data"]["corpGroup"]["editableProperties"]["description"] == "My test description"
|
||||
|
||||
# Reset Corp Group Description
|
||||
json = {
|
||||
"query": """mutation updateDescription($input: DescriptionUpdateInput!) {\n
|
||||
updateDescription(input: $input) }""",
|
||||
"variables": {
|
||||
"input": {
|
||||
"description": "",
|
||||
"resourceUrn": group_urn
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
response = frontend_session.post(f"{FRONTEND_ENDPOINT}/api/v2/graphql", json=json)
|
||||
response.raise_for_status()
|
||||
|
||||
@pytest.mark.dependency(
|
||||
depends=[
|
||||
"test_healthchecks",
|
||||
@ -1108,6 +1246,7 @@ def test_remove_user(frontend_session):
|
||||
res_data = response.json()
|
||||
|
||||
assert res_data
|
||||
assert "error" not in res_data
|
||||
assert res_data["data"]
|
||||
assert res_data["data"]["corpUser"]
|
||||
assert res_data["data"]["corpUser"]["properties"] is None
|
||||
|
Loading…
x
Reference in New Issue
Block a user