package auth; import static auth.AuthUtils.*; import static auth.sso.oidc.OidcConfigs.*; import static utils.ConfigUtil.*; import auth.sso.SsoConfigs; import auth.sso.SsoManager; import auth.sso.oidc.OidcConfigs; import auth.sso.oidc.OidcProvider; import client.AuthServiceClient; import com.datahub.authentication.Actor; import com.datahub.authentication.ActorType; import com.datahub.authentication.Authentication; import com.google.inject.AbstractModule; import com.google.inject.Provides; import com.google.inject.Singleton; import com.linkedin.entity.client.SystemEntityClient; import com.linkedin.entity.client.SystemRestliEntityClient; import com.linkedin.metadata.restli.DefaultRestliClientFactory; import com.linkedin.parseq.retry.backoff.ExponentialBackoff; import com.linkedin.util.Configuration; import config.ConfigurationProvider; import controllers.SsoCallbackController; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apache.commons.codec.digest.DigestUtils; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.pac4j.core.client.Client; import org.pac4j.core.client.Clients; import org.pac4j.core.config.Config; import org.pac4j.core.context.session.SessionStore; import org.pac4j.play.LogoutController; import org.pac4j.play.http.PlayHttpActionAdapter; import org.pac4j.play.store.PlayCacheSessionStore; import org.pac4j.play.store.PlayCookieSessionStore; import org.pac4j.play.store.PlaySessionStore; import org.pac4j.play.store.ShiroAesDataEncrypter; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import play.Environment; import play.cache.SyncCacheApi; import utils.ConfigUtil; /** Responsible for configuring, validating, and providing authentication related components. */ public class AuthModule extends AbstractModule { /** * Pac4j Stores Session State in a browser-side cookie in encrypted fashion. This configuration * value provides a stable encryption base from which to derive the encryption key. * *
We hash this value (SHA256), then take the first 16 bytes as the AES key.
*/
private static final String PAC4J_AES_KEY_BASE_CONF = "play.http.secret.key";
private static final String PAC4J_SESSIONSTORE_PROVIDER_CONF = "pac4j.sessionStore.provider";
private static final String ENTITY_CLIENT_RETRY_INTERVAL = "entityClient.retryInterval";
private static final String ENTITY_CLIENT_NUM_RETRIES = "entityClient.numRetries";
private final com.typesafe.config.Config _configs;
public AuthModule(final Environment environment, final com.typesafe.config.Config configs) {
_configs = configs;
}
@Override
protected void configure() {
/**
* In Pac4J, you are given the option to store the profiles of authenticated users in either (i)
* PlayCacheSessionStore - saves your data in the Play cache or (ii) PlayCookieSessionStore
* saves your data in the Play session cookie However there is problem
* (https://github.com/datahub-project/datahub/issues/4448) observed when storing the Pac4j
* profile in cookie. Whenever the profile returned by Pac4j is greater than 4096 characters,
* the response will be rejected by the browser. Default to PlayCacheCookieStore so that
* datahub-frontend container remains as a stateless service
*/
String sessionStoreProvider = _configs.getString(PAC4J_SESSIONSTORE_PROVIDER_CONF);
if (sessionStoreProvider.equals("PlayCacheSessionStore")) {
final PlayCacheSessionStore playCacheSessionStore =
new PlayCacheSessionStore(getProvider(SyncCacheApi.class));
bind(SessionStore.class).toInstance(playCacheSessionStore);
bind(PlaySessionStore.class).toInstance(playCacheSessionStore);
} else {
PlayCookieSessionStore playCacheCookieStore;
try {
// To generate a valid encryption key from an input value, we first
// hash the input to generate a fixed-length string. Then, we convert
// it to hex and slice the first 16 bytes, because AES key length must strictly
// have a specific length.
final String aesKeyBase = _configs.getString(PAC4J_AES_KEY_BASE_CONF);
final String aesKeyHash =
DigestUtils.sha256Hex(aesKeyBase.getBytes(StandardCharsets.UTF_8));
final String aesEncryptionKey = aesKeyHash.substring(0, 16);
playCacheCookieStore =
new PlayCookieSessionStore(new ShiroAesDataEncrypter(aesEncryptionKey.getBytes()));
} catch (Exception e) {
throw new RuntimeException("Failed to instantiate Pac4j cookie session store!", e);
}
bind(SessionStore.class).toInstance(playCacheCookieStore);
bind(PlaySessionStore.class).toInstance(playCacheCookieStore);
}
try {
bind(SsoCallbackController.class)
.toConstructor(
SsoCallbackController.class.getConstructor(
SsoManager.class,
Authentication.class,
SystemEntityClient.class,
AuthServiceClient.class,
com.typesafe.config.Config.class));
} catch (NoSuchMethodException | SecurityException e) {
throw new RuntimeException(
"Failed to bind to SsoCallbackController. Cannot find constructor", e);
}
// logout
final LogoutController logoutController = new LogoutController();
logoutController.setDefaultUrl("/");
bind(LogoutController.class).toInstance(logoutController);
}
@Provides
@Singleton
protected Config provideConfig(SsoManager ssoManager) {
if (ssoManager.isSsoEnabled()) {
final Clients clients = new Clients();
final List