mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-06 05:22:53 +00:00
Backend: Enforce principal domain in the JWT filter (#5155)
This commit is contained in:
parent
c1fa31eacc
commit
059acfc529
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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)) {
|
||||||
|
|||||||
@ -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());
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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");
|
||||||
|
|||||||
@ -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() {}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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:-""}
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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']);
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user