Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(auth) - New privilege for Associate tags #8644

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c923b8b
New privilege for Associate tags
Aug 16, 2023
f60a413
Add Associate_tags policy for admin and editors
Aug 16, 2023
0da4478
Merge branch 'master' into master
mkamalas Aug 16, 2023
5067773
Set Tag Authorization error as 403 error code
Aug 18, 2023
0ee58b0
Merge branch 'master' into master
mkamalas Aug 18, 2023
e03e2af
Merge branch 'master' into master
mkamalas Aug 20, 2023
67a991f
Merge branch 'master' into master
mkamalas Aug 22, 2023
df12d4a
Merge branch 'master' into master
mkamalas Aug 23, 2023
e5216d4
Merge branch 'master' into master
mkamalas Aug 23, 2023
7f002d5
Merge branch 'master' into master
mkamalas Aug 23, 2023
c35550a
Merge branch 'master' into master
mkamalas Aug 24, 2023
2750e54
Add Associate_Tags privilege for datahub
Aug 27, 2023
15d56b1
Merge branch 'master' into master
mkamalas Aug 27, 2023
80efa94
For NOT_EQUALS, check all resources match the condition
mkamalas Sep 11, 2023
c60d9ab
Merge branch 'master' into master
mkamalas Aug 8, 2024
bf81623
Merge from upstream
mkamalas Aug 27, 2024
193a093
Format and linting fixes
mkamalas Aug 30, 2024
74074fb
Merge branch 'datahub-project:master' into master
mkamalas Aug 30, 2024
f37a807
Fix documentation for NOT_EQUALS
mkamalas Aug 30, 2024
fae9dd0
Merge branch 'master' into master
mkamalas Sep 4, 2024
4c1aa72
Resolve merge conflict issue
mkamalas Sep 9, 2024
f703c95
Update metadata-service/auth-impl/src/main/java/com/datahub/authoriza…
mkamalas Sep 18, 2024
8b43d10
Merge branch 'master' into master
mkamalas Sep 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public enum DataHubGraphQLErrorCode {
BAD_REQUEST(400),
UNAUTHORIZED(403),
NOT_FOUND(404),
UNAUTHORIZED_TAG_ERROR(405),
Copy link
Collaborator

@anshbansal anshbansal Aug 16, 2023

Choose a reason for hiding this comment

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

HTTP 405 is well defined in general tech. Let's not override the standard HTTP codes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reverted back the error code to 403 with different error message

SERVER_ERROR(500);

private final int _code;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.linkedin.datahub.graphql.exception;


/**
* Exception thrown when update fails due to ASSOCIATE_TAG privilege.
*/
public class TagAuthorizationException extends DataHubGraphQLException {

public TagAuthorizationException(String message) {
super(message, DataHubGraphQLErrorCode.UNAUTHORIZED_TAG_ERROR);
}

public TagAuthorizationException(String message, Throwable cause) {
super(message, DataHubGraphQLErrorCode.UNAUTHORIZED_TAG_ERROR);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.linkedin.common.urn.Urn;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.exception.TagAuthorizationException;
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
import com.linkedin.datahub.graphql.generated.TagAssociationInput;
import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils;
Expand Down Expand Up @@ -35,6 +36,10 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator.");
}

if (!LabelUtils.isAuthorizedToAssociateTag(environment.getContext(), tagUrn)) {
throw new TagAuthorizationException("Only users granted permission to this tag can assign or remove it");
}

return CompletableFuture.supplyAsync(() -> {
LabelUtils.validateResourceAndLabel(
tagUrn,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.linkedin.common.urn.UrnUtils;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.exception.TagAuthorizationException;
import com.linkedin.datahub.graphql.generated.AddTagsInput;
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils;
Expand Down Expand Up @@ -43,6 +44,12 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator.");
}

tagUrns.forEach((tagUrn) -> {
if (!LabelUtils.isAuthorizedToAssociateTag(environment.getContext(), tagUrn)) {
throw new TagAuthorizationException("Only users granted permission to this tag can assign or remove it");
}
});

LabelUtils.validateResourceAndLabel(
tagUrns,
targetUrn,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.linkedin.common.urn.UrnUtils;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.exception.TagAuthorizationException;
import com.linkedin.datahub.graphql.generated.BatchAddTagsInput;
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils;
Expand Down Expand Up @@ -45,7 +46,7 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
return CompletableFuture.supplyAsync(() -> {

// First, validate the batch
validateTags(tagUrns);
validateTags(tagUrns, context);

if (resources.size() == 1 && resources.get(0).getSubResource() != null) {
return handleAddTagsToSingleSchemaField(context, resources, tagUrns);
Expand Down Expand Up @@ -117,9 +118,13 @@ private Boolean attemptBatchAddTagsWithSiblings(
}
}

private void validateTags(List<Urn> tagUrns) {
private void validateTags(List<Urn> tagUrns, QueryContext context) {
for (Urn tagUrn : tagUrns) {
LabelUtils.validateLabel(tagUrn, Constants.TAG_ENTITY_NAME, _entityService);

if (!LabelUtils.isAuthorizedToAssociateTag(context, tagUrn)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Auth calls should come first before sending validation operations to the entity service, ideally done as soon as possible before any business logic occurs to short circuit.

throw new TagAuthorizationException("Only users granted permission to this tag can assign or remove it");
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.linkedin.common.urn.UrnUtils;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.exception.TagAuthorizationException;
import com.linkedin.datahub.graphql.generated.BatchRemoveTagsInput;
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils;
Expand Down Expand Up @@ -37,6 +38,9 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
return CompletableFuture.supplyAsync(() -> {

// First, validate the batch
validateTags(tagUrns, context);

// Next, validate the batch
validateInputResources(resources, context);

try {
Expand All @@ -50,6 +54,14 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
});
}

private void validateTags(List<Urn> tagUrns, QueryContext context) {
for (Urn tagUrn : tagUrns) {
if (!LabelUtils.isAuthorizedToAssociateTag(context, tagUrn)) {
throw new TagAuthorizationException("Only users granted permission to this tag can assign or remove it");
}
}
}

private void validateInputResources(List<ResourceRefInput> resources, QueryContext context) {
for (ResourceRefInput resource : resources) {
validateInputResource(resource, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.linkedin.common.urn.Urn;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.exception.TagAuthorizationException;
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
import com.linkedin.datahub.graphql.generated.TagAssociationInput;
import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils;
Expand Down Expand Up @@ -33,6 +34,9 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
if (!LabelUtils.isAuthorizedToUpdateTags(environment.getContext(), targetUrn, input.getSubResource())) {
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator.");
}
if (!LabelUtils.isAuthorizedToAssociateTag(environment.getContext(), tagUrn)) {
throw new TagAuthorizationException("Only users granted permission to this tag can assign or remove it");
}

return CompletableFuture.supplyAsync(() -> {
LabelUtils.validateResourceAndLabel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,21 @@ public static boolean isAuthorizedToUpdateTags(@Nonnull QueryContext context, Ur
orPrivilegeGroups);
}

public static boolean isAuthorizedToAssociateTag(@Nonnull QueryContext context, Urn targetUrn) {

// Decide whether the current principal should be allowed to associate the tag
final DisjunctivePrivilegeGroup orPrivilegeGroups = new DisjunctivePrivilegeGroup(ImmutableList.of(
new ConjunctivePrivilegeGroup(ImmutableList.of(PoliciesConfig.ASSOCIATE_TAGS_PRIVILEGE.getType()))
Copy link
Collaborator

Choose a reason for hiding this comment

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

hm I'm a little confused here - so what's the benefit of this new privilege when we have the EDIT_ENTITY_TAGS_PRIVILEGE privilege already that seems to be doing type of thing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hm I'm a little confused here - so what's the benefit of this new privilege when we have the EDIT_ENTITY_TAGS_PRIVILEGE privilege already that seems to be doing type of thing?

Please check my response to your other comment on the need for this new privilege

));

return AuthorizationUtils.isAuthorized(
context.getAuthorizer(),
context.getActorUrn(),
targetUrn.getEntityType(),
targetUrn.toString(),
orPrivilegeGroups);
}

public static boolean isAuthorizedToUpdateTerms(@Nonnull QueryContext context, Urn targetUrn, String subResource) {

Boolean isTargetingSchema = subResource != null && subResource.length() > 0;
Expand Down
4 changes: 4 additions & 0 deletions datahub-graphql-core/src/main/resources/entity.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -8200,6 +8200,10 @@ enum PolicyMatchCondition {
Whether the field matches the value
"""
EQUALS
"""
Whether the field does not the value
"""
NOT_EQUALS
}

"""
Expand Down
7 changes: 7 additions & 0 deletions datahub-web-react/src/app/entity/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ export const handleBatchError = (urns, e, defaultMessage) => {
duration: 3,
};
}
if (urns.length > 1 && getGraphqlErrorCode(e) === 405) {
return {
content:
'Only users granted permission to this tag can assign or remove it. The bulk edit being performed will not be saved. ',
duration: 3,
};
}
return defaultMessage;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ import { Link } from 'react-router-dom';
import { Button, Divider, Modal, Tag, Typography } from 'antd';
import styled from 'styled-components';
import { useEntityRegistry } from '../../useEntityRegistry';
import { Maybe, Policy, PolicyState, PolicyType } from '../../../types.generated';
import { Maybe, Policy, PolicyMatchCondition, PolicyState, PolicyType } from '../../../types.generated';
import { useAppConfig } from '../../useAppConfig';
import { convertLegacyResourceFilter, getFieldValues, mapResourceTypeToDisplayName } from './policyUtils';
import {
convertLegacyResourceFilter,
getFieldCondition,
getFieldValues,
mapResourceTypeToDisplayName,
} from './policyUtils';
import AvatarsGroup from '../AvatarsGroup';

type PrivilegeOptionType = {
Expand Down Expand Up @@ -69,6 +74,7 @@ export default function PolicyDetailsModal({ policy, visible, onClose, privilege
const resources = convertLegacyResourceFilter(policy?.resources);
const resourceTypes = getFieldValues(resources?.filter, 'RESOURCE_TYPE') || [];
const resourceEntities = getFieldValues(resources?.filter, 'RESOURCE_URN') || [];
const policyMatchCondition = getFieldCondition(resources?.filter, 'RESOURCE_URN');
const domains = getFieldValues(resources?.filter, 'DOMAIN') || [];

const {
Expand Down Expand Up @@ -157,6 +163,13 @@ export default function PolicyDetailsModal({ policy, visible, onClose, privilege
);
})) || <PoliciesTag>All</PoliciesTag>}
</div>
<div>
<Typography.Title level={5}>Asset Condition</Typography.Title>
<ThinDivider />
<PoliciesTag>
{policyMatchCondition === PolicyMatchCondition.NotEquals ? 'Excludes' : 'Includes'}
</PoliciesTag>
</div>
<div>
<Typography.Title level={5}>Assets</Typography.Title>
<ThinDivider />
Expand Down
Loading
Loading