/* * Weblounge: Web Content Management System * Copyright (c) 2011 The Weblounge Team * http://weblounge.o2it.ch * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package ch.entwine.weblounge.kernel.security; import static ch.entwine.weblounge.dispatcher.SharedHttpContext.CONTEXT_ID; import static ch.entwine.weblounge.dispatcher.SharedHttpContext.PATTERN; import static ch.entwine.weblounge.dispatcher.SharedHttpContext.SERVICE_RANKING; import static ch.entwine.weblounge.dispatcher.SharedHttpContext.WEBLOUNGE_CONTEXT_ID; import ch.entwine.weblounge.common.impl.util.config.ConfigurationUtils; import ch.entwine.weblounge.common.security.Security; import ch.entwine.weblounge.kernel.site.SiteManager; import org.apache.felix.webconsole.WebConsoleSecurityProvider; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.osgi.context.ConfigurableOsgiBundleApplicationContext; import org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext; import java.io.IOException; import java.net.URL; import java.util.Dictionary; import java.util.Hashtable; import javax.servlet.Filter; /** * Read the <code>security.xml</code> defining the general security constraints * from the bundle resources. * <p> * After the configuration is read, the service registers a {@link Filter} which * enforces the security policy at runtime. */ public class SpringSecurityConfigurationService implements ManagedService { /** Logging facility */ private static final Logger logger = LoggerFactory.getLogger(SpringSecurityConfigurationService.class); /** Service pid, used to look up the service configuration */ public static final String SERVICE_PID = "ch.entwine.weblounge.security"; /** Name of the configuration file */ public static final String SECURITY_CONFIG_FILE = "/security/security.xml"; /** Configuration key for the enabled/disabled configuration */ public static final String OPT_ENABLED = "security.enabled"; /** The related spring security service */ protected SpringSecurityServiceImpl securityService = null; /** The current bundle context */ protected BundleContext bundleCtx = null; /** The spring security filter */ protected SecurityFilter securityFilter = null; /** The web console security */ protected WebConsoleSecurityProvider webConsoleProvider = null; /** Reference to the security marker */ protected ServiceRegistration securityMarker = null; /** Is security enabled */ protected boolean securityEnabled = true; /** The security filter registration */ protected ServiceRegistration securityFilterRegistration = null; /** The registration for the web console security provider */ protected ServiceRegistration webConsoleSecurityRegistration = null; /** The sites that are online */ protected SiteManager sites = null; /** * Callback from the OSGi environment on service activation. * * @param ctx * the component context * @throws IOException * if reading the service configuration fails * @throws ConfigurationException * if the service configuration is malformed */ void activate(ComponentContext ctx) throws IOException, ConfigurationException { bundleCtx = ctx.getBundleContext(); // Try to get hold of the service configuration ServiceReference configAdminRef = bundleCtx.getServiceReference(ConfigurationAdmin.class.getName()); if (configAdminRef != null) { ConfigurationAdmin configAdmin = (ConfigurationAdmin) bundleCtx.getService(configAdminRef); Dictionary<?, ?> config = configAdmin.getConfiguration(SERVICE_PID).getProperties(); if (config != null) { updated(config); } else { logger.debug("No customized security configuration found"); } } else { logger.debug("No configuration admin service found while looking for security configuration"); } // Create the spring security context URL securityConfig = bundleCtx.getBundle().getResource(SECURITY_CONFIG_FILE); ConfigurableOsgiBundleApplicationContext springContext = null; springContext = new OsgiBundleXmlApplicationContext(new String[] { securityConfig.toExternalForm() }); springContext.setBundleContext(bundleCtx); springContext.refresh(); // Get the security filter chain from the spring context Filter defaultSecurityFilter = (Filter) springContext.getBean("springSecurityFilterChain"); securityFilter = new SecurityFilter(securityService, sites, defaultSecurityFilter); // Create the web console security provider webConsoleProvider = new WebloungeWebConsoleSecurityProvider(securityService); // Activate the security filters if (securityEnabled) { startSecurity(); } else { logger.info("Security is turned off by configuration"); } // Tell the security service abut the current policy securityService.setEnabled(securityEnabled); // Register the security marker publishSecurityMarker(); } /** * Callback from OSGi environment on service inactivation. * * @param ctx * the component context */ void deactivate(ComponentContext ctx) { logger.info("Tearing down spring security"); // Unregister the security filters if (securityFilterRegistration != null) { stopSecurity(); } // Remove the security marker if (securityMarker != null) { try { securityMarker.unregister(); } catch (IllegalStateException e) { // Never mind, the service has been unregistered already } catch (Throwable t) { logger.error("Unregistering security context failed: {}", t.getMessage()); } } } /** * {@inheritDoc} * * @see org.osgi.service.cm.ManagedService#updated(java.util.Dictionary) */ @SuppressWarnings("rawtypes") public void updated(Dictionary properties) throws ConfigurationException { if (properties == null) { logger.debug("No customized security configuration found"); return; } String enabledProperty = (String) properties.get(OPT_ENABLED); boolean isEnabled = ConfigurationUtils.isTrue(enabledProperty, true); // Enable/disable security if (isEnabled != this.securityEnabled) { if (isEnabled) { startSecurity(); } else { stopSecurity(); } } // Tell the security service abut the current policy securityService.setEnabled(isEnabled); // Store the security enabled setting this.securityEnabled = isEnabled; } /** * Activates security by registering both a security filter and a web console * security provider. */ private void startSecurity() { logger.info("Enabling spring security"); Dictionary<String, String> props = new Hashtable<String, String>(); props.put(PATTERN, ".*"); props.put(CONTEXT_ID, WEBLOUNGE_CONTEXT_ID); props.put(SERVICE_RANKING, "0"); props.put("security", "weblounge"); try { securityFilterRegistration = bundleCtx.registerService(Filter.class.getName(), securityFilter, props); logger.debug("Spring security context registered"); } catch (Throwable t) { logger.error("Error registering security context: {}", t.getMessage()); } logger.info("Securing the Felix management console"); try { webConsoleSecurityRegistration = bundleCtx.registerService(WebConsoleSecurityProvider.class.getName(), webConsoleProvider, null); logger.debug("Web console security provider registered"); } catch (Throwable t) { logger.error("Error registering web console security provider: {}", t.getMessage()); } } /** * Registers the security marker in the OSGi registry. */ private void publishSecurityMarker() { Dictionary<String, String> securityProperties = new Hashtable<String, String>(); bundleCtx.registerService(Security.class.getName(), new Security() { }, securityProperties); } /** * Deactivates security by unregistering both the security filter and the web * console security provider. */ private void stopSecurity() { logger.info("Disabling spring security"); if (securityFilterRegistration != null) { try { securityFilterRegistration.unregister(); logger.debug("Spring security context unregistered"); } catch (IllegalStateException e) { // Never mind, the service has been unregistered already } catch (Throwable t) { logger.error("Unregistering security context", t.getMessage()); } } logger.info("Disabling web console security provider"); if (webConsoleSecurityRegistration != null) { try { webConsoleSecurityRegistration.unregister(); logger.debug("Web console security provider unregistered"); } catch (IllegalStateException e) { logger.debug("Web console security provider was already unregistered"); } catch (Throwable t) { logger.error("Unregistering web console security provider", t.getMessage()); } } } /** * Callback from OSGi to set the spring security service. * * @param securityService * the security service */ void setSecurityService(SpringSecurityServiceImpl securityService) { this.securityService = securityService; } /** * Callback for OSGi to set the site manager. * * @param siteManager * the site manager */ void setSiteManager(SiteManager siteManager) { this.sites = siteManager; } /** * Callback for OSGi to remove the site manager. * * @param siteManager * the site manager */ void removeSiteManager(SiteManager siteManager) { this.sites = null; } }