/**
* Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright ownership. Apereo
* 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 the
* following location:
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>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.apereo.portal.security;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* This class provides a static "factory" method that returns a security context retrieved based on
* the information provided in security.properties, including all relevant subcontexts. A typical
* sequence would be:
*
* <pre>
* SecurityContext sec = InitialSecurityContextFactory.getInitialContext("root");
* Principal princ = sec.getPrincipalInstance();
* OpaqueCredentials pwd = sec.getOpaqueCredentialsInstance();
* princ.setUID("user");
* pwd.setCredentials("password");
* sec.authenticate();
* if (sec.isAuthenticated())
* System.out.println("Yup");
* else
* System.out.println("Nope");
* </pre>
*
*/
public class InitialSecurityContextFactory {
private static final Log log = LogFactory.getLog(InitialSecurityContextFactory.class);
private static final String CONTEXT_PROPERTY_PREFIX = "securityContextProperty";
/**
* Used to store the configuration for each initial context, this allows a the ISecurityContext
* chain to be created more quickly since the properties file doesn't need to be parsed at each
* getInitialContext call.
*/
private static final Map contextConfigCache = new Hashtable();
public static ISecurityContext getInitialContext(final String rootContext)
throws PortalSecurityException {
BaseContextConfiguration contextConfigBase;
/*
* Synchronize on the contextConfigCache Map, this ensures two threads don't
* end up creating the same BaseContextConfiguration.
*/
synchronized (contextConfigCache) {
contextConfigBase = (BaseContextConfiguration) contextConfigCache.get(rootContext);
//The desired base context configuraton doesn't exist
if (contextConfigBase == null) {
//Initial contexts must have names that are not compound
if (rootContext.indexOf('.') != -1) {
PortalSecurityException ep =
new PortalSecurityException("Initial Context can't be compound");
log.error("Initial context cannot be compound", ep);
throw (ep);
}
contextConfigBase = new BaseContextConfiguration();
contextConfigCache.put(rootContext, contextConfigBase);
}
}
/*
* Changing the synchronized object will minimize blocking in the case
* of different root contexts. The config initialization code is thread
* safe as long as each thread is initializing a different config.
*/
/*
* Synchronize on the contextConfig, this ensures two threads don't
* try to intialize the config in parallel. Only one will be allowed
* into the synchronized block at a time, if the config has not been
* initialized it will initialize it and set the flag to true then
* exit the block. The waiting thread(s) will have a reference to a
* now initialized config and skip the initalization code.
*/
synchronized (contextConfigBase) {
if (!contextConfigBase.initialized) {
//Try to load the properties
final Properties securityProperties = new Properties();
InputStream securityPropertiesStream = null;
try {
securityPropertiesStream =
InitialSecurityContextFactory.class.getResourceAsStream(
"/properties/security.properties");
securityProperties.load(securityPropertiesStream);
} catch (IOException e) {
PortalSecurityException ep = new PortalSecurityException(e);
log.error("Exception loading security properties", ep);
throw (ep);
} finally {
try {
if (securityPropertiesStream != null) {
securityPropertiesStream.close();
}
} catch (IOException ioe) {
log.error("getInitialContext() unable to close InputStream", ioe);
}
}
//Load the context configurations
contextConfigBase.rootConfig =
loadContextConfigurationChain(rootContext, securityProperties);
contextConfigBase.initialized = true;
}
}
//Should have a valid contextConfig here
try {
//Create the context tree
ISecurityContext ctx = createSecurityContextChain(contextConfigBase.rootConfig);
return ctx;
} catch (NullPointerException npe) {
String errorMsg = "Error while creating ISecurityContext chain.";
PortalSecurityException ep = new PortalSecurityException(errorMsg, npe);
log.error(ep, ep);
throw ep;
}
}
/**
* Recursively parses the tree of {@link ContextConfiguration} objects to create a tree (chain)
* of {@link ISecurityContext}s. The root context is returned by the method after all of it's
* sub-contexts have been created and configured.
*
* @param contextConfig The {@link ContextConfiguration} to use as the root
* @return A configured {@link ISecurityContext}
* @throws PortalSecurityException If an excetion is thrown by the {@link
* ISecurityContext#addSubContext(String, ISecurityContext)} method.
*/
private static ISecurityContext createSecurityContextChain(
final ContextConfiguration contextConfig) throws PortalSecurityException {
final ISecurityContext securityContext = contextConfig.contextFactory.getSecurityContext();
//If it is a configurable SecurityContext pass in the properties
if (securityContext instanceof IConfigurableSecurityContext) {
((IConfigurableSecurityContext) securityContext)
.setProperties((Properties) contextConfig.contextProperties.clone());
}
//Create all the sub contexts
for (int index = 0; index < contextConfig.subConfigs.length; index++) {
final ISecurityContext subSecurityContext =
createSecurityContextChain(contextConfig.subConfigs[index]);
securityContext.addSubContext(
contextConfig.subConfigs[index].contextName, subSecurityContext);
}
return securityContext;
}
/**
* This method parses the {@link Properties} file to find the configuration for the specified
* context. The factory is loaded and the configuration is named then the {@link Properties} are
* parsed to find all context configuration properties and sub-contexts for this context. For
* each sub-context this method is called recursively.
*
* @param fullContextName The fully qualified name of the context to configure.
* @param securtiyProperties The {@link Properties} to use for configuration.
* @return A fully configured {@link ContextConfiguration} object.
* @throws PortalSecurityException If no context with the specified named exists or the factory
* cannot be created.
*/
private static ContextConfiguration loadContextConfigurationChain(
final String fullContextName, final Properties securtiyProperties)
throws PortalSecurityException {
//Load the context factory name
final String factoryName = securtiyProperties.getProperty(fullContextName);
if (factoryName == null) {
final PortalSecurityException ep =
new PortalSecurityException("No such security context " + fullContextName);
log.error(ep.getMessage(), ep);
throw (ep);
}
//The contextConfig this method will return
final ContextConfiguration contextConfig = new ContextConfiguration();
final int lastDotIndex = fullContextName.lastIndexOf(".");
String localContextName = fullContextName;
if (lastDotIndex >= 0) {
try {
localContextName = fullContextName.substring(lastDotIndex + 1);
} catch (IndexOutOfBoundsException ioobe) {
final PortalSecurityException pse =
new PortalSecurityException(
"Invalid context name " + fullContextName, ioobe);
log.error(pse.getMessage(), pse);
throw pse;
}
}
//Create the context factory
try {
final ISecurityContextFactory factory =
(ISecurityContextFactory) Class.forName(factoryName).newInstance();
contextConfig.contextFactory = factory;
contextConfig.contextName = localContextName;
} catch (Exception e) {
final PortalSecurityException ep =
new PortalSecurityException("Failed to instantiate " + factoryName);
log.error("Failed to instantiate " + factoryName, e);
throw (ep);
}
//Just move this string concatination out of the loop to save cycles
final String contextConfigPropertyPrefix = CONTEXT_PROPERTY_PREFIX + "." + fullContextName;
//Read sub context names & properties for this context
final Collection subContexts = new Vector();
for (final Enumeration ctxnames = securtiyProperties.propertyNames();
ctxnames.hasMoreElements();
) {
final String securityPropName = (String) ctxnames.nextElement();
if (securityPropName.startsWith(fullContextName)
&& securityPropName.length() > fullContextName.length()
&& securityPropName.indexOf(".", fullContextName.length() + 1) < 0) {
//call getContextConfiguration(name) on each
final ContextConfiguration subContextConfig =
loadContextConfigurationChain(securityPropName, securtiyProperties);
//add context to list for this context
subContexts.add(subContextConfig);
}
//Context configuration properties as securityContextProperty. entries
// Format is:
// securityContextProperty.<SecurityContextName>.<PropertyName>
// <PropertyName> cannot contain .
else if (securityPropName.startsWith(contextConfigPropertyPrefix)
&& securityPropName.length() > contextConfigPropertyPrefix.length()
&& securityPropName.indexOf(".", contextConfigPropertyPrefix.length() + 1)
< 0) {
final String propValue = securtiyProperties.getProperty(securityPropName);
final String propName =
securityPropName.substring(contextConfigPropertyPrefix.length() + 1);
contextConfig.contextProperties.setProperty(propName, propValue);
}
}
//Set the sub contexts into this context
contextConfig.subConfigs =
(ContextConfiguration[])
subContexts.toArray(new ContextConfiguration[subContexts.size()]);
return contextConfig;
}
}
class BaseContextConfiguration {
ContextConfiguration rootConfig = null;
boolean initialized = false;
}
class ContextConfiguration {
ISecurityContextFactory contextFactory = null;
String contextName = null;
final Properties contextProperties = new Properties();
ContextConfiguration[] subConfigs = null;
}