package auth; import client.AuthServiceClient; import com.datahub.authentication.Actor; import com.datahub.authentication.ActorType; import com.google.inject.AbstractModule; import com.google.inject.Provides; import com.google.inject.Singleton; import com.linkedin.entity.client.EntityClient; import com.linkedin.entity.client.RestliEntityClient; import com.linkedin.metadata.restli.DefaultRestliClientFactory; import com.linkedin.util.Configuration; import com.datahub.authentication.Authentication; import java.nio.charset.StandardCharsets; import java.util.Collections; import org.apache.commons.codec.digest.DigestUtils; 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.PlayCookieSessionStore; import org.pac4j.play.store.PlaySessionStore; import org.pac4j.play.store.ShiroAesDataEncrypter; import play.Environment; import java.util.ArrayList; import java.util.List; import auth.sso.oidc.OidcProvider; import auth.sso.oidc.OidcConfigs; import auth.sso.SsoConfigs; import auth.sso.SsoManager; import controllers.SsoCallbackController; import utils.ConfigUtil; import static auth.AuthUtils.*; import static auth.sso.oidc.OidcConfigs.*; import static 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 (SHA1), then take the first 16 bytes as the AES key. */ private static final String PAC4J_AES_KEY_BASE_CONF = "play.http.secret.key"; private final com.typesafe.config.Config _configs; public AuthModule(final Environment environment, final com.typesafe.config.Config configs) { _configs = configs; } @Override protected void configure() { 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.sha1Hex(aesKeyBase.getBytes(StandardCharsets.UTF_8)); final String aesEncryptionKey = aesKeyHash.substring(0, 16); playCacheCookieStore = new PlayCookieSessionStore( new ShiroAesDataEncrypter(aesEncryptionKey)); } 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, EntityClient.class, AuthServiceClient.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 clientList = new ArrayList<>(); clientList.add(ssoManager.getSsoProvider().client()); clients.setClients(clientList); final Config config = new Config(clients); config.setHttpActionAdapter(new PlayHttpActionAdapter()); return config; } return new Config(); } @Provides @Singleton protected SsoManager provideSsoManager() { SsoManager manager = new SsoManager(); // Seed the SSO manager with a default SSO provider. if (isSsoEnabled(_configs)) { SsoConfigs ssoConfigs = new SsoConfigs(_configs); if (ssoConfigs.isOidcEnabled()) { // Register OIDC Provider, add to list of managers. OidcConfigs oidcConfigs = new OidcConfigs(_configs); OidcProvider oidcProvider = new OidcProvider(oidcConfigs); // Set the default SSO provider to this OIDC client. manager.setSsoProvider(oidcProvider); } } return manager; } @Provides @Singleton protected Authentication provideSystemAuthentication() { // Returns an instance of Authentication used to authenticate system initiated calls to Metadata Service. String systemClientId = _configs.getString(SYSTEM_CLIENT_ID_CONFIG_PATH); String systemSecret = _configs.getString(SYSTEM_CLIENT_SECRET_CONFIG_PATH); final Actor systemActor = new Actor(ActorType.USER, systemClientId); // TODO: Change to service actor once supported. return new Authentication( systemActor, String.format("Basic %s:%s", systemClientId, systemSecret), Collections.emptyMap() ); } @Provides @Singleton protected EntityClient provideEntityClient() { return new RestliEntityClient(buildRestliClient()); } @Provides @Singleton protected AuthServiceClient provideAuthClient(Authentication systemAuthentication) { // Init a GMS auth client final String metadataServiceHost = _configs.hasPath(METADATA_SERVICE_HOST_CONFIG_PATH) ? _configs.getString(METADATA_SERVICE_HOST_CONFIG_PATH) : Configuration.getEnvironmentVariable(GMS_HOST_ENV_VAR, DEFAULT_GMS_HOST); final int metadataServicePort = _configs.hasPath(METADATA_SERVICE_PORT_CONFIG_PATH) ? _configs.getInt(METADATA_SERVICE_PORT_CONFIG_PATH) : Integer.parseInt(Configuration.getEnvironmentVariable(GMS_PORT_ENV_VAR, DEFAULT_GMS_PORT)); final Boolean metadataServiceUseSsl = _configs.hasPath(METADATA_SERVICE_USE_SSL_CONFIG_PATH) ? _configs.getBoolean(METADATA_SERVICE_USE_SSL_CONFIG_PATH) : Boolean.parseBoolean(Configuration.getEnvironmentVariable(GMS_USE_SSL_ENV_VAR, DEFAULT_GMS_USE_SSL)); return new AuthServiceClient( metadataServiceHost, metadataServicePort, metadataServiceUseSsl, systemAuthentication); } private com.linkedin.restli.client.Client buildRestliClient() { final String metadataServiceHost = utils.ConfigUtil.getString( _configs, METADATA_SERVICE_HOST_CONFIG_PATH, utils.ConfigUtil.DEFAULT_METADATA_SERVICE_HOST); final int metadataServicePort = utils.ConfigUtil.getInt( _configs, utils.ConfigUtil.METADATA_SERVICE_PORT_CONFIG_PATH, utils.ConfigUtil.DEFAULT_METADATA_SERVICE_PORT); final boolean metadataServiceUseSsl = utils.ConfigUtil.getBoolean( _configs, utils.ConfigUtil.METADATA_SERVICE_USE_SSL_CONFIG_PATH, ConfigUtil.DEFAULT_METADATA_SERVICE_USE_SSL ); final String metadataServiceSslProtocol = utils.ConfigUtil.getString( _configs, utils.ConfigUtil.METADATA_SERVICE_SSL_PROTOCOL_CONFIG_PATH, ConfigUtil.DEFAULT_METADATA_SERVICE_SSL_PROTOCOL ); return DefaultRestliClientFactory.getRestLiClient(metadataServiceHost, metadataServicePort, metadataServiceUseSsl, metadataServiceSslProtocol); } protected boolean isSsoEnabled(com.typesafe.config.Config configs) { // If OIDC is enabled, we infer SSO to be enabled. return configs.hasPath(OIDC_ENABLED_CONFIG_PATH) && Boolean.TRUE.equals( Boolean.parseBoolean(configs.getString(OIDC_ENABLED_CONFIG_PATH))); } protected boolean isMetadataServiceAuthEnabled(com.typesafe.config.Config configs) { // If OIDC is enabled, we infer SSO to be enabled. return configs.hasPath(METADATA_SERVICE_AUTH_ENABLED_CONFIG_PATH) && Boolean.TRUE.equals( Boolean.parseBoolean(configs.getString(METADATA_SERVICE_AUTH_ENABLED_CONFIG_PATH))); } }