/*
* oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text.
*
* Copyright (c) 2014, Gluu
*/
package org.xdi.oxauth.service;
import com.unboundid.ldap.sdk.ResultCode;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.LoggerContext;
import org.codehaus.jackson.map.ObjectMapper;
import org.gluu.site.ldap.OperationsFacade;
import org.gluu.site.ldap.persistence.LdapEntryManager;
import org.gluu.site.ldap.persistence.exception.LdapMappingException;
import org.jboss.weld.util.reflection.ParameterizedTypeImpl;
import org.slf4j.Logger;
import org.xdi.exception.ConfigurationException;
import org.xdi.model.SimpleProperty;
import org.xdi.model.custom.script.CustomScriptType;
import org.xdi.model.ldap.GluuLdapConfiguration;
import org.xdi.oxauth.model.appliance.GluuAppliance;
import org.xdi.oxauth.model.auth.AuthenticationMode;
import org.xdi.oxauth.model.config.ConfigurationFactory;
import org.xdi.oxauth.model.config.oxIDPAuthConf;
import org.xdi.oxauth.model.configuration.AppConfiguration;
import org.xdi.oxauth.model.util.SecurityProviderUtility;
import org.xdi.oxauth.service.cdi.event.*;
import org.xdi.oxauth.service.external.ExternalAuthenticationService;
import org.xdi.oxauth.service.status.ldap.LdapStatusTimer;
import org.xdi.service.PythonService;
import org.xdi.service.cdi.event.ConfigurationUpdate;
import org.xdi.service.cdi.event.LdapConfigurationReload;
import org.xdi.service.cdi.event.Scheduled;
import org.xdi.service.cdi.util.CdiUtil;
import org.xdi.service.custom.script.CustomScriptManager;
import org.xdi.service.ldap.LdapConnectionService;
import org.xdi.service.timer.QuartzSchedulerManager;
import org.xdi.service.timer.event.TimerEvent;
import org.xdi.service.timer.schedule.TimerSchedule;
import org.xdi.util.StringHelper;
import org.xdi.util.properties.FileConfiguration;
import org.xdi.util.security.StringEncrypter;
import org.xdi.util.security.StringEncrypter.EncryptionException;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.BeforeDestroyed;
import javax.enterprise.context.Initialized;
import javax.enterprise.event.Event;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.ServletContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author Javier Rojas Blum
* @author Yuriy Movchan
* @author Yuriy Zabrovarnyy
* @version 0.1, 24/10/2011
*/
@ApplicationScoped
@Named
public class AppInitializer {
private final static String EVENT_TYPE = "AppInitializerTimerEvent";
private final static int DEFAULT_INTERVAL = 30; // 30 seconds
public static final String LDAP_AUTH_CONFIG_NAME = "ldapAuthConfig";
public static final String LDAP_ENTRY_MANAGER_NAME = "ldapEntryManager";
public static final String LDAP_AUTH_ENTRY_MANAGER_NAME = "ldapAuthEntryManager";
@Inject
private Logger log;
@Inject
private BeanManager beanManager;
@Inject
private Event<String> event;
@Inject
private Event<TimerEvent> timerEvent;
@Inject @Named(LDAP_ENTRY_MANAGER_NAME)
private Instance<LdapEntryManager> ldapEntryManagerInstance;
@Inject @Named(LDAP_AUTH_ENTRY_MANAGER_NAME)
private Instance<List<LdapEntryManager>> ldapAuthEntryManagerInstance;
@Inject @Named(LDAP_AUTH_CONFIG_NAME)
private Instance<List<GluuLdapConfiguration>> ldapAuthConfigInstance;
@Inject
private Instance<AuthenticationMode> authenticationModeInstance;
@Inject
private Instance<EncryptionService> encryptionServiceInstance;
@Inject
private ApplianceService applianceService;
@Inject
private PythonService pythonService;
@Inject
private MetricService metricService;
@Inject
private CustomScriptManager customScriptManager;
@Inject
private ConfigurationFactory configurationFactory;
@Inject
private CleanerTimer cleanerTimer;
@Inject
private KeyGeneratorTimer keyGeneratorTimer;
@Inject
private LdapStatusTimer ldapStatusTimer;
@Inject
private QuartzSchedulerManager quartzSchedulerManager;
private FileConfiguration ldapConfig;
private List<GluuLdapConfiguration> ldapAuthConfigs;
private LdapConnectionService connectionProvider;
private LdapConnectionService bindConnectionProvider;
private List<LdapConnectionService> authConnectionProviders;
private List<LdapConnectionService> authBindConnectionProviders;
private AtomicBoolean isActive;
private long lastFinishedTime;
private AuthenticationMode authenticationMode;
@PostConstruct
public void createApplicationComponents() {
SecurityProviderUtility.installBCProvider();
}
public void applicationInitialized(@Observes @Initialized(ApplicationScoped.class) Object init) {
createConnectionProvider();
configurationFactory.create();
LdapEntryManager localLdapEntryManager = ldapEntryManagerInstance.get();
List<GluuLdapConfiguration> ldapAuthConfigs = loadLdapAuthConfigs(localLdapEntryManager);
createAuthConnectionProviders(ldapAuthConfigs);
setDefaultAuthenticationMethod(localLdapEntryManager);
// Initialize python interpreter
pythonService.initPythonInterpreter(configurationFactory.getLdapConfiguration().getString("pythonModulesDir", null));
// Initialize script manager
List<CustomScriptType> supportedCustomScriptTypes = Arrays.asList(CustomScriptType.PERSON_AUTHENTICATION, CustomScriptType.CLIENT_REGISTRATION,
CustomScriptType.ID_GENERATOR, CustomScriptType.UMA_AUTHORIZATION_POLICY, CustomScriptType.APPLICATION_SESSION, CustomScriptType.DYNAMIC_SCOPE);
// Start timer
quartzSchedulerManager.start();
// Schedule timer tasks
metricService.initTimer();
configurationFactory.initTimer();
ldapStatusTimer.initTimer();
cleanerTimer.initTimer();
customScriptManager.initTimer(supportedCustomScriptTypes);
keyGeneratorTimer.initTimer();
initTimer();
}
@Produces @ApplicationScoped
public StringEncrypter getStringEncrypter() {
String encodeSalt = configurationFactory.getCryptoConfigurationSalt();
if (StringHelper.isEmpty(encodeSalt)) {
throw new ConfigurationException("Encode salt isn't defined");
}
try {
StringEncrypter stringEncrypter = StringEncrypter.instance(encodeSalt);
return stringEncrypter;
} catch (EncryptionException ex) {
throw new ConfigurationException("Failed to create StringEncrypter instance");
}
}
public void initTimer() {
this.isActive = new AtomicBoolean(false);
this.lastFinishedTime = System.currentTimeMillis();
timerEvent.fire(new TimerEvent(new TimerSchedule(1 * 60, DEFAULT_INTERVAL), new AuthConfigurationEvent(),
Scheduled.Literal.INSTANCE));
}
public void destroy(@Observes @BeforeDestroyed(ApplicationScoped.class) ServletContext init) {
log.info("Closing LDAP connection at server shutdown...");
LdapEntryManager ldapEntryManager = ldapEntryManagerInstance.get();
closeLdapEntryManager(ldapEntryManager);
List<LdapEntryManager> ldapAuthEntryManagers = ldapAuthEntryManagerInstance.get();
closeLdapAuthEntryManagers(ldapAuthEntryManagers);
}
public void reloadConfigurationTimerEvent(@Observes @Scheduled AuthConfigurationEvent authConfigurationEvent) {
if (this.isActive.get()) {
return;
}
if (!this.isActive.compareAndSet(false, true)) {
return;
}
try {
reloadConfiguration();
} catch (Throwable ex) {
log.error("Exception happened while reloading application configuration", ex);
} finally {
this.isActive.set(false);
this.lastFinishedTime = System.currentTimeMillis();
}
}
private void reloadConfiguration() {
LdapEntryManager localLdapEntryManager = ldapEntryManagerInstance.get();
log.trace("Attempting to use {}: {}", LDAP_ENTRY_MANAGER_NAME, localLdapEntryManager.getLdapOperationService());
List<GluuLdapConfiguration> newLdapAuthConfigs = loadLdapAuthConfigs(localLdapEntryManager);
if (!this.ldapAuthConfigs.equals(newLdapAuthConfigs)) {
recreateLdapAuthEntryManagers(newLdapAuthConfigs);
event.select(ReloadAuthScript.Literal.INSTANCE).fire(ExternalAuthenticationService.MODIFIED_INTERNAL_TYPES_EVENT_TYPE);
setDefaultAuthenticationMethod(localLdapEntryManager);
}
}
/*
* Utility method which can be used in custom scripts
*/
public LdapEntryManager createLdapAuthEntryManager(GluuLdapConfiguration ldapAuthConfig) {
LdapConnectionProviders ldapConnectionProviders = createAuthConnectionProviders(ldapAuthConfig);
LdapEntryManager ldapAuthEntryManager = new LdapEntryManager(new OperationsFacade(ldapConnectionProviders.getConnectionProvider(), ldapConnectionProviders.getConnectionBindProvider()));
log.debug("Created custom authentication LdapEntryManager: {}", ldapAuthEntryManager);
return ldapAuthEntryManager;
}
@Produces @ApplicationScoped @Named(LDAP_ENTRY_MANAGER_NAME)
public LdapEntryManager getLdapEntryManager() {
LdapEntryManager ldapEntryManager = new LdapEntryManager(new OperationsFacade(this.connectionProvider, this.bindConnectionProvider));
log.info("Created {}: {}", new Object[] { LDAP_ENTRY_MANAGER_NAME, ldapEntryManager.getLdapOperationService() });
return ldapEntryManager;
}
@Produces @ApplicationScoped @Named(LDAP_AUTH_CONFIG_NAME)
public List<GluuLdapConfiguration> createLdapAuthConfigs() {
return ldapAuthConfigs;
}
@Produces @ApplicationScoped @Named(LDAP_AUTH_ENTRY_MANAGER_NAME)
public List<LdapEntryManager> createLdapAuthEntryManager() {
List<LdapEntryManager> ldapAuthEntryManagers = new ArrayList<LdapEntryManager>();
if (this.ldapAuthConfigs.size() == 0) {
return ldapAuthEntryManagers;
}
for (int i = 0; i < this.ldapAuthConfigs.size(); i++) {
LdapEntryManager ldapAuthEntryManager = new LdapEntryManager(new OperationsFacade(this.authConnectionProviders.get(i), this.authBindConnectionProviders.get(i)));
log.debug("Created {}#{}: {}", new Object[] { LDAP_AUTH_ENTRY_MANAGER_NAME, i, ldapAuthEntryManager });
ldapAuthEntryManagers.add(ldapAuthEntryManager);
}
return ldapAuthEntryManagers;
}
public void recreateLdapEntryManager(@Observes @LdapConfigurationReload String event) {
// Get existing application scoped instance
LdapEntryManager oldLdapEntryManager = CdiUtil.getContextBean(beanManager, LdapEntryManager.class, LDAP_ENTRY_MANAGER_NAME);
// Recreate components
createConnectionProvider();
// Close existing connections
closeLdapEntryManager(oldLdapEntryManager);
// Force to create new bean
LdapEntryManager ldapEntryManager = ldapEntryManagerInstance.get();
ldapEntryManagerInstance.destroy(ldapEntryManager);
log.info("Recreated instance {}: {}", LDAP_ENTRY_MANAGER_NAME, ldapEntryManager);
}
private void createConnectionProvider() {
this.ldapConfig = configurationFactory.getLdapConfiguration();
Properties connectionProperties = (Properties) this.ldapConfig.getProperties();
this.connectionProvider = createConnectionProvider(connectionProperties);
log.debug("Created connectionProvider: {}", connectionProvider);
Properties bindConnectionProperties = prepareBindConnectionProperties(connectionProperties);
this.bindConnectionProvider = createBindConnectionProvider(bindConnectionProperties, connectionProperties);
log.debug("Created bindConnectionProvider: {}", bindConnectionProvider);
}
private void closeLdapEntryManager(LdapEntryManager oldLdapEntryManager) {
// Close existing connections
log.debug("Attempting to destroy {}: {}", LDAP_ENTRY_MANAGER_NAME, oldLdapEntryManager);
oldLdapEntryManager.destroy();
log.debug("Destroyed {}: {}", LDAP_ENTRY_MANAGER_NAME, oldLdapEntryManager);
}
public void recreateLdapAuthEntryManagers(List<GluuLdapConfiguration> newLdapAuthConfigs) {
// Get existing application scoped instance
List<LdapEntryManager> oldLdapAuthEntryManagers = CdiUtil.getContextBean(beanManager,
new ParameterizedTypeImpl(List.class, LdapEntryManager.class), LDAP_AUTH_ENTRY_MANAGER_NAME);
// Recreate components
createAuthConnectionProviders(newLdapAuthConfigs);
// Close existing connections
closeLdapAuthEntryManagers(oldLdapAuthEntryManagers);
// Destroy old Ldap auth entry managers
for (LdapEntryManager oldLdapAuthEntryManager : oldLdapAuthEntryManagers) {
log.debug("Attempting to destroy {}: {}", LDAP_AUTH_ENTRY_MANAGER_NAME, oldLdapAuthEntryManager);
oldLdapAuthEntryManager.destroy();
log.debug("Destroyed {}: {}", LDAP_AUTH_ENTRY_MANAGER_NAME, oldLdapAuthEntryManager);
}
// Force to create new bean
List<LdapEntryManager> ldapAuthEntryManagers = ldapAuthEntryManagerInstance.get();
ldapAuthEntryManagerInstance.destroy(ldapAuthEntryManagers);
log.info("Recreated instance {}: {}", LDAP_AUTH_ENTRY_MANAGER_NAME, ldapAuthEntryManagers);
}
private void createAuthConnectionProviders(List<GluuLdapConfiguration> newLdapAuthConfigs) {
// Backup current references to objects to allow shutdown properly
List<GluuLdapConfiguration> oldLdapAuthConfigs = ldapAuthConfigInstance.get();
List<LdapConnectionService> tmpAuthConnectionProviders = new ArrayList<LdapConnectionService>();
List<LdapConnectionService> tmpAuthBindConnectionProviders = new ArrayList<LdapConnectionService>();
// Prepare connection providers per LDAP authentication configuration
for (GluuLdapConfiguration ldapAuthConfig : newLdapAuthConfigs) {
LdapConnectionProviders ldapConnectionProviders = createAuthConnectionProviders(ldapAuthConfig);
tmpAuthConnectionProviders.add(ldapConnectionProviders.getConnectionProvider());
tmpAuthBindConnectionProviders.add(ldapConnectionProviders.getConnectionBindProvider());
}
this.ldapAuthConfigs = newLdapAuthConfigs;
this.authConnectionProviders = tmpAuthConnectionProviders;
this.authBindConnectionProviders = tmpAuthBindConnectionProviders;
ldapAuthConfigInstance.destroy(oldLdapAuthConfigs);
}
private void closeLdapAuthEntryManagers(List<LdapEntryManager> oldLdapAuthEntryManagers) {
// Close existing connections
for (LdapEntryManager oldLdapAuthEntryManager : oldLdapAuthEntryManagers) {
log.debug("Attempting to destroy {}: {}", LDAP_AUTH_ENTRY_MANAGER_NAME, oldLdapAuthEntryManager);
oldLdapAuthEntryManager.destroy();
log.debug("Destroyed {}: {}", LDAP_AUTH_ENTRY_MANAGER_NAME, oldLdapAuthEntryManager);
}
}
public LdapConnectionProviders createAuthConnectionProviders(GluuLdapConfiguration ldapAuthConfig) {
Properties connectionProperties = prepareAuthConnectionProperties(ldapAuthConfig);
LdapConnectionService connectionProvider = createConnectionProvider(connectionProperties);
Properties bindConnectionProperties = prepareBindConnectionProperties(connectionProperties);
LdapConnectionService bindConnectionProvider = createBindConnectionProvider(bindConnectionProperties, connectionProperties);
return new LdapConnectionProviders(connectionProvider, bindConnectionProvider);
}
private Properties prepareAuthConnectionProperties(GluuLdapConfiguration ldapAuthConfig) {
FileConfiguration configuration = configurationFactory.getLdapConfiguration();
Properties properties = (Properties) configuration.getProperties().clone();
if (ldapAuthConfig != null) {
properties.setProperty("servers", buildServersString(ldapAuthConfig.getServers()));
String bindDn = ldapAuthConfig.getBindDN();
if (StringHelper.isNotEmpty(bindDn)) {
properties.setProperty("bindDN", bindDn);
properties.setProperty("bindPassword", ldapAuthConfig.getBindPassword());
}
properties.setProperty("useSSL", Boolean.toString(ldapAuthConfig.isUseSSL()));
properties.setProperty("maxconnections", Integer.toString(ldapAuthConfig.getMaxConnections()));
}
return properties;
}
private Properties prepareBindConnectionProperties(Properties connectionProperties) {
// TODO: Use own properties with prefix specified in variable 'bindConfigurationComponentName'
Properties bindProperties = (Properties) connectionProperties.clone();
bindProperties.remove("bindDN");
bindProperties.remove("bindPassword");
return bindProperties;
}
private LdapConnectionService createConnectionProvider(Properties connectionProperties) {
EncryptionService securityService = encryptionServiceInstance.get();
LdapConnectionService connectionProvider = new LdapConnectionService(securityService.decryptProperties(connectionProperties));
return connectionProvider;
}
private LdapConnectionService createBindConnectionProvider(Properties bindConnectionProperties, Properties connectionProperties) {
LdapConnectionService bindConnectionProvider = createConnectionProvider(bindConnectionProperties);
if (ResultCode.INAPPROPRIATE_AUTHENTICATION.equals(bindConnectionProvider.getCreationResultCode())) {
log.warn("It's not possible to create authentication LDAP connection pool using anonymous bind. Attempting to create it using binDN/bindPassword");
bindConnectionProvider = createConnectionProvider(connectionProperties);
}
return bindConnectionProvider;
}
private String buildServersString(List<?> servers) {
StringBuilder sb = new StringBuilder();
if (servers == null) {
return sb.toString();
}
boolean first = true;
for (Object server : servers) {
if (first) {
first = false;
} else {
sb.append(",");
}
if (server instanceof SimpleProperty) {
sb.append(((SimpleProperty) server).getValue());
} else {
sb.append(server);
}
}
return sb.toString();
}
private List<oxIDPAuthConf> loadLdapIdpAuthConfigs(LdapEntryManager localLdapEntryManager) {
GluuAppliance appliance = loadAppliance(localLdapEntryManager, "oxIDPAuthentication");
if ((appliance == null) || (appliance.getOxIDPAuthentication() == null)) {
return null;
}
List<oxIDPAuthConf> configurations = new ArrayList<oxIDPAuthConf>();
for (String configurationJson : appliance.getOxIDPAuthentication()) {
try {
oxIDPAuthConf configuration = (oxIDPAuthConf) jsonToObject(configurationJson, oxIDPAuthConf.class);
if (configuration.getType().equalsIgnoreCase("ldap") || configuration.getType().equalsIgnoreCase("auth")) {
configurations.add(configuration);
}
} catch (Exception ex) {
log.error("Failed to create object by json: '{}'", ex, configurationJson);
}
}
return configurations;
}
private void setDefaultAuthenticationMethod(LdapEntryManager localLdapEntryManager) {
GluuAppliance appliance = loadAppliance(localLdapEntryManager, "oxAuthenticationMode");
authenticationMode = null;
if (appliance != null) {
this.authenticationMode = new AuthenticationMode(appliance.getAuthenticationMode());
}
authenticationModeInstance.destroy(authenticationModeInstance.get());
}
@Produces @ApplicationScoped
public AuthenticationMode getDefaultAuthenticationMode() {
return authenticationMode;
}
private GluuAppliance loadAppliance(LdapEntryManager localLdapEntryManager, String ... ldapReturnAttributes) {
String baseDn = configurationFactory.getBaseDn().getAppliance();
String applianceInum = configurationFactory.getAppConfiguration().getApplianceInum();
if (StringHelper.isEmpty(baseDn) || StringHelper.isEmpty(applianceInum)) {
return null;
}
String applianceDn = String.format("inum=%s,%s", applianceInum, baseDn);
GluuAppliance appliance = null;
try {
appliance = localLdapEntryManager.find(GluuAppliance.class, applianceDn, ldapReturnAttributes);
} catch (LdapMappingException ex) {
log.error("Failed to load appliance entry from Ldap", ex);
return null;
}
return appliance;
}
public GluuLdapConfiguration loadLdapAuthConfig(oxIDPAuthConf configuration) {
if (configuration == null) {
return null;
}
try {
if (configuration.getType().equalsIgnoreCase("auth")) {
return mapLdapConfig(configuration.getConfig());
}
} catch (Exception ex) {
log.error("Failed to create object by oxIDPAuthConf: '{}'", ex, configuration);
}
return null;
}
private List<GluuLdapConfiguration> loadLdapAuthConfigs(LdapEntryManager localLdapEntryManager) {
List<GluuLdapConfiguration> ldapAuthConfigs = new ArrayList<GluuLdapConfiguration>();
List<oxIDPAuthConf> ldapIdpAuthConfigs = loadLdapIdpAuthConfigs(localLdapEntryManager);
if (ldapIdpAuthConfigs == null) {
return ldapAuthConfigs;
}
for (oxIDPAuthConf ldapIdpAuthConfig : ldapIdpAuthConfigs) {
GluuLdapConfiguration ldapAuthConfig = loadLdapAuthConfig(ldapIdpAuthConfig);
if ((ldapAuthConfig != null) && ldapAuthConfig.isEnabled()) {
ldapAuthConfigs.add(ldapAuthConfig);
}
}
return ldapAuthConfigs;
}
private GluuLdapConfiguration mapLdapConfig(String config) throws Exception {
return (GluuLdapConfiguration) jsonToObject(config, GluuLdapConfiguration.class);
}
private Object jsonToObject(String json, Class<?> clazz) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Object clazzObject = mapper.readValue(json, clazz);
return clazzObject;
}
public void updateLoggingSeverity(@Observes @ConfigurationUpdate AppConfiguration appConfiguration) {
String loggingLevel = appConfiguration.getLoggingLevel();
if (StringHelper.isEmpty(loggingLevel)) {
return;
}
log.info("Setting loggers level to: '{}'", loggingLevel);
LoggerContext loggerContext = LoggerContext.getContext(false);
if (StringHelper.equalsIgnoreCase("DEFAULT", loggingLevel)) {
log.info("Reloading log4j configuration");
loggerContext.reconfigure();
return;
}
Level level = Level.toLevel(loggingLevel, Level.INFO);
for (org.apache.logging.log4j.core.Logger logger : loggerContext.getLoggers()) {
String loggerName = logger.getName();
if (loggerName.startsWith("org.xdi.service") || loggerName.startsWith("org.xdi.oxauth") || loggerName.startsWith("org.gluu")) {
logger.setLevel(level);
}
}
}
private class LdapConnectionProviders {
private LdapConnectionService connectionProvider;
private LdapConnectionService connectionBindProvider;
public LdapConnectionProviders(LdapConnectionService connectionProvider, LdapConnectionService connectionBindProvider) {
this.connectionProvider = connectionProvider;
this.connectionBindProvider = connectionBindProvider;
}
public LdapConnectionService getConnectionProvider() {
return connectionProvider;
}
public LdapConnectionService getConnectionBindProvider() {
return connectionBindProvider;
}
}
}