Backend: Enforce principal domain in the JWT filter (#5155)

This commit is contained in:
Vivek Ratnavel Subramanian 2022-06-01 10:07:12 -07:00 committed by GitHub
parent c1fa31eacc
commit 059acfc529
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 125 additions and 27 deletions

View File

@ -210,15 +210,15 @@ public class CatalogApplication extends Application<CatalogApplicationConfig> {
filter = filter =
Class.forName(filterClazzName) Class.forName(filterClazzName)
.asSubclass(ContainerRequestFilter.class) .asSubclass(ContainerRequestFilter.class)
.getConstructor(AuthenticationConfiguration.class) .getConstructor(AuthenticationConfiguration.class, AuthorizerConfiguration.class)
.newInstance(authenticationConfiguration); .newInstance(authenticationConfiguration, authorizerConf);
LOG.info("Registering ContainerRequestFilter: {}", filter.getClass().getCanonicalName()); LOG.info("Registering ContainerRequestFilter: {}", filter.getClass().getCanonicalName());
environment.jersey().register(filter); environment.jersey().register(filter);
} }
} else { } else {
LOG.info("Authorizer config not set, setting noop authorizer"); LOG.info("Authorizer config not set, setting noop authorizer");
authorizer = new NoopAuthorizer(); authorizer = new NoopAuthorizer();
ContainerRequestFilter filter = new NoopFilter(authenticationConfiguration); ContainerRequestFilter filter = new NoopFilter(authenticationConfiguration, null);
environment.jersey().register(filter); environment.jersey().register(filter);
} }
} }

View File

@ -31,7 +31,7 @@ public class AuthenticationConfiguration {
@Getter @Setter private String authority; @Getter @Setter private String authority;
@Getter @Setter private String clientId; @Getter @Setter private String clientId;
@Getter @Setter private String callbackUrl; @Getter @Setter private String callbackUrl;
@Getter @Setter private List<String> jwtPrincipalClaims = List.of("email", "preferred_username", "sub"); @Getter @Setter private List<String> jwtPrincipalClaims;
@Override @Override
public String toString() { public String toString() {

View File

@ -24,6 +24,7 @@ public class AuthorizerConfiguration {
@NotEmpty @Getter @Setter private Set<String> adminPrincipals; @NotEmpty @Getter @Setter private Set<String> adminPrincipals;
@NotEmpty @Getter @Setter private Set<String> botPrincipals; @NotEmpty @Getter @Setter private Set<String> botPrincipals;
@NotEmpty @Getter @Setter private String principalDomain; @NotEmpty @Getter @Setter private String principalDomain;
@NotEmpty @Getter @Setter private Boolean enforcePrincipalDomain;
@Override @Override
public String toString() { public String toString() {

View File

@ -32,7 +32,7 @@ public class CatalogOpenIdAuthorizationRequestFilter implements ContainerRequest
@SuppressWarnings("unused") @SuppressWarnings("unused")
private CatalogOpenIdAuthorizationRequestFilter() {} private CatalogOpenIdAuthorizationRequestFilter() {}
public CatalogOpenIdAuthorizationRequestFilter(AuthenticationConfiguration config) {} public CatalogOpenIdAuthorizationRequestFilter(AuthenticationConfiguration config, AuthorizerConfiguration conf) {}
public void filter(ContainerRequestContext containerRequestContext) { public void filter(ContainerRequestContext containerRequestContext) {
if (isHealthEndpoint(containerRequestContext)) { if (isHealthEndpoint(containerRequestContext)) {

View File

@ -14,6 +14,7 @@
package org.openmetadata.catalog.security; package org.openmetadata.catalog.security;
import static org.openmetadata.catalog.Entity.FIELD_OWNER; import static org.openmetadata.catalog.Entity.FIELD_OWNER;
import static org.openmetadata.catalog.security.SecurityUtil.DEFAULT_PRINCIPAL_DOMAIN;
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty; import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
import java.io.IOException; import java.io.IOException;
@ -68,11 +69,12 @@ public class DefaultAuthorizer implements Authorizer {
} }
addOrUpdateUser(user); addOrUpdateUser(user);
} catch (EntityNotFoundException | IOException ex) { } catch (EntityNotFoundException | IOException ex) {
String domain = principalDomain.isEmpty() ? DEFAULT_PRINCIPAL_DOMAIN : principalDomain;
User user = User user =
new User() new User()
.withId(UUID.randomUUID()) .withId(UUID.randomUUID())
.withName(adminUser) .withName(adminUser)
.withEmail(adminUser + "@" + principalDomain) .withEmail(adminUser + "@" + domain)
.withIsAdmin(true) .withIsAdmin(true)
.withUpdatedBy(adminUser) .withUpdatedBy(adminUser)
.withUpdatedAt(System.currentTimeMillis()); .withUpdatedAt(System.currentTimeMillis());
@ -92,11 +94,12 @@ public class DefaultAuthorizer implements Authorizer {
} }
addOrUpdateUser(user); addOrUpdateUser(user);
} catch (EntityNotFoundException | IOException ex) { } catch (EntityNotFoundException | IOException ex) {
String domain = principalDomain.isEmpty() ? DEFAULT_PRINCIPAL_DOMAIN : principalDomain;
User user = User user =
new User() new User()
.withId(UUID.randomUUID()) .withId(UUID.randomUUID())
.withName(botUser) .withName(botUser)
.withEmail(botUser + "@" + principalDomain) .withEmail(botUser + "@" + domain)
.withIsBot(true) .withIsBot(true)
.withUpdatedBy(botUser) .withUpdatedBy(botUser)
.withUpdatedAt(System.currentTimeMillis()); .withUpdatedAt(System.currentTimeMillis());

View File

@ -18,6 +18,7 @@ import com.auth0.jwk.JwkProvider;
import com.auth0.jwt.JWT; import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.node.TextNode; import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@ -28,7 +29,9 @@ import java.net.URL;
import java.security.interfaces.RSAPublicKey; import java.security.interfaces.RSAPublicKey;
import java.util.Calendar; import java.util.Calendar;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.TreeMap;
import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
@ -37,6 +40,7 @@ import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Provider; import javax.ws.rs.ext.Provider;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.openmetadata.catalog.Entity; import org.openmetadata.catalog.Entity;
import org.openmetadata.catalog.entity.teams.AuthenticationMechanism; import org.openmetadata.catalog.entity.teams.AuthenticationMechanism;
import org.openmetadata.catalog.entity.teams.User; import org.openmetadata.catalog.entity.teams.User;
@ -55,12 +59,15 @@ public class JwtFilter implements ContainerRequestFilter {
private List<String> jwtPrincipalClaims; private List<String> jwtPrincipalClaims;
private JwkProvider jwkProvider; private JwkProvider jwkProvider;
private String principalDomain;
private boolean enforcePrincipalDomain;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private JwtFilter() {} private JwtFilter() {}
@SneakyThrows @SneakyThrows
public JwtFilter(AuthenticationConfiguration authenticationConfiguration) { public JwtFilter(
AuthenticationConfiguration authenticationConfiguration, AuthorizerConfiguration authorizerConfiguration) {
this.jwtPrincipalClaims = authenticationConfiguration.getJwtPrincipalClaims(); this.jwtPrincipalClaims = authenticationConfiguration.getJwtPrincipalClaims();
ImmutableList.Builder<URL> publicKeyUrlsBuilder = ImmutableList.builder(); ImmutableList.Builder<URL> publicKeyUrlsBuilder = ImmutableList.builder();
@ -68,12 +75,20 @@ public class JwtFilter implements ContainerRequestFilter {
publicKeyUrlsBuilder.add(new URL(publicKeyUrlStr)); publicKeyUrlsBuilder.add(new URL(publicKeyUrlStr));
} }
this.jwkProvider = new MultiUrlJwkProvider(publicKeyUrlsBuilder.build()); this.jwkProvider = new MultiUrlJwkProvider(publicKeyUrlsBuilder.build());
this.principalDomain = authorizerConfiguration.getPrincipalDomain();
this.enforcePrincipalDomain = authorizerConfiguration.getEnforcePrincipalDomain();
} }
@VisibleForTesting @VisibleForTesting
JwtFilter(JwkProvider jwkProvider, List<String> jwtPrincipalClaims) { JwtFilter(
JwkProvider jwkProvider,
List<String> jwtPrincipalClaims,
String principalDomain,
boolean enforcePrincipalDomain) {
this.jwkProvider = jwkProvider; this.jwkProvider = jwkProvider;
this.jwtPrincipalClaims = jwtPrincipalClaims; this.jwtPrincipalClaims = jwtPrincipalClaims;
this.principalDomain = principalDomain;
this.enforcePrincipalDomain = enforcePrincipalDomain;
} }
@SneakyThrows @SneakyThrows
@ -114,26 +129,38 @@ public class JwtFilter implements ContainerRequestFilter {
} }
// Get username from JWT token // Get username from JWT token
String userName = Map<String, Claim> claims = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
claims.putAll(jwt.getClaims());
String jwtClaim =
jwtPrincipalClaims.stream() jwtPrincipalClaims.stream()
.filter(jwt.getClaims()::containsKey) .filter(claims::containsKey)
.findFirst() .findFirst()
.map(jwt::getClaim) .map(claims::get)
.map(claim -> claim.as(TextNode.class).asText()) .map(claim -> claim.as(TextNode.class).asText())
.map(
authorizedClaim -> {
if (authorizedClaim.contains("@")) {
return authorizedClaim.split("@")[0];
} else {
return authorizedClaim;
}
})
.orElseThrow( .orElseThrow(
() -> () ->
new AuthenticationException( new AuthenticationException(
"Invalid JWT token, none of the following claims are present " + jwtPrincipalClaims)); "Invalid JWT token, none of the following claims are present " + jwtPrincipalClaims));
String userName;
String domain;
if (jwtClaim.contains("@")) {
userName = jwtClaim.split("@")[0];
domain = jwtClaim.split("@")[1];
} else {
userName = jwtClaim;
domain = StringUtils.EMPTY;
}
// validate principal domain
if (enforcePrincipalDomain) {
if (!domain.equals(principalDomain)) {
throw new AuthenticationException(
String.format("Not Authorized! Email does not match the principal domain %s", principalDomain));
}
}
// validate bot token // validate bot token
if (jwt.getClaims().containsKey(BOT_CLAIM) && jwt.getClaims().get(BOT_CLAIM).asBoolean()) { if (claims.containsKey(BOT_CLAIM) && claims.get(BOT_CLAIM).asBoolean()) {
validateBotToken(tokenFromHeader, userName); validateBotToken(tokenFromHeader, userName);
} }
// Setting Security Context // Setting Security Context

View File

@ -25,7 +25,8 @@ import org.openmetadata.catalog.security.auth.CatalogSecurityContext;
public class NoopFilter implements ContainerRequestFilter { public class NoopFilter implements ContainerRequestFilter {
@Context private UriInfo uriInfo; @Context private UriInfo uriInfo;
public NoopFilter(AuthenticationConfiguration authenticationConfiguration) {} public NoopFilter(
AuthenticationConfiguration authenticationConfiguration, AuthorizerConfiguration authorizerConfiguration) {}
public void filter(ContainerRequestContext containerRequestContext) { public void filter(ContainerRequestContext containerRequestContext) {
CatalogPrincipal catalogPrincipal = new CatalogPrincipal("anonymous"); CatalogPrincipal catalogPrincipal = new CatalogPrincipal("anonymous");

View File

@ -34,6 +34,7 @@ public final class SecurityUtil {
public static final int BOT = 2; public static final int BOT = 2;
public static final int OWNER = 4; public static final int OWNER = 4;
public static final int PERMISSIONS = 8; public static final int PERMISSIONS = 8;
public static final String DEFAULT_PRINCIPAL_DOMAIN = "openmetadata.org";
private SecurityUtil() {} private SecurityUtil() {}

View File

@ -49,6 +49,7 @@ import org.mockito.ArgumentCaptor;
class JwtFilterTest { class JwtFilterTest {
private static JwtFilter jwtFilter; private static JwtFilter jwtFilter;
private static JwkProvider jwkProvider;
private static Algorithm algorithm; private static Algorithm algorithm;
private static UriInfo mockRequestURIInfo; private static UriInfo mockRequestURIInfo;
@ -65,7 +66,7 @@ class JwtFilterTest {
// This is used to verify the JWT // This is used to verify the JWT
Jwk mockJwk = mock(Jwk.class); Jwk mockJwk = mock(Jwk.class);
when(mockJwk.getPublicKey()).thenReturn(keyPair.getPublic()); when(mockJwk.getPublicKey()).thenReturn(keyPair.getPublic());
JwkProvider jwkProvider = mock(JwkProvider.class); jwkProvider = mock(JwkProvider.class);
when(jwkProvider.get(algorithm.getSigningKeyId())).thenReturn(mockJwk); when(jwkProvider.get(algorithm.getSigningKeyId())).thenReturn(mockJwk);
// This is needed by JwtFilter for some metadata, not very important // This is needed by JwtFilter for some metadata, not very important
@ -75,7 +76,44 @@ class JwtFilterTest {
when(mockRequestURIInfo.getRequestUri()).thenReturn(uri); when(mockRequestURIInfo.getRequestUri()).thenReturn(uri);
List<String> principalClaims = List.of("sub", "email"); List<String> principalClaims = List.of("sub", "email");
jwtFilter = new JwtFilter(jwkProvider, principalClaims); String domain = "openmetadata.org";
boolean enforcePrincipalDomain = false;
jwtFilter = new JwtFilter(jwkProvider, principalClaims, domain, enforcePrincipalDomain);
}
@Test
void testPrincipalDomainEnforcement() {
List<String> principalClaims = List.of("EMAIL", "sub");
String domain = "openmetadata.org";
boolean enforcePrincipalDomain = true;
jwtFilter = new JwtFilter(jwkProvider, principalClaims, domain, enforcePrincipalDomain);
// success case
String jwt =
JWT.create()
.withExpiresAt(Date.from(Instant.now().plus(1, ChronoUnit.DAYS)))
.withClaim("email", "sam@openmetadata.org")
.sign(algorithm);
ContainerRequestContext context = createRequestContextWithJwt(jwt);
jwtFilter.filter(context);
ArgumentCaptor<SecurityContext> securityContextArgument = ArgumentCaptor.forClass(SecurityContext.class);
verify(context, times(1)).setSecurityContext(securityContextArgument.capture());
assertEquals("sam", securityContextArgument.getValue().getUserPrincipal().getName());
// error case
jwt =
JWT.create()
.withExpiresAt(Date.from(Instant.now().plus(1, ChronoUnit.DAYS)))
.withClaim("email", "sam@gmail.com")
.sign(algorithm);
ContainerRequestContext newContext = createRequestContextWithJwt(jwt);
Exception exception = assertThrows(AuthenticationException.class, () -> jwtFilter.filter(newContext));
assertTrue(exception.getMessage().toLowerCase(Locale.ROOT).contains("email does not match the principal domain"));
} }
@Test @Test

View File

@ -131,7 +131,8 @@ authorizerConfiguration:
containerRequestFilter: ${AUTHORIZER_REQUEST_FILTER:-org.openmetadata.catalog.security.NoopFilter} containerRequestFilter: ${AUTHORIZER_REQUEST_FILTER:-org.openmetadata.catalog.security.NoopFilter}
adminPrincipals: ${AUTHORIZER_ADMIN_PRINCIPALS:-[admin]} adminPrincipals: ${AUTHORIZER_ADMIN_PRINCIPALS:-[admin]}
botPrincipals: ${AUTHORIZER_INGESTION_PRINCIPALS:-[ingestion-bot]} botPrincipals: ${AUTHORIZER_INGESTION_PRINCIPALS:-[ingestion-bot]}
principalDomain: ${AUTHORIZER_PRINCIPAL_DOMAIN:-""} principalDomain: ${AUTHORIZER_PRINCIPAL_DOMAIN:-"openmetadata.org"}
enforcePrincipalDomain: ${AUTHORIZER_ENFORCE_PRINCIPAL_DOMAIN:-false}
authenticationConfiguration: authenticationConfiguration:
provider: ${AUTHENTICATION_PROVIDER:-no-auth} provider: ${AUTHENTICATION_PROVIDER:-no-auth}
@ -141,6 +142,7 @@ authenticationConfiguration:
authority: ${AUTHENTICATION_AUTHORITY:-https://accounts.google.com} authority: ${AUTHENTICATION_AUTHORITY:-https://accounts.google.com}
clientId: ${AUTHENTICATION_CLIENT_ID:-""} clientId: ${AUTHENTICATION_CLIENT_ID:-""}
callbackUrl: ${AUTHENTICATION_CALLBACK_URL:-""} callbackUrl: ${AUTHENTICATION_CALLBACK_URL:-""}
jwtPrincipalClaims: ${AUTHENTICATION_JWT_PRINCIPAL_CLAIMS:-[email,preferred_username,sub]}
jwtTokenConfiguration: jwtTokenConfiguration:
rsapublicKeyFilePath: ${RSA_PUBLIC_KEY_FILE_PATH:-""} rsapublicKeyFilePath: ${RSA_PUBLIC_KEY_FILE_PATH:-""}

View File

@ -64,12 +64,14 @@ services:
AUTHORIZER_ADMIN_PRINCIPALS: ${AUTHORIZER_ADMIN_PRINCIPALS:-[admin]} AUTHORIZER_ADMIN_PRINCIPALS: ${AUTHORIZER_ADMIN_PRINCIPALS:-[admin]}
AUTHORIZER_INGESTION_PRINCIPALS: ${AUTHORIZER_INGESTION_PRINCIPALS:-[ingestion-bot]} AUTHORIZER_INGESTION_PRINCIPALS: ${AUTHORIZER_INGESTION_PRINCIPALS:-[ingestion-bot]}
AUTHORIZER_PRINCIPAL_DOMAIN: ${AUTHORIZER_PRINCIPAL_DOMAIN:-""} AUTHORIZER_PRINCIPAL_DOMAIN: ${AUTHORIZER_PRINCIPAL_DOMAIN:-""}
AUTHORIZER_ENFORCE_PRINCIPAL_DOMAIN: ${AUTHORIZER_ENFORCE_PRINCIPAL_DOMAIN:-false}
AUTHENTICATION_PROVIDER: ${AUTHENTICATION_PROVIDER:-no-auth} AUTHENTICATION_PROVIDER: ${AUTHENTICATION_PROVIDER:-no-auth}
CUSTOM_OIDC_AUTHENTICATION_PROVIDER_NAME: ${CUSTOM_OIDC_AUTHENTICATION_PROVIDER_NAME:-""} CUSTOM_OIDC_AUTHENTICATION_PROVIDER_NAME: ${CUSTOM_OIDC_AUTHENTICATION_PROVIDER_NAME:-""}
AUTHENTICATION_PUBLIC_KEYS: ${AUTHENTICATION_PUBLIC_KEY:-[https://www.googleapis.com/oauth2/v3/certs]} AUTHENTICATION_PUBLIC_KEYS: ${AUTHENTICATION_PUBLIC_KEY:-[https://www.googleapis.com/oauth2/v3/certs]}
AUTHENTICATION_AUTHORITY: ${AUTHENTICATION_AUTHORITY:-https://accounts.google.com} AUTHENTICATION_AUTHORITY: ${AUTHENTICATION_AUTHORITY:-https://accounts.google.com}
AUTHENTICATION_CLIENT_ID: ${AUTHENTICATION_CLIENT_ID:-""} AUTHENTICATION_CLIENT_ID: ${AUTHENTICATION_CLIENT_ID:-""}
AUTHENTICATION_CALLBACK_URL: ${AUTHENTICATION_CALLBACK_URL:-""} AUTHENTICATION_CALLBACK_URL: ${AUTHENTICATION_CALLBACK_URL:-""}
AUTHENTICATION_JWT_PRINCIPAL_CLAIMS: ${AUTHENTICATION_JWT_PRINCIPAL_CLAIMS:-[email,preferred_username,sub]}
# OpenMetadata Server Airflow Configuration # OpenMetadata Server Airflow Configuration
AIRFLOW_HOST: ${AIRFLOW_HOST:-http://ingestion:8080} AIRFLOW_HOST: ${AIRFLOW_HOST:-http://ingestion:8080}
SERVER_HOST_API_URL: ${SERVER_HOST_API_URL:-http://localhost:8585/api} SERVER_HOST_API_URL: ${SERVER_HOST_API_URL:-http://localhost:8585/api}

View File

@ -53,12 +53,14 @@ services:
AUTHORIZER_ADMIN_PRINCIPALS: ${AUTHORIZER_ADMIN_PRINCIPALS:-[admin]} AUTHORIZER_ADMIN_PRINCIPALS: ${AUTHORIZER_ADMIN_PRINCIPALS:-[admin]}
AUTHORIZER_INGESTION_PRINCIPALS: ${AUTHORIZER_INGESTION_PRINCIPAL:-[ingestion-bot]} AUTHORIZER_INGESTION_PRINCIPALS: ${AUTHORIZER_INGESTION_PRINCIPAL:-[ingestion-bot]}
AUTHORIZER_PRINCIPAL_DOMAIN: ${AUTHORIZER_PRINCIPAL_DOMAIN:-""} AUTHORIZER_PRINCIPAL_DOMAIN: ${AUTHORIZER_PRINCIPAL_DOMAIN:-""}
AUTHORIZER_ENFORCE_PRINCIPAL_DOMAIN: ${AUTHORIZER_ENFORCE_PRINCIPAL_DOMAIN:-false}
AUTHENTICATION_PROVIDER: ${AUTHENTICATION_PROVIDER:-no-auth} AUTHENTICATION_PROVIDER: ${AUTHENTICATION_PROVIDER:-no-auth}
CUSTOM_OIDC_AUTHENTICATION_PROVIDER_NAME: ${CUSTOM_OIDC_AUTHENTICATION_PROVIDER_NAME:-""} CUSTOM_OIDC_AUTHENTICATION_PROVIDER_NAME: ${CUSTOM_OIDC_AUTHENTICATION_PROVIDER_NAME:-""}
AUTHENTICATION_PUBLIC_KEYS: ${AUTHENTICATION_PUBLIC_KEY:-[https://www.googleapis.com/oauth2/v3/certs]} AUTHENTICATION_PUBLIC_KEYS: ${AUTHENTICATION_PUBLIC_KEY:-[https://www.googleapis.com/oauth2/v3/certs]}
AUTHENTICATION_AUTHORITY: ${AUTHENTICATION_AUTHORITY:-https://accounts.google.com} AUTHENTICATION_AUTHORITY: ${AUTHENTICATION_AUTHORITY:-https://accounts.google.com}
AUTHENTICATION_CLIENT_ID: ${AUTHENTICATION_CLIENT_ID:-""} AUTHENTICATION_CLIENT_ID: ${AUTHENTICATION_CLIENT_ID:-""}
AUTHENTICATION_CALLBACK_URL: ${AUTHENTICATION_CALLBACK_URL:-""} AUTHENTICATION_CALLBACK_URL: ${AUTHENTICATION_CALLBACK_URL:-""}
AUTHENTICATION_JWT_PRINCIPAL_CLAIMS: ${AUTHENTICATION_JWT_PRINCIPAL_CLAIMS:-[email,preferred_username,sub]}
# OpenMetadata Server Airflow Configuration # OpenMetadata Server Airflow Configuration
AIRFLOW_HOST: ${AIRFLOW_HOST:-http://ingestion:8080} AIRFLOW_HOST: ${AIRFLOW_HOST:-http://ingestion:8080}
SERVER_HOST_API_URL: ${SERVER_HOST_API_URL:-http://localhost:8585/api} SERVER_HOST_API_URL: ${SERVER_HOST_API_URL:-http://localhost:8585/api}

View File

@ -316,6 +316,7 @@ export const AuthProvider = ({
if (error.response) { if (error.response) {
const { status } = error.response; const { status } = error.response;
if (status === ClientErrors.UNAUTHORIZED) { if (status === ClientErrors.UNAUTHORIZED) {
showErrorToast(error);
resetUserDetails(true); resetUserDetails(true);
} else if (status === ClientErrors.FORBIDDEN) { } else if (status === ClientErrors.FORBIDDEN) {
showErrorToast(jsonData['api-error-messages']['forbidden-error']); showErrorToast(jsonData['api-error-messages']['forbidden-error']);

View File

@ -12,11 +12,25 @@
*/ */
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { isString } from 'lodash'; import { isEmpty, isString } from 'lodash';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import jsonData from '../jsons/en'; import jsonData from '../jsons/en';
import { getErrorText } from './StringsUtils'; import { getErrorText } from './StringsUtils';
export const hashCode = (str: string) => {
let hash = 0,
i,
chr;
if (isEmpty(str)) return hash;
for (i = 0; i < str.length; i++) {
chr = str.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
};
/** /**
* Display an error toast message. * Display an error toast message.
* @param error error text or AxiosError object * @param error error text or AxiosError object
@ -39,11 +53,17 @@ export const showErrorToast = (
errorMessage = getErrorText(error, fallback); errorMessage = getErrorText(error, fallback);
// do not show error toasts for 401 // do not show error toasts for 401
// since they will be intercepted and the user will be redirected to the signin page // since they will be intercepted and the user will be redirected to the signin page
if (error && error.response?.status === 401) { // except for principal domain mismatch errors
if (
error &&
error.response?.status === 401 &&
!errorMessage.includes('principal domain')
) {
return; return;
} }
} }
toast.error(errorMessage, { toast.error(errorMessage, {
toastId: hashCode(errorMessage),
autoClose: autoCloseTimer, autoClose: autoCloseTimer,
}); });
}; };