Skip to content

Commit

Permalink
Merge branch 'master' into s3-gcs-external-urls
Browse files Browse the repository at this point in the history
  • Loading branch information
acrylJonny authored Mar 3, 2025
2 parents a2a08b2 + 17de393 commit 916c297
Show file tree
Hide file tree
Showing 63 changed files with 3,924 additions and 174 deletions.
40 changes: 40 additions & 0 deletions datahub-frontend/app/auth/GuestAuthenticationConfigs.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package auth;

public class GuestAuthenticationConfigs {
public static final String GUEST_ENABLED_CONFIG_PATH = "auth.guest.enabled";
public static final String GUEST_USER_CONFIG_PATH = "auth.guest.user";
public static final String GUEST_PATH_CONFIG_PATH = "auth.guest.path";
public static final String DEFAULT_GUEST_USER_NAME = "guest";
public static final String DEFAULT_GUEST_PATH = "/public";

private Boolean isEnabled = false;
private String guestUser =
DEFAULT_GUEST_USER_NAME; // Default if not defined but guest auth is enabled.
private String guestPath =
DEFAULT_GUEST_PATH; // The path for initial access to login as guest and bypass login page.

public GuestAuthenticationConfigs(final com.typesafe.config.Config configs) {
if (configs.hasPath(GUEST_ENABLED_CONFIG_PATH)
&& configs.getBoolean(GUEST_ENABLED_CONFIG_PATH)) {
isEnabled = true;
}
if (configs.hasPath(GUEST_USER_CONFIG_PATH)) {
guestUser = configs.getString(GUEST_USER_CONFIG_PATH);
}
if (configs.hasPath(GUEST_PATH_CONFIG_PATH)) {
guestPath = configs.getString(GUEST_PATH_CONFIG_PATH);
}
}

public boolean isGuestEnabled() {
return isEnabled;
}

public String getGuestUser() {
return guestUser;
}

public String getGuestPath() {
return guestPath;
}
}
21 changes: 21 additions & 0 deletions datahub-frontend/app/controllers/AuthenticationController.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import auth.AuthUtils;
import auth.CookieConfigs;
import auth.GuestAuthenticationConfigs;
import auth.JAASConfigs;
import auth.NativeAuthenticationConfigs;
import auth.sso.SsoManager;
Expand Down Expand Up @@ -58,6 +59,8 @@ public class AuthenticationController extends Controller {
private final CookieConfigs cookieConfigs;
private final JAASConfigs jaasConfigs;
private final NativeAuthenticationConfigs nativeAuthenticationConfigs;
private final GuestAuthenticationConfigs guestAuthenticationConfigs;

private final boolean verbose;

@Inject private org.pac4j.core.config.Config ssoConfig;
Expand All @@ -73,6 +76,7 @@ public AuthenticationController(@Nonnull Config configs) {
cookieConfigs = new CookieConfigs(configs);
jaasConfigs = new JAASConfigs(configs);
nativeAuthenticationConfigs = new NativeAuthenticationConfigs(configs);
guestAuthenticationConfigs = new GuestAuthenticationConfigs(configs);
verbose = configs.hasPath(AUTH_VERBOSE_LOGGING) && configs.getBoolean(AUTH_VERBOSE_LOGGING);
}

Expand Down Expand Up @@ -110,6 +114,23 @@ public Result authenticate(Http.Request request) {
return Results.redirect(redirectPath);
}

if (guestAuthenticationConfigs.isGuestEnabled()
&& guestAuthenticationConfigs.getGuestPath().equals(redirectPath)) {
final String accessToken =
authClient.generateSessionTokenForUser(guestAuthenticationConfigs.getGuestUser());
redirectPath =
"/"; // We requested guest login by accessing {guestPath} URL. It is not really a target.
CorpuserUrn guestUserUrn = new CorpuserUrn(guestAuthenticationConfigs.getGuestUser());
return Results.redirect(redirectPath)
.withSession(createSessionMap(guestUserUrn.toString(), accessToken))
.withCookies(
createActorCookie(
guestUserUrn.toString(),
cookieConfigs.getTtlInHours(),
cookieConfigs.getAuthCookieSameSite(),
cookieConfigs.getAuthCookieSecure()));
}

// 1. If SSO is enabled, redirect to IdP if not authenticated.
if (ssoManager.isSsoEnabled()) {
return redirectToIdentityProvider(request, redirectPath)
Expand Down
5 changes: 5 additions & 0 deletions datahub-frontend/conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@ auth.oidc.grantType = ${?AUTH_OIDC_GRANT_TYPE}
#
auth.jaas.enabled = ${?AUTH_JAAS_ENABLED}
auth.native.enabled = ${?AUTH_NATIVE_ENABLED}
auth.guest.enabled = ${?GUEST_AUTHENTICATION_ENABLED}
# The name of the guest user id
auth.guest.user = ${?GUEST_AUTHENTICATION_USER}
# The path to bypass login page and get logged in as guest
auth.guest.path = ${?GUEST_AUTHENTICATION_PATH}

# Enforces the usage of a valid email for user sign up
auth.native.signUp.enforceValidEmail = true
Expand Down
61 changes: 61 additions & 0 deletions datahub-frontend/test/security/GuestAuthenticationConfigsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package security;

import static org.junit.jupiter.api.Assertions.*;

import auth.GuestAuthenticationConfigs;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junitpioneer.jupiter.ClearEnvironmentVariable;
import org.junitpioneer.jupiter.SetEnvironmentVariable;

@TestInstance(TestInstance.Lifecycle.PER_METHOD)
@SetEnvironmentVariable(key = "DATAHUB_SECRET", value = "test")
@SetEnvironmentVariable(key = "KAFKA_BOOTSTRAP_SERVER", value = "")
@SetEnvironmentVariable(key = "DATAHUB_ANALYTICS_ENABLED", value = "false")
@SetEnvironmentVariable(key = "AUTH_OIDC_ENABLED", value = "true")
@SetEnvironmentVariable(key = "AUTH_OIDC_JIT_PROVISIONING_ENABLED", value = "false")
@SetEnvironmentVariable(key = "AUTH_OIDC_CLIENT_ID", value = "testclient")
@SetEnvironmentVariable(key = "AUTH_OIDC_CLIENT_SECRET", value = "testsecret")
@SetEnvironmentVariable(key = "AUTH_VERBOSE_LOGGING", value = "true")
class GuestAuthenticationConfigsTest {

@BeforeEach
@ClearEnvironmentVariable(key = "GUEST_AUTHENTICATION_ENABLED")
@ClearEnvironmentVariable(key = "GUEST_AUTHENTICATION_USER")
@ClearEnvironmentVariable(key = "GUEST_AUTHENTICATION_PATH")
public void clearConfigCache() {
ConfigFactory.invalidateCaches();
}

@Test
public void testGuestConfigDisabled() {
Config config = ConfigFactory.load();
GuestAuthenticationConfigs guestAuthConfig = new GuestAuthenticationConfigs(config);
assertFalse(guestAuthConfig.isGuestEnabled());
}

@Test
@SetEnvironmentVariable(key = "GUEST_AUTHENTICATION_ENABLED", value = "true")
public void testGuestConfigEnabled() {
Config config = ConfigFactory.load();
GuestAuthenticationConfigs guestAuthConfig = new GuestAuthenticationConfigs(config);
assertTrue(guestAuthConfig.isGuestEnabled());
assertEquals("guest", guestAuthConfig.getGuestUser());
assertEquals("/public", guestAuthConfig.getGuestPath());
}

@Test
@SetEnvironmentVariable(key = "GUEST_AUTHENTICATION_ENABLED", value = "true")
@SetEnvironmentVariable(key = "GUEST_AUTHENTICATION_USER", value = "publicUser")
@SetEnvironmentVariable(key = "GUEST_AUTHENTICATION_PATH", value = "/publicPath")
public void testGuestConfigWithUserEnabled() {
Config config = ConfigFactory.load();
GuestAuthenticationConfigs guestAuthConfig = new GuestAuthenticationConfigs(config);
assertTrue(guestAuthConfig.isGuestEnabled());
assertEquals("publicUser", guestAuthConfig.getGuestUser());
assertEquals("/publicPath", guestAuthConfig.getGuestPath());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1026,7 +1026,8 @@ private void configureQueryResolvers(final RuntimeWiring.Builder builder) {
new ScrollAcrossEntitiesResolver(this.entityClient, this.viewService))
.dataFetcher(
"searchAcrossLineage",
new SearchAcrossLineageResolver(this.entityClient, this.entityRegistry))
new SearchAcrossLineageResolver(
this.entityClient, this.entityRegistry, this.viewService))
.dataFetcher(
"scrollAcrossLineage", new ScrollAcrossLineageResolver(this.entityClient))
.dataFetcher(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import com.google.common.collect.ImmutableSet;
import com.linkedin.common.urn.Urn;
import com.linkedin.common.urn.UrnUtils;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.concurrency.GraphQLConcurrencyUtils;
import com.linkedin.datahub.graphql.generated.AndFilterInput;
Expand All @@ -25,8 +26,12 @@
import com.linkedin.metadata.query.LineageFlags;
import com.linkedin.metadata.query.SearchFlags;
import com.linkedin.metadata.query.filter.Filter;
import com.linkedin.metadata.query.filter.SortCriterion;
import com.linkedin.metadata.search.LineageSearchResult;
import com.linkedin.metadata.service.ViewService;
import com.linkedin.metadata.utils.elasticsearch.FilterUtils;
import com.linkedin.r2.RemoteInvocationException;
import com.linkedin.view.DataHubViewInfo;
import graphql.VisibleForTesting;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
Expand All @@ -53,10 +58,13 @@ public class SearchAcrossLineageResolver

private final EntityRegistry _entityRegistry;

private final ViewService _viewService;

@VisibleForTesting final Set<String> _allEntities;
private final List<String> _allowedEntities;

public SearchAcrossLineageResolver(EntityClient entityClient, EntityRegistry entityRegistry) {
public SearchAcrossLineageResolver(
EntityClient entityClient, EntityRegistry entityRegistry, final ViewService viewService) {
this._entityClient = entityClient;
this._entityRegistry = entityRegistry;
this._allEntities =
Expand All @@ -68,6 +76,8 @@ public SearchAcrossLineageResolver(EntityClient entityClient, EntityRegistry ent
this._allEntities.stream()
.filter(e -> !TRANSIENT_ENTITIES.contains(e))
.collect(Collectors.toList());

this._viewService = viewService;
}

private List<String> getEntityNamesFromInput(List<EntityType> inputTypes) {
Expand Down Expand Up @@ -127,6 +137,13 @@ public CompletableFuture<SearchAcrossLineageResults> get(DataFetchingEnvironment
com.linkedin.metadata.graph.LineageDirection.valueOf(lineageDirection.toString());
return GraphQLConcurrencyUtils.supplyAsync(
() -> {
final DataHubViewInfo maybeResolvedView =
(input.getViewUrn() != null)
? resolveView(
context.getOperationContext(),
_viewService,
UrnUtils.getUrn(input.getViewUrn()))
: null;
try {
log.debug(
"Executing search across relationships: source urn {}, direction {}, entity types {}, query {}, filters: {}, start: {}, count: {}",
Expand All @@ -138,8 +155,13 @@ public CompletableFuture<SearchAcrossLineageResults> get(DataFetchingEnvironment
start,
count);

final Filter filter =
final Filter baseFilter =
ResolverUtils.buildFilter(input.getFilters(), input.getOrFilters());
Filter filter =
maybeResolvedView != null
? FilterUtils.combineFilters(
baseFilter, maybeResolvedView.getDefinition().getFilter())
: baseFilter;
final SearchFlags searchFlags;
com.linkedin.datahub.graphql.generated.SearchFlags inputFlags = input.getSearchFlags();
if (inputFlags != null) {
Expand All @@ -150,6 +172,7 @@ public CompletableFuture<SearchAcrossLineageResults> get(DataFetchingEnvironment
} else {
searchFlags = new SearchFlags().setFulltext(true).setSkipHighlighting(true);
}
List<SortCriterion> sortCriteria = SearchUtils.getSortCriteria(input.getSortInput());
LineageSearchResult salResults =
_entityClient.searchAcrossLineage(
context
Expand All @@ -162,7 +185,7 @@ public CompletableFuture<SearchAcrossLineageResults> get(DataFetchingEnvironment
sanitizedQuery,
maxHops,
filter,
null,
sortCriteria,
start,
count);

Expand Down
10 changes: 10 additions & 0 deletions datahub-graphql-core/src/main/resources/search.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,16 @@ input SearchAcrossLineageInput {
Flags controlling the lineage query
"""
lineageFlags: LineageFlags

"""
Optional - A View to apply when generating results
"""
viewUrn: String

"""
Optional - Information on how to sort this search result
"""
sortInput: SearchSortInput
}

"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.linkedin.metadata.search.LineageSearchEntityArray;
import com.linkedin.metadata.search.MatchedFieldArray;
import com.linkedin.metadata.search.SearchResultMetadata;
import com.linkedin.metadata.service.ViewService;
import graphql.schema.DataFetchingEnvironment;
import io.datahubproject.metadata.context.OperationContext;
import java.io.InputStream;
Expand Down Expand Up @@ -76,7 +77,7 @@ public void testAllEntitiesInitialization() {
InputStream inputStream = ClassLoader.getSystemResourceAsStream("entity-registry.yml");
EntityRegistry entityRegistry = new ConfigEntityRegistry(inputStream);
SearchAcrossLineageResolver resolver =
new SearchAcrossLineageResolver(_entityClient, entityRegistry);
new SearchAcrossLineageResolver(_entityClient, entityRegistry, mock(ViewService.class));
assertTrue(resolver._allEntities.contains("dataset"));
assertTrue(resolver._allEntities.contains("dataFlow"));
// Test for case sensitivity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.linkedin.metadata.search.LineageSearchResult;
import com.linkedin.metadata.search.MatchedFieldArray;
import com.linkedin.metadata.search.SearchResultMetadata;
import com.linkedin.metadata.service.ViewService;
import graphql.schema.DataFetchingEnvironment;
import io.datahubproject.metadata.context.OperationContext;
import java.io.InputStream;
Expand All @@ -48,6 +49,7 @@ public class SearchAcrossLineageResolverTest {
private SearchAcrossLineageResolver _resolver;

private EntityRegistry _entityRegistry;
private ViewService _viewService;

@BeforeMethod
public void setupTest() {
Expand All @@ -56,15 +58,16 @@ public void setupTest() {
_authentication = mock(Authentication.class);

_entityRegistry = mock(EntityRegistry.class);
_resolver = new SearchAcrossLineageResolver(_entityClient, _entityRegistry);
_viewService = mock(ViewService.class);
_resolver = new SearchAcrossLineageResolver(_entityClient, _entityRegistry, _viewService);
}

@Test
public void testAllEntitiesInitialization() {
InputStream inputStream = ClassLoader.getSystemResourceAsStream("entity-registry.yml");
EntityRegistry entityRegistry = new ConfigEntityRegistry(inputStream);
SearchAcrossLineageResolver resolver =
new SearchAcrossLineageResolver(_entityClient, entityRegistry);
new SearchAcrossLineageResolver(_entityClient, entityRegistry, _viewService);
assertTrue(resolver._allEntities.contains("dataset"));
assertTrue(resolver._allEntities.contains("dataFlow"));
// Test for case sensitivity
Expand Down Expand Up @@ -115,7 +118,7 @@ public void testSearchAcrossLineage() throws Exception {
eq(QUERY),
eq(null),
any(),
eq(null),
eq(Collections.emptyList()),
eq(START),
eq(COUNT)))
.thenReturn(lineageSearchResult);
Expand Down
4 changes: 2 additions & 2 deletions datahub-web-react/src/app/searchV2/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { useAppConfig, useIsShowSeparateSiblingsEnabled } from '../useAppConfig'

const StyledAutoComplete = styled(AutoComplete)<{ $isShowNavBarRedesign?: boolean }>`
width: 100%;
max-width: ${(props) => (props.$isShowNavBarRedesign ? '423px' : '540px')};
max-width: ${(props) => (props.$isShowNavBarRedesign ? '632px' : '540px')};
`;

const SkeletonContainer = styled.div`
Expand Down Expand Up @@ -466,7 +466,7 @@ export const SearchBar = ({
maxHeight: 1000,
overflowY: 'visible',
position: (fixAutoComplete && 'fixed') || 'relative',
...(isShowNavBarRedesign ? { minWidth: '435px' } : {}),
...(isShowNavBarRedesign ? { minWidth: '648px' } : {}),
}}
onDropdownVisibleChange={(isOpen) => {
if (!isOpen) {
Expand Down
Loading

0 comments on commit 916c297

Please sign in to comment.