| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.felix.jaas.internal; |
| |
| import org.apache.felix.jaas.LoginContextFactory; |
| import org.apache.felix.jaas.LoginModuleFactory; |
| import org.apache.felix.jaas.boot.ProxyLoginModule; |
| import org.apache.felix.scr.annotations.Component; |
| import org.apache.felix.scr.annotations.ConfigurationPolicy; |
| import org.apache.felix.scr.annotations.Property; |
| import org.apache.felix.scr.annotations.PropertyOption; |
| import org.apache.sling.commons.osgi.PropertiesUtil; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.framework.ServiceRegistration; |
| import org.osgi.service.cm.ConfigurationException; |
| import org.osgi.service.cm.ManagedService; |
| import org.osgi.service.log.LogService; |
| import org.osgi.util.tracker.ServiceTracker; |
| import org.osgi.util.tracker.ServiceTrackerCustomizer; |
| |
| import javax.security.auth.Subject; |
| import javax.security.auth.callback.CallbackHandler; |
| import javax.security.auth.login.*; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.NoSuchProviderException; |
| import java.security.Provider; |
| import java.security.Security; |
| import java.util.*; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| @Component(label = "%jaas.spi.name", |
| description = "%jaas.spi.description", |
| metatype = true, |
| ds = false, |
| name = "org.apache.felix.jaas.ConfigurationSpi", |
| policy = ConfigurationPolicy.REQUIRE) |
| public class ConfigSpiOsgi extends ConfigurationSpi implements ManagedService, |
| ServiceTrackerCustomizer, LoginContextFactory |
| { |
| /** |
| * Name of the algorithm to use to fetch JAAS Config |
| */ |
| public static final String JAAS_CONFIG_ALGO_NAME = "JavaLoginConfig"; |
| |
| public static final String SERVICE_PID = "org.apache.felix.jaas.ConfigurationSpi"; |
| |
| private enum GlobalConfigurationPolicy |
| { |
| DEFAULT, REPLACE, PROXY |
| } |
| |
| private volatile Map<String, Realm> configs = Collections.emptyMap(); |
| |
| private final Logger log; |
| |
| /** |
| * This is the name of application/realm used by LoginContext if no |
| * AppConfigurationEntry is found for the appName which is passed while constructing it. |
| * |
| * In case it does not find any config it looks for config entry for an app named 'other' |
| */ |
| static final String DEFAULT_REALM_NAME = "other"; |
| |
| @Property |
| private static final String JAAS_DEFAULT_REALM_NAME = "jaas.defaultRealmName"; |
| private String defaultRealmName; |
| |
| private static final String DEFAULT_CONFIG_PROVIDER_NAME = "FelixJaasProvider"; |
| @Property(value = DEFAULT_CONFIG_PROVIDER_NAME) |
| private static final String JAAS_CONFIG_PROVIDER_NAME = "jaas.configProviderName"; |
| |
| @Property(value = "default", options = { |
| @PropertyOption(name = "default", value = "%jaas.configPolicy.default"), |
| @PropertyOption(name = "replace", value = "%jaas.configPolicy.replace"), |
| @PropertyOption(name = "proxy", value = "%jaas.configPolicy.proxy") }) |
| static final String JAAS_CONFIG_POLICY = "jaas.globalConfigPolicy"; |
| |
| private final Configuration osgiConfig = new OsgiConfiguration(); |
| |
| private final Configuration originalConfig; |
| |
| private final Configuration proxyConfig; |
| |
| private volatile GlobalConfigurationPolicy globalConfigPolicy = GlobalConfigurationPolicy.DEFAULT; |
| |
| private final Map<ServiceReference, LoginModuleProvider> providerMap = new ConcurrentHashMap<ServiceReference, LoginModuleProvider>(); |
| |
| private volatile String jaasConfigProviderName; |
| |
| private final Object lock = new Object(); |
| |
| private final BundleContext context; |
| |
| private final ServiceTracker tracker; |
| |
| private ServiceRegistration spiReg; |
| |
| public ConfigSpiOsgi(BundleContext context, Logger log) throws ConfigurationException { |
| this.context = context; |
| this.log = log; |
| this.originalConfig = getGlobalConfiguration(); |
| this.proxyConfig = new DelegatingConfiguration(osgiConfig, originalConfig); |
| |
| updated(getDefaultConfig()); |
| this.tracker = new ServiceTracker(context, LoginModuleFactory.class.getName(), |
| this); |
| |
| Properties props = new Properties(); |
| props.setProperty(Constants.SERVICE_VENDOR, "Apache Software Foundation"); |
| props.setProperty(Constants.SERVICE_PID, SERVICE_PID); |
| |
| this.context.registerService(ManagedService.class.getName(), this, props); |
| |
| //TODO Should this registration be made conditional i.e. service is only registered |
| //only if there active LoginModules present |
| this.context.registerService(LoginContextFactory.class.getName(), this, new Properties()); |
| } |
| |
| @Override |
| public LoginContext createLoginContext(String realm, Subject subject, |
| CallbackHandler handler) throws LoginException |
| { |
| final Thread currentThread = Thread.currentThread(); |
| final ClassLoader cl = currentThread.getContextClassLoader(); |
| try |
| { |
| currentThread.setContextClassLoader(ProxyLoginModule.class.getClassLoader()); |
| Configuration config = Configuration.getInstance("JavaLoginConfig", null, |
| jaasConfigProviderName); |
| return new LoginContext(realm, subject, handler, config); |
| } |
| catch (NoSuchProviderException e) |
| { |
| throw new LoginException(e.getMessage()); |
| } |
| catch (NoSuchAlgorithmException e) |
| { |
| throw new LoginException(e.getMessage()); |
| } |
| finally |
| { |
| currentThread.setContextClassLoader(cl); |
| } |
| } |
| |
| @Override |
| protected AppConfigurationEntry[] engineGetAppConfigurationEntry(String name) |
| { |
| Realm realm = configs.get(name); |
| if (realm == null) |
| { |
| log.log(LogService.LOG_WARNING, "No JAAS module configured for realm " + name); |
| return null; |
| } |
| |
| return realm.engineGetAppConfigurationEntry(); |
| } |
| |
| Map<String, Realm> getAllConfiguration() |
| { |
| return configs; |
| } |
| |
| private void recreateConfigs() |
| { |
| Map<String, Realm> realmToConfigMap = new HashMap<String, Realm>(); |
| for (LoginModuleProvider lmp : providerMap.values()) |
| { |
| String realmName = lmp.realmName(); |
| if (realmName == null) |
| { |
| realmName = defaultRealmName; |
| } |
| |
| Realm realm = realmToConfigMap.get(realmName); |
| if (realm == null) |
| { |
| realm = new Realm(realmName); |
| realmToConfigMap.put(realmName, realm); |
| } |
| |
| realm.add(new AppConfigurationHolder(lmp)); |
| } |
| |
| for (Realm realm : realmToConfigMap.values()) |
| { |
| realm.afterPropertiesSet(); |
| } |
| |
| this.configs = Collections.unmodifiableMap(realmToConfigMap); |
| } |
| |
| private void registerSpiWithOSGi() |
| { |
| //We also register the Spi with OSGI SR if any configuration is available |
| //This would allow any client component to determine when it should start |
| //and use the config |
| if (spiReg == null && configs != null && !configs.isEmpty()) |
| { |
| Properties props = new Properties(); |
| props.setProperty("providerName", "felix"); |
| |
| spiReg = context.registerService(ConfigurationSpi.class.getName(), this, |
| props); |
| } |
| } |
| |
| //--------------LifeCycle methods ------------------------------------- |
| |
| void open() |
| { |
| this.configs = Collections.emptyMap(); |
| this.tracker.open(); |
| } |
| |
| void close() |
| { |
| |
| if (spiReg != null) |
| { |
| spiReg.unregister(); |
| } |
| |
| this.tracker.close(); |
| deregisterProvider(jaasConfigProviderName); |
| |
| synchronized (lock) |
| { |
| providerMap.clear(); |
| configs = null; //Cannot call clear as its an unmodifiable map |
| } |
| |
| if (globalConfigPolicy != GlobalConfigurationPolicy.DEFAULT) |
| { |
| restoreOriginalConfiguration(); |
| } |
| } |
| |
| // --------------Config handling ---------------------------------------- |
| |
| @Override |
| public synchronized void updated(Dictionary properties) throws ConfigurationException |
| { |
| //TODO Do not know but for fresh install it is null |
| if (properties == null) |
| { |
| return; |
| } |
| String newDefaultRealmName = PropertiesUtil.toString( |
| properties.get(JAAS_DEFAULT_REALM_NAME), DEFAULT_REALM_NAME); |
| |
| if (!newDefaultRealmName.equals(defaultRealmName)) |
| { |
| synchronized (lock) |
| { |
| defaultRealmName = newDefaultRealmName; |
| recreateConfigs(); |
| } |
| } |
| |
| String newProviderName = PropertiesUtil.toString(properties.get(JAAS_CONFIG_PROVIDER_NAME), |
| DEFAULT_CONFIG_PROVIDER_NAME); |
| |
| deregisterProvider(jaasConfigProviderName); |
| registerProvider(newProviderName); |
| jaasConfigProviderName = newProviderName; |
| |
| manageGlobalConfiguration(properties); |
| } |
| |
| private void manageGlobalConfiguration(Dictionary props) |
| { |
| String configPolicy = PropertiesUtil.toString(props.get(JAAS_CONFIG_POLICY), |
| GlobalConfigurationPolicy.DEFAULT.name()); |
| configPolicy = Util.trimToNull(configPolicy); |
| |
| GlobalConfigurationPolicy policy = GlobalConfigurationPolicy.DEFAULT; |
| if (configPolicy != null) |
| { |
| policy = GlobalConfigurationPolicy.valueOf(configPolicy.toUpperCase()); |
| } |
| |
| this.globalConfigPolicy = policy; |
| |
| if (policy == GlobalConfigurationPolicy.REPLACE) |
| { |
| Configuration.setConfiguration(osgiConfig); |
| log.log(LogService.LOG_INFO, |
| "Replacing the global JAAS configuration with OSGi based configuration"); |
| } |
| else if (policy == GlobalConfigurationPolicy.PROXY) |
| { |
| Configuration.setConfiguration(proxyConfig); |
| log.log( |
| LogService.LOG_INFO, |
| "Replacing the global JAAS configuration with OSGi based proxy configuration. " |
| + "It would look first in the OSGi based configuration and if not found would use the default global " |
| + "configuration"); |
| } |
| else if (policy == GlobalConfigurationPolicy.DEFAULT) |
| { |
| restoreOriginalConfiguration(); |
| } |
| } |
| |
| private void restoreOriginalConfiguration() |
| { |
| if (originalConfig == null) |
| { |
| return; |
| } |
| |
| Configuration current = Configuration.getConfiguration(); |
| if (current != originalConfig) |
| { |
| Configuration.setConfiguration(originalConfig); |
| } |
| } |
| |
| private Dictionary<String,String> getDefaultConfig() throws ConfigurationException |
| { |
| //Determine default config. Value can still be overridden by BundleContext properties |
| Dictionary<String,String> dict = new Hashtable<String,String>(); |
| put(dict,JAAS_DEFAULT_REALM_NAME,DEFAULT_REALM_NAME); |
| put(dict,JAAS_CONFIG_PROVIDER_NAME,DEFAULT_CONFIG_PROVIDER_NAME); |
| put(dict, JAAS_CONFIG_POLICY, GlobalConfigurationPolicy.DEFAULT.name()); |
| return dict; |
| } |
| |
| private void put(Dictionary<String,String> dict, String key, String defaultValue) |
| { |
| dict.put(key,PropertiesUtil.toString(context.getProperty(key),defaultValue)); |
| } |
| |
| // --------------JAAS/JCA/Security ---------------------------------------- |
| |
| private void registerProvider(String providerName) |
| { |
| Security.addProvider(new OSGiProvider(providerName)); |
| log.log(LogService.LOG_INFO, "Registered provider " + providerName |
| + " for managing JAAS config with type " + JAAS_CONFIG_ALGO_NAME); |
| } |
| |
| private void deregisterProvider(String providerName) |
| { |
| if (providerName != null) |
| { |
| Security.removeProvider(providerName); |
| log.log(LogService.LOG_INFO, "Removed provider " + providerName + " type " |
| + JAAS_CONFIG_ALGO_NAME + " from Security providers list"); |
| } |
| } |
| |
| // ---------- ServiceTracker ---------------------------------------------- |
| |
| @Override |
| public Object addingService(ServiceReference reference) |
| { |
| LoginModuleFactory lmf = (LoginModuleFactory) context.getService(reference); |
| boolean registerSpi = false; |
| synchronized (lock) |
| { |
| boolean noConfigAtStart = configs.isEmpty(); |
| registerFactory(reference, lmf); |
| recreateConfigs(); |
| if (spiReg == null && noConfigAtStart && !configs.isEmpty()) |
| { |
| registerSpi = true; |
| } |
| } |
| |
| //Register SPI outside of this lock |
| if (registerSpi) |
| { |
| registerSpiWithOSGi(); |
| } |
| |
| return lmf; |
| } |
| |
| @Override |
| public void modifiedService(ServiceReference reference, Object service) |
| { |
| LoginModuleFactory lmf = providerMap.get(reference); |
| if (lmf instanceof OsgiLoginModuleProvider) { |
| // refresh to update configs |
| ((OsgiLoginModuleProvider) lmf).configure(); |
| } |
| synchronized (lock) |
| { |
| recreateConfigs(); |
| } |
| } |
| |
| @Override |
| public void removedService(ServiceReference reference, Object service) |
| { |
| synchronized (lock) |
| { |
| deregisterFactory(reference); |
| recreateConfigs(); |
| } |
| context.ungetService(reference); |
| } |
| |
| private void deregisterFactory(ServiceReference ref) |
| { |
| LoginModuleProvider lmp = providerMap.remove(ref); |
| if (lmp != null) |
| { |
| log.log(LogService.LOG_INFO, "Deregistering LoginModuleFactory " + lmp); |
| } |
| } |
| |
| private void registerFactory(ServiceReference ref, LoginModuleFactory lmf) |
| { |
| LoginModuleProvider lmfExt; |
| if (lmf instanceof LoginModuleProvider) |
| { |
| lmfExt = (LoginModuleProvider) lmf; |
| } |
| else |
| { |
| lmfExt = new OsgiLoginModuleProvider(ref, lmf); |
| } |
| log.log(LogService.LOG_INFO, "Registering LoginModuleFactory " + lmf); |
| providerMap.put(ref, lmfExt); |
| } |
| |
| private static Configuration getGlobalConfiguration() |
| { |
| try |
| { |
| return Configuration.getConfiguration(); |
| } |
| catch (Exception e) |
| { |
| // means no JAAS configuration file OR no permission to read it |
| } |
| return null; |
| } |
| |
| private class OSGiProvider extends Provider |
| { |
| public static final String TYPE_CONFIGURATION = "Configuration"; |
| |
| OSGiProvider(String providerName) |
| { |
| super(providerName, 1.0, "OSGi based provider for Jaas configuration"); |
| } |
| |
| @Override |
| public synchronized Service getService(String type, String algorithm) |
| { |
| if (TYPE_CONFIGURATION.equals(type) |
| && JAAS_CONFIG_ALGO_NAME.equals(algorithm)) |
| { |
| return new ConfigurationService(this); |
| } |
| return super.getService(type, algorithm); |
| } |
| } |
| |
| private class ConfigurationService extends Provider.Service |
| { |
| |
| public ConfigurationService(Provider provider) |
| { |
| super(provider, OSGiProvider.TYPE_CONFIGURATION, //the type of this service |
| JAAS_CONFIG_ALGO_NAME, //the algorithm name |
| ConfigSpiOsgi.class.getName(), //the name of the class implementing this service |
| Collections.<String> emptyList(), //List of aliases or null if algorithm has no aliases |
| Collections.<String, String> emptyMap()); //Map of attributes or null if this implementation |
| } |
| |
| @Override |
| public Object newInstance(Object constructorParameter) |
| throws NoSuchAlgorithmException |
| { |
| //constructorParameter is the one which is passed as Configuration.Parameters params |
| //for now we do not make use of that |
| return ConfigSpiOsgi.this; |
| } |
| } |
| |
| //---------------------------- Global Configuration Handling |
| |
| private class OsgiConfiguration extends Configuration |
| { |
| |
| @Override |
| public AppConfigurationEntry[] getAppConfigurationEntry(String name) |
| { |
| return ConfigSpiOsgi.this.engineGetAppConfigurationEntry(name); |
| } |
| } |
| |
| private class DelegatingConfiguration extends Configuration |
| { |
| private final Configuration primary; |
| private final Configuration secondary; |
| |
| private DelegatingConfiguration(Configuration primary, Configuration secondary) |
| { |
| this.primary = primary; |
| this.secondary = secondary; |
| } |
| |
| @Override |
| public AppConfigurationEntry[] getAppConfigurationEntry(String name) |
| { |
| // check if jaas-loginModule or fallback is configured |
| AppConfigurationEntry[] result = null; |
| try |
| { |
| result = primary.getAppConfigurationEntry(name); |
| } |
| catch (Exception e) |
| { |
| // means no JAAS configuration file OR no permission to read it |
| } |
| |
| if (result == null) |
| { |
| try |
| { |
| result = secondary.getAppConfigurationEntry(name); |
| } |
| catch (Exception e) |
| { |
| // WLP 9.2.0 throws IllegalArgumentException for unknown appName |
| } |
| } |
| |
| return result; |
| } |
| } |
| |
| //-------------------------------------OSGi Config Management |
| |
| static final class Realm |
| { |
| private final String realmName; |
| private AppConfigurationEntry[] configArray; |
| private List<AppConfigurationHolder> configs = new ArrayList<AppConfigurationHolder>(); |
| |
| Realm(String realmName) |
| { |
| this.realmName = realmName; |
| } |
| |
| public void add(AppConfigurationHolder config) |
| { |
| configs.add(config); |
| } |
| |
| public void afterPropertiesSet() |
| { |
| Collections.sort(configs); |
| configArray = new AppConfigurationEntry[configs.size()]; |
| for (int i = 0; i < configs.size(); i++) |
| { |
| configArray[i] = configs.get(i).getEntry(); |
| } |
| configs = Collections.unmodifiableList(configs); |
| } |
| |
| public String getRealmName() |
| { |
| return realmName; |
| } |
| |
| public List<AppConfigurationHolder> getConfigs() |
| { |
| return configs; |
| } |
| |
| public AppConfigurationEntry[] engineGetAppConfigurationEntry() |
| { |
| return Arrays.copyOf(configArray, configArray.length); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "Realm{" + "realmName='" + realmName + '\'' + '}'; |
| } |
| } |
| |
| static final class AppConfigurationHolder implements Comparable<AppConfigurationHolder> |
| { |
| private static final String LOGIN_MODULE_CLASS = ProxyLoginModule.class.getName(); |
| private final LoginModuleProvider provider; |
| private final int ranking; |
| private final AppConfigurationEntry entry; |
| |
| public AppConfigurationHolder(LoginModuleProvider provider) |
| { |
| this.provider = provider; |
| this.ranking = provider.ranking(); |
| |
| Map<String, Object> options = new HashMap<String, Object>(provider.options()); |
| options.put(ProxyLoginModule.PROP_LOGIN_MODULE_FACTORY, provider); |
| this.entry = new AppConfigurationEntry(LOGIN_MODULE_CLASS, |
| provider.getControlFlag(), Collections.unmodifiableMap(options)); |
| } |
| |
| public int compareTo(AppConfigurationHolder that) |
| { |
| if (this.ranking == that.ranking) |
| { |
| return 0; |
| } |
| return this.ranking > that.ranking ? -1 : 1; |
| } |
| |
| public AppConfigurationEntry getEntry() |
| { |
| return entry; |
| } |
| |
| public LoginModuleProvider getProvider() |
| { |
| return provider; |
| } |
| } |
| } |