From bac61e029151e0bca28c7081dd213c105c08d31d Mon Sep 17 00:00:00 2001 From: aditya-radhakrishnan Date: Mon, 19 Sep 2022 19:39:32 -0700 Subject: [PATCH] Some refactoring --- .../datahub/graphql/GmsGraphQLEngine.java | 5 - .../CreateNativeUserInviteTokenResolver.java | 43 ----- .../GetNativeUserInviteTokenResolver.java | 42 ----- .../src/main/resources/entity.graphql | 10 -- ...eateNativeUserInviteTokenResolverTest.java | 50 ------ .../GetNativeUserInviteTokenResolverTest.java | 50 ------ datahub-web-react/src/app/auth/SignUp.tsx | 26 +-- .../identity/user/ViewInviteTokenModal.tsx | 163 ++++++++++-------- datahub-web-react/src/graphql/user.graphql | 12 -- .../linkedin/metadata/entity/AspectUtils.java | 20 ++- .../invite/InviteTokenService.java | 16 +- .../user/NativeUserService.java | 14 +- .../authorization/role/RoleService.java | 13 +- .../auth/InviteTokenServiceFactory.java | 2 +- .../gms/factory/auth/RoleServiceFactory.java | 2 +- 15 files changed, 138 insertions(+), 330 deletions(-) delete mode 100644 datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/user/CreateNativeUserInviteTokenResolver.java delete mode 100644 datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/user/GetNativeUserInviteTokenResolver.java delete mode 100644 datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/user/CreateNativeUserInviteTokenResolverTest.java delete mode 100644 datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/user/GetNativeUserInviteTokenResolverTest.java diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java index a97f8292d47a63..c292c0cd3acc34 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java @@ -202,9 +202,7 @@ import com.linkedin.datahub.graphql.resolvers.type.PlatformSchemaUnionTypeResolver; import com.linkedin.datahub.graphql.resolvers.type.ResultsTypeResolver; import com.linkedin.datahub.graphql.resolvers.type.TimeSeriesAspectInterfaceTypeResolver; -import com.linkedin.datahub.graphql.resolvers.user.CreateNativeUserInviteTokenResolver; import com.linkedin.datahub.graphql.resolvers.user.CreateNativeUserResetTokenResolver; -import com.linkedin.datahub.graphql.resolvers.user.GetNativeUserInviteTokenResolver; import com.linkedin.datahub.graphql.resolvers.user.ListUsersResolver; import com.linkedin.datahub.graphql.resolvers.user.RemoveUserResolver; import com.linkedin.datahub.graphql.resolvers.user.UpdateUserStatusResolver; @@ -674,7 +672,6 @@ private void configureQueryResolvers(final RuntimeWiring.Builder builder) { .dataFetcher("getRootGlossaryTerms", new GetRootGlossaryTermsResolver(this.entityClient)) .dataFetcher("getRootGlossaryNodes", new GetRootGlossaryNodesResolver(this.entityClient)) .dataFetcher("entityExists", new EntityExistsResolver(this.entityService)) - .dataFetcher("getNativeUserInviteToken", new GetNativeUserInviteTokenResolver(this.inviteTokenService)) .dataFetcher("entity", getEntityResolver()) .dataFetcher("entities", getEntitiesResolver()) .dataFetcher("listRoles", new ListRolesResolver(this.entityClient)) @@ -794,8 +791,6 @@ private void configureMutationResolvers(final RuntimeWiring.Builder builder) { .dataFetcher("updateName", new UpdateNameResolver(entityService)) .dataFetcher("addRelatedTerms", new AddRelatedTermsResolver(this.entityService)) .dataFetcher("removeRelatedTerms", new RemoveRelatedTermsResolver(this.entityService)) - .dataFetcher("createNativeUserInviteToken", - new CreateNativeUserInviteTokenResolver(this.inviteTokenService)) .dataFetcher("createNativeUserResetToken", new CreateNativeUserResetTokenResolver(this.nativeUserService)) .dataFetcher("batchUpdateSoftDeleted", new BatchUpdateSoftDeletedResolver(this.entityService)) .dataFetcher("updateUserSetting", new UpdateUserSettingResolver(this.entityService)) diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/user/CreateNativeUserInviteTokenResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/user/CreateNativeUserInviteTokenResolver.java deleted file mode 100644 index 59f665946d80ab..00000000000000 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/user/CreateNativeUserInviteTokenResolver.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.linkedin.datahub.graphql.resolvers.user; - -import com.datahub.authentication.Authentication; -import com.datahub.authentication.invite.InviteTokenService; -import com.linkedin.datahub.graphql.QueryContext; -import com.linkedin.datahub.graphql.exception.AuthorizationException; -import com.linkedin.datahub.graphql.generated.InviteToken; -import graphql.schema.DataFetcher; -import graphql.schema.DataFetchingEnvironment; -import java.util.concurrent.CompletableFuture; -import lombok.RequiredArgsConstructor; - -import static com.linkedin.datahub.graphql.authorization.AuthorizationUtils.*; - -/** - * Resolver responsible for creating an invite token that Admins can share with prospective users to create native - * user accounts. - */ -@RequiredArgsConstructor -@Deprecated -public class CreateNativeUserInviteTokenResolver implements DataFetcher> { - private final InviteTokenService _inviteTokenService; - - @Override - public CompletableFuture get(final DataFetchingEnvironment environment) throws Exception { - final QueryContext context = environment.getContext(); - if (!canManagePolicies(context)) { - throw new AuthorizationException( - "Unauthorized to create invite tokens. Please contact your DataHub administrator if this needs corrective action."); - } - - final Authentication authentication = context.getAuthentication(); - - return CompletableFuture.supplyAsync(() -> { - - try { - return new InviteToken(_inviteTokenService.getInviteToken(null, true, authentication)); - } catch (Exception e) { - throw new RuntimeException("Failed to create new invite token"); - } - }); - } -} diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/user/GetNativeUserInviteTokenResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/user/GetNativeUserInviteTokenResolver.java deleted file mode 100644 index c3517b61fcedf1..00000000000000 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/user/GetNativeUserInviteTokenResolver.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.linkedin.datahub.graphql.resolvers.user; - -import com.datahub.authentication.Authentication; -import com.datahub.authentication.invite.InviteTokenService; -import com.linkedin.datahub.graphql.QueryContext; -import com.linkedin.datahub.graphql.exception.AuthorizationException; -import com.linkedin.datahub.graphql.generated.InviteToken; -import graphql.schema.DataFetcher; -import graphql.schema.DataFetchingEnvironment; -import java.util.concurrent.CompletableFuture; -import lombok.RequiredArgsConstructor; - -import static com.linkedin.datahub.graphql.authorization.AuthorizationUtils.*; - -/** - * Resolver responsible for getting an existing invite token that Admins can share with prospective users to create - * native user accounts. If the invite token does not already exist, this resolver will create a new one. - */ -@RequiredArgsConstructor -@Deprecated -public class GetNativeUserInviteTokenResolver implements DataFetcher> { - private final InviteTokenService _inviteTokenService; - - @Override - public CompletableFuture get(final DataFetchingEnvironment environment) throws Exception { - final QueryContext context = environment.getContext(); - if (!canManagePolicies(context)) { - throw new AuthorizationException( - "Unauthorized to get invite tokens. Please contact your DataHub administrator if this needs corrective action."); - } - - final Authentication authentication = context.getAuthentication(); - - return CompletableFuture.supplyAsync(() -> { - try { - return new InviteToken(_inviteTokenService.getInviteToken(null, false, authentication)); - } catch (Exception e) { - throw new RuntimeException("Failed to get invite token"); - } - }); - } -} diff --git a/datahub-graphql-core/src/main/resources/entity.graphql b/datahub-graphql-core/src/main/resources/entity.graphql index c5899d0d80a3e5..8a231eaa2854e6 100644 --- a/datahub-graphql-core/src/main/resources/entity.graphql +++ b/datahub-graphql-core/src/main/resources/entity.graphql @@ -164,11 +164,6 @@ type Query { """ entityExists(urn: String!): Boolean - """ - Gets the current invite token. If the optional regenerate param is set to true, generate a new invite token. - """ - getNativeUserInviteToken: InviteToken @deprecated(reason: "Use getInviteToken instead") - """ Gets an entity based on its urn """ @@ -494,11 +489,6 @@ type Mutation { """ removeRelatedTerms(input: RelatedTermsInput!): Boolean - """ - Generates an invite token that can be shared with prospective users to create their accounts. - """ - createNativeUserInviteToken: InviteToken @deprecated(reason: "Use createInviteToken instead") - """ Generates a token that can be shared with existing native users to reset their credentials. """ diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/user/CreateNativeUserInviteTokenResolverTest.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/user/CreateNativeUserInviteTokenResolverTest.java deleted file mode 100644 index e71bc0fcd91094..00000000000000 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/user/CreateNativeUserInviteTokenResolverTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.linkedin.datahub.graphql.resolvers.user; - -import com.datahub.authentication.Authentication; -import com.datahub.authentication.invite.InviteTokenService; -import com.linkedin.datahub.graphql.QueryContext; -import graphql.schema.DataFetchingEnvironment; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import static com.linkedin.datahub.graphql.TestUtils.*; -import static org.mockito.Mockito.*; -import static org.testng.Assert.*; - - -public class CreateNativeUserInviteTokenResolverTest { - - private static final String INVITE_TOKEN = "inviteToken"; - - private InviteTokenService _inviteTokenService; - private CreateNativeUserInviteTokenResolver _resolver; - private DataFetchingEnvironment _dataFetchingEnvironment; - private Authentication _authentication; - - @BeforeMethod - public void setupTest() { - _inviteTokenService = mock(InviteTokenService.class); - _dataFetchingEnvironment = mock(DataFetchingEnvironment.class); - _authentication = mock(Authentication.class); - - _resolver = new CreateNativeUserInviteTokenResolver(_inviteTokenService); - } - - @Test - public void testFailsDenyContext() { - QueryContext mockContext = getMockDenyContext(); - when(_dataFetchingEnvironment.getContext()).thenReturn(mockContext); - - assertThrows(() -> _resolver.get(_dataFetchingEnvironment).join()); - } - - @Test - public void testPasses() throws Exception { - QueryContext mockContext = getMockAllowContext(); - when(_dataFetchingEnvironment.getContext()).thenReturn(mockContext); - when(mockContext.getAuthentication()).thenReturn(_authentication); - when(_inviteTokenService.getInviteToken(any(), eq(true), eq(_authentication))).thenReturn(INVITE_TOKEN); - - assertEquals(INVITE_TOKEN, _resolver.get(_dataFetchingEnvironment).join().getInviteToken()); - } -} diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/user/GetNativeUserInviteTokenResolverTest.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/user/GetNativeUserInviteTokenResolverTest.java deleted file mode 100644 index 80cc0436f4904b..00000000000000 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/user/GetNativeUserInviteTokenResolverTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.linkedin.datahub.graphql.resolvers.user; - -import com.datahub.authentication.Authentication; -import com.datahub.authentication.invite.InviteTokenService; -import com.linkedin.datahub.graphql.QueryContext; -import graphql.schema.DataFetchingEnvironment; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import static com.linkedin.datahub.graphql.TestUtils.*; -import static org.mockito.Mockito.*; -import static org.testng.Assert.*; - - -public class GetNativeUserInviteTokenResolverTest { - - private static final String INVITE_TOKEN = "inviteToken"; - - private InviteTokenService _inviteTokenService; - private GetNativeUserInviteTokenResolver _resolver; - private DataFetchingEnvironment _dataFetchingEnvironment; - private Authentication _authentication; - - @BeforeMethod - public void setupTest() { - _inviteTokenService = mock(InviteTokenService.class); - _dataFetchingEnvironment = mock(DataFetchingEnvironment.class); - _authentication = mock(Authentication.class); - - _resolver = new GetNativeUserInviteTokenResolver(_inviteTokenService); - } - - @Test - public void testFailsDenyContext() { - QueryContext mockContext = getMockDenyContext(); - when(_dataFetchingEnvironment.getContext()).thenReturn(mockContext); - - assertThrows(() -> _resolver.get(_dataFetchingEnvironment).join()); - } - - @Test - public void testPasses() throws Exception { - QueryContext mockContext = getMockAllowContext(); - when(_dataFetchingEnvironment.getContext()).thenReturn(mockContext); - when(mockContext.getAuthentication()).thenReturn(_authentication); - when(_inviteTokenService.getInviteToken(any(), eq(false), eq(_authentication))).thenReturn(INVITE_TOKEN); - - assertEquals(INVITE_TOKEN, _resolver.get(_dataFetchingEnvironment).join().getInviteToken()); - } -} diff --git a/datahub-web-react/src/app/auth/SignUp.tsx b/datahub-web-react/src/app/auth/SignUp.tsx index 386399427d6978..17a271063235d2 100644 --- a/datahub-web-react/src/app/auth/SignUp.tsx +++ b/datahub-web-react/src/app/auth/SignUp.tsx @@ -1,9 +1,10 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { Input, Button, Form, message, Image, Select } from 'antd'; import { UserOutlined, LockOutlined } from '@ant-design/icons'; import { useReactiveVar } from '@apollo/client'; import styled, { useTheme } from 'styled-components/macro'; -import { Redirect } from 'react-router'; +// import { Redirect } from 'react-router'; +import { useHistory } from 'react-router-dom'; import styles from './login.module.css'; import { Message } from '../shared/Message'; import { isLoggedInVar } from './checkAuthStatus'; @@ -57,6 +58,7 @@ const TitleSelector = styled(Select)` export type SignUpProps = Record; export const SignUp: React.VFC = () => { + const history = useHistory(); const isLoggedIn = useReactiveVar(isLoggedInVar); const inviteToken = useGetInviteTokenFromUrlParams(); @@ -66,7 +68,7 @@ export const SignUp: React.VFC = () => { const { refreshContext } = useAppConfig(); const [acceptRoleMutation] = useAcceptRoleMutation(); - const acceptRole = useCallback(() => { + const acceptRole = () => { acceptRoleMutation({ variables: { input: { @@ -89,7 +91,7 @@ export const SignUp: React.VFC = () => { duration: 3, }); }); - }, [acceptRoleMutation, inviteToken]); + }; const handleSignUp = useCallback( (values: FormValues) => { @@ -114,7 +116,6 @@ export const SignUp: React.VFC = () => { } isLoggedInVar(true); refreshContext(); - acceptRole(); analytics.event({ type: EventType.SignUpEvent, title: values.title }); return Promise.resolve(); }) @@ -123,12 +124,15 @@ export const SignUp: React.VFC = () => { }) .finally(() => setLoading(false)); }, - [refreshContext, inviteToken, acceptRole], + [refreshContext, inviteToken], ); - if (isLoggedIn && !loading) { - return ; - } + useEffect(() => { + if (isLoggedIn && !loading) { + acceptRole(); + history.push(PageRoutes.ROOT); + } + }); return (
@@ -140,10 +144,10 @@ export const SignUp: React.VFC = () => { {loading && }
Email} + label={} > } data-testid="email" /> diff --git a/datahub-web-react/src/app/identity/user/ViewInviteTokenModal.tsx b/datahub-web-react/src/app/identity/user/ViewInviteTokenModal.tsx index fd15e24fa3eb4d..a731be26b46528 100644 --- a/datahub-web-react/src/app/identity/user/ViewInviteTokenModal.tsx +++ b/datahub-web-react/src/app/identity/user/ViewInviteTokenModal.tsx @@ -1,14 +1,15 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import * as QueryString from 'query-string'; import { useLocation } from 'react-router'; -import { RedoOutlined, UserOutlined } from '@ant-design/icons'; -import { Button, Col, message, Modal, Select, Typography } from 'antd'; -import styled from 'styled-components'; +import { UserOutlined } from '@ant-design/icons'; +import { Button, message, Modal, Select, Tooltip, Typography } from 'antd'; +import styled from 'styled-components/macro'; import { PageRoutes } from '../../../conf/Global'; import { useGetInviteTokenQuery, useListRolesQuery } from '../../../graphql/role.generated'; import { DataHubRole } from '../../../types.generated'; import { mapRoleIcon } from './UserUtils'; import { useCreateInviteTokenMutation } from '../../../graphql/mutations.generated'; +import { ANTD_GRAY } from '../../entity/shared/constants'; const ModalSection = styled.div` display: flex; @@ -16,7 +17,7 @@ const ModalSection = styled.div` padding-bottom: 12px; `; -const ModalSectionHeader = styled(Typography.Text)` +const ModalSectionFooter = styled(Typography.Paragraph)` &&&& { padding: 0px; margin: 0px; @@ -24,27 +25,36 @@ const ModalSectionHeader = styled(Typography.Text)` } `; -const ModalSectionParagraph = styled(Typography.Paragraph)` - &&&& { - padding: 0px; - margin: 0px; - } -`; - -const InviteLinkParagraph = styled(Typography.Paragraph)` +const InviteLinkDiv = styled.div` + margin-top: -12px; display: flex; flex-direction: row; justify-content: flex-start; + gap: 10px; + align-items: center; `; -const CreateInviteTokenButton = styled(Button)` - display: inline-block; - width: 20px; - margin-left: -6px; +const CopyText = styled(Typography.Text)` + display: flex; + gap: 10px; + align-items: center; +`; + +const CopyButton = styled(Button)` + background-color: #1890ff; + color: white; + + &:focus:not(:hover) { + background-color: #1890ff; + color: white; + } +`; + +const RefreshButton = styled(Button)` + color: ${ANTD_GRAY[7]}; `; const RoleSelect = styled(Select)` - margin-top: 11px; min-width: 105px; `; @@ -66,8 +76,6 @@ export default function ViewInviteTokenModal({ visible, onClose }: Props) { const [query, setQuery] = useState(undefined); useEffect(() => setQuery(paramsQuery), [paramsQuery]); const [selectedRole, setSelectedRole] = useState(); - const [isInviteTokenCreated, setIsInviteTokenCreated] = useState(false); - const [createInviteTokenMutation, { data: createInviteTokenData }] = useCreateInviteTokenMutation(); // Code related to listing role options and selecting a role const noRoleText = 'No Role'; @@ -99,17 +107,27 @@ export default function ViewInviteTokenModal({ visible, onClose }: Props) { ); }); - const onSelectRole = (roleUrn: string) => { - const roleFromMap: DataHubRole = rolesMap.get(roleUrn) as DataHubRole; - setSelectedRole(roleFromMap); - }; - - // Code related to creating an invite token + // Code related to getting or creating an invite token const { data: getInviteTokenData } = useGetInviteTokenQuery({ skip: !visible, variables: { input: { roleUrn: selectedRole?.urn } }, }); + const [inviteToken, setInviteToken] = useState(getInviteTokenData?.getInviteToken?.inviteToken || ''); + + const [createInviteTokenMutation] = useCreateInviteTokenMutation(); + + useEffect(() => { + if (getInviteTokenData?.getInviteToken?.inviteToken) { + setInviteToken(getInviteTokenData.getInviteToken.inviteToken); + } + }, [getInviteTokenData]); + + const onSelectRole = (roleUrn: string) => { + const roleFromMap: DataHubRole = rolesMap.get(roleUrn) as DataHubRole; + setSelectedRole(roleFromMap); + }; + const createInviteToken = (roleUrn?: string) => { createInviteTokenMutation({ variables: { @@ -118,9 +136,10 @@ export default function ViewInviteTokenModal({ visible, onClose }: Props) { }, }, }) - .then(({ errors }) => { + .then(({ data, errors }) => { if (!errors) { - setIsInviteTokenCreated(true); + setInviteToken(data?.createInviteToken?.inviteToken || ''); + message.success('Generated new invite link'); } }) .catch((e) => { @@ -132,63 +151,65 @@ export default function ViewInviteTokenModal({ visible, onClose }: Props) { }); }; - const inviteToken = useMemo(() => { - if (isInviteTokenCreated) { - return createInviteTokenData?.createInviteToken?.inviteToken; - } - return getInviteTokenData?.getInviteToken?.inviteToken || ''; - }, [getInviteTokenData, createInviteTokenData, isInviteTokenCreated]); - const inviteLink = `${baseUrl}${PageRoutes.SIGN_UP}?invite_token=${inviteToken}`; return ( - Invite Users + Share Invite Link } visible={visible} onCancel={onClose} > - Role for users joining with this invite link - - - - - {noRoleText} - - } - value={selectedRole?.urn || undefined} - onChange={(e) => onSelectRole(e as string)} - > - - {mapRoleIcon(noRoleText)} + + + {noRoleText} - - {roleSelectOptions()} - - - - -
{inviteLink}
-
- -
-
- - Generate a new link - - Generate a new invite link! Note, any old links will cease to be active. - - createInviteToken(selectedRole?.urn)} size="small" type="text"> - - + + } + value={selectedRole?.urn || undefined} + onChange={(e) => onSelectRole(e as string)} + > + + {mapRoleIcon(noRoleText)} + {noRoleText} + + {roleSelectOptions()} + + +
{inviteLink}
+
+ + { + navigator.clipboard.writeText(inviteLink); + message.success('Copied invite link to clipboard'); + }} + > + COPY + + + + { + createInviteToken(selectedRole?.urn); + }} + > + REFRESH + + + + + Copy an invite link to send to your users. When they join, users will be automatically assigned to + the selected role. +
); diff --git a/datahub-web-react/src/graphql/user.graphql b/datahub-web-react/src/graphql/user.graphql index 62b299612bab0e..8e3d33e2bc34e9 100644 --- a/datahub-web-react/src/graphql/user.graphql +++ b/datahub-web-react/src/graphql/user.graphql @@ -181,18 +181,6 @@ mutation updateCorpUserProperties($urn: String!, $input: CorpUserUpdateInput!) { } } -mutation createNativeUserInviteToken { - createNativeUserInviteToken { - inviteToken - } -} - -query getNativeUserInviteToken { - getNativeUserInviteToken { - inviteToken - } -} - mutation createNativeUserResetToken($input: CreateNativeUserResetTokenInput!) { createNativeUserResetToken(input: $input) { resetToken diff --git a/metadata-io/src/main/java/com/linkedin/metadata/entity/AspectUtils.java b/metadata-io/src/main/java/com/linkedin/metadata/entity/AspectUtils.java index c26fa7420c03f2..330808f8b38917 100644 --- a/metadata-io/src/main/java/com/linkedin/metadata/entity/AspectUtils.java +++ b/metadata-io/src/main/java/com/linkedin/metadata/entity/AspectUtils.java @@ -1,10 +1,12 @@ package com.linkedin.metadata.entity; +import com.datahub.authentication.Authentication; import com.google.common.collect.ImmutableSet; import com.linkedin.common.urn.Urn; import com.linkedin.data.template.RecordTemplate; import com.linkedin.entity.Aspect; import com.linkedin.entity.EntityResponse; +import com.linkedin.entity.client.EntityClient; import com.linkedin.events.metadata.ChangeType; import com.linkedin.metadata.utils.EntityKeyUtils; import com.linkedin.metadata.utils.GenericRecordUtils; @@ -17,11 +19,10 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; -import com.linkedin.entity.client.EntityClient; -import com.datahub.authentication.Authentication; import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; + @Slf4j public class AspectUtils { @@ -83,9 +84,7 @@ private static MetadataChangeProposal getProposalFromAspect(String aspectName, R } public static MetadataChangeProposal buildMetadataChangeProposal( - @Nonnull Urn urn, - @Nonnull String aspectName, - @Nonnull RecordTemplate aspect) { + @Nonnull Urn urn, @Nonnull String aspectName, @Nonnull RecordTemplate aspect) { final MetadataChangeProposal proposal = new MetadataChangeProposal(); proposal.setEntityUrn(urn); proposal.setEntityType(urn.getEntityType()); @@ -94,4 +93,15 @@ public static MetadataChangeProposal buildMetadataChangeProposal( proposal.setChangeType(ChangeType.UPSERT); return proposal; } + + public static MetadataChangeProposal buildMetadataChangeProposal(@Nonnull String entityType, + @Nonnull RecordTemplate keyAspect, @Nonnull String aspectName, @Nonnull RecordTemplate aspect) { + final MetadataChangeProposal proposal = new MetadataChangeProposal(); + proposal.setEntityType(entityType); + proposal.setEntityKeyAspect(GenericRecordUtils.serializeAspect(keyAspect)); + proposal.setAspectName(aspectName); + proposal.setAspect(GenericRecordUtils.serializeAspect(aspect)); + proposal.setChangeType(ChangeType.UPSERT); + return proposal; + } } \ No newline at end of file diff --git a/metadata-service/auth-impl/src/main/java/com/datahub/authentication/invite/InviteTokenService.java b/metadata-service/auth-impl/src/main/java/com/datahub/authentication/invite/InviteTokenService.java index 0417fc76d86352..b4e7a213877140 100644 --- a/metadata-service/auth-impl/src/main/java/com/datahub/authentication/invite/InviteTokenService.java +++ b/metadata-service/auth-impl/src/main/java/com/datahub/authentication/invite/InviteTokenService.java @@ -5,7 +5,6 @@ import com.linkedin.entity.EntityResponse; import com.linkedin.entity.EnvelopedAspectMap; import com.linkedin.entity.client.EntityClient; -import com.linkedin.events.metadata.ChangeType; import com.linkedin.metadata.key.InviteTokenKey; import com.linkedin.metadata.query.filter.Condition; import com.linkedin.metadata.query.filter.ConjunctiveCriterion; @@ -16,7 +15,6 @@ import com.linkedin.metadata.search.SearchEntity; import com.linkedin.metadata.search.SearchResult; import com.linkedin.metadata.secret.SecretService; -import com.linkedin.metadata.utils.GenericRecordUtils; import com.linkedin.mxe.MetadataChangeProposal; import com.linkedin.r2.RemoteInvocationException; import java.net.URISyntaxException; @@ -30,6 +28,7 @@ import lombok.extern.slf4j.Slf4j; import static com.linkedin.metadata.Constants.*; +import static com.linkedin.metadata.entity.AspectUtils.*; @Slf4j @@ -171,14 +170,11 @@ private String createInviteToken(@Nullable final String roleUrnStr, @Nonnull fin inviteTokenAspect.setRole(roleUrn); } - // Ingest inviteToken MCP - final MetadataChangeProposal inviteTokenProposal = new MetadataChangeProposal(); - inviteTokenProposal.setEntityType(INVITE_TOKEN_ENTITY_NAME); - inviteTokenProposal.setEntityKeyAspect(GenericRecordUtils.serializeAspect(inviteTokenKey)); - inviteTokenProposal.setAspectName(INVITE_TOKEN_ASPECT_NAME); - inviteTokenProposal.setAspect(GenericRecordUtils.serializeAspect(inviteTokenAspect)); - inviteTokenProposal.setChangeType(ChangeType.UPSERT); - _entityClient.ingestProposal(inviteTokenProposal, authentication); + // Ingest new InviteToken aspect + final MetadataChangeProposal proposal = + buildMetadataChangeProposal(INVITE_TOKEN_ENTITY_NAME, inviteTokenKey, INVITE_TOKEN_ASPECT_NAME, + inviteTokenAspect); + _entityClient.ingestProposal(proposal, authentication); return inviteTokenStr; } diff --git a/metadata-service/auth-impl/src/main/java/com/datahub/authentication/user/NativeUserService.java b/metadata-service/auth-impl/src/main/java/com/datahub/authentication/user/NativeUserService.java index 133dea5050ac1e..b5e4677fd05c4a 100644 --- a/metadata-service/auth-impl/src/main/java/com/datahub/authentication/user/NativeUserService.java +++ b/metadata-service/auth-impl/src/main/java/com/datahub/authentication/user/NativeUserService.java @@ -19,8 +19,8 @@ import java.time.Instant; import java.util.Base64; import java.util.Objects; +import java.util.UUID; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; @@ -32,10 +32,7 @@ */ @Slf4j public class NativeUserService { - public static final int LOWERCASE_ASCII_START = 97; - public static final int LOWERCASE_ASCII_END = 122; private static final int SALT_TOKEN_LENGTH = 16; - private static final int PASSWORD_RESET_TOKEN_LENGTH = 32; private static final String HASHING_ALGORITHM = "SHA-256"; private static final long ONE_DAY_MILLIS = TimeUnit.DAYS.toMillis(1); @@ -141,7 +138,7 @@ public String generateNativeUserPasswordResetToken(@Nonnull String userUrnString throw new RuntimeException("User does not exist or is a non-native user!"); } // Add reset token to CorpUserCredentials - String passwordResetToken = generateRandomLowercaseToken(PASSWORD_RESET_TOKEN_LENGTH); + String passwordResetToken = generateRandomLowercaseToken(); corpUserCredentials.setPasswordResetToken(_secretService.encrypt(passwordResetToken)); long expirationTime = Instant.now().plusMillis(ONE_DAY_MILLIS).toEpochMilli(); @@ -211,11 +208,8 @@ byte[] getRandomBytes(int length) { return randomBytes; } - // TODO: Refactor to use UUID.randomUUID().toString(); - String generateRandomLowercaseToken(int length) { - return _secureRandom.ints(length, LOWERCASE_ASCII_START, LOWERCASE_ASCII_END + 1) - .mapToObj(i -> String.valueOf((char) i)) - .collect(Collectors.joining()); + String generateRandomLowercaseToken() { + return UUID.randomUUID().toString(); } byte[] saltPassword(@Nonnull byte[] salt, @Nonnull String password) throws IOException { diff --git a/metadata-service/auth-impl/src/main/java/com/datahub/authorization/role/RoleService.java b/metadata-service/auth-impl/src/main/java/com/datahub/authorization/role/RoleService.java index 278eb4e2a3e8cb..4a419e93b98565 100644 --- a/metadata-service/auth-impl/src/main/java/com/datahub/authorization/role/RoleService.java +++ b/metadata-service/auth-impl/src/main/java/com/datahub/authorization/role/RoleService.java @@ -4,9 +4,7 @@ import com.linkedin.common.UrnArray; import com.linkedin.common.urn.Urn; import com.linkedin.entity.client.EntityClient; -import com.linkedin.events.metadata.ChangeType; import com.linkedin.identity.RoleMembership; -import com.linkedin.metadata.utils.GenericRecordUtils; import com.linkedin.mxe.MetadataChangeProposal; import com.linkedin.r2.RemoteInvocationException; import java.net.URISyntaxException; @@ -15,6 +13,7 @@ import lombok.extern.slf4j.Slf4j; import static com.linkedin.metadata.Constants.*; +import static com.linkedin.metadata.entity.AspectUtils.*; @Slf4j @@ -38,13 +37,9 @@ public void assignRoleToActor(@Nonnull final String actor, @Nonnull final Urn ro RoleMembership roleMembership = new RoleMembership(); roleMembership.setRoles(new UrnArray(roleUrn)); - // Finally, create the MetadataChangeProposal. - final MetadataChangeProposal proposal = new MetadataChangeProposal(); - proposal.setEntityUrn(actorUrn); - proposal.setEntityType(CORP_USER_ENTITY_NAME); - proposal.setAspectName(ROLE_MEMBERSHIP_ASPECT_NAME); - proposal.setAspect(GenericRecordUtils.serializeAspect(roleMembership)); - proposal.setChangeType(ChangeType.UPSERT); + // Ingest new RoleMembership aspect + final MetadataChangeProposal proposal = + buildMetadataChangeProposal(actorUrn, ROLE_MEMBERSHIP_ASPECT_NAME, roleMembership); _entityClient.ingestProposal(proposal, authentication); } } diff --git a/metadata-service/factories/src/main/java/com/linkedin/gms/factory/auth/InviteTokenServiceFactory.java b/metadata-service/factories/src/main/java/com/linkedin/gms/factory/auth/InviteTokenServiceFactory.java index 39caff7a188be6..47f7ef0e0c1eb9 100644 --- a/metadata-service/factories/src/main/java/com/linkedin/gms/factory/auth/InviteTokenServiceFactory.java +++ b/metadata-service/factories/src/main/java/com/linkedin/gms/factory/auth/InviteTokenServiceFactory.java @@ -1,8 +1,8 @@ package com.linkedin.gms.factory.auth; import com.datahub.authentication.invite.InviteTokenService; -import com.linkedin.entity.client.JavaEntityClient; import com.linkedin.gms.factory.spring.YamlPropertySourceFactory; +import com.linkedin.metadata.client.JavaEntityClient; import com.linkedin.metadata.secret.SecretService; import javax.annotation.Nonnull; import org.springframework.beans.factory.annotation.Autowired; diff --git a/metadata-service/factories/src/main/java/com/linkedin/gms/factory/auth/RoleServiceFactory.java b/metadata-service/factories/src/main/java/com/linkedin/gms/factory/auth/RoleServiceFactory.java index 3842062beaad30..42f3e797c33bdd 100644 --- a/metadata-service/factories/src/main/java/com/linkedin/gms/factory/auth/RoleServiceFactory.java +++ b/metadata-service/factories/src/main/java/com/linkedin/gms/factory/auth/RoleServiceFactory.java @@ -3,8 +3,8 @@ package com.linkedin.gms.factory.auth; import com.datahub.authorization.role.RoleService; -import com.linkedin.entity.client.JavaEntityClient; import com.linkedin.gms.factory.spring.YamlPropertySourceFactory; +import com.linkedin.metadata.client.JavaEntityClient; import javax.annotation.Nonnull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier;