/* * 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.cocoon.auth.impl; import java.util.List; import java.util.Map; import org.apache.avalon.framework.activity.Disposable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.avalon.framework.context.Context; import org.apache.avalon.framework.context.ContextException; import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.framework.service.ServiceException; import org.apache.avalon.framework.service.ServiceManager; import org.apache.avalon.framework.service.Serviceable; import org.apache.cocoon.auth.AbstractSecurityHandler; import org.apache.cocoon.auth.ApplicationManager; import org.apache.cocoon.auth.StandardUser; import org.apache.cocoon.auth.User; import org.apache.cocoon.components.source.SourceUtil; import org.apache.cocoon.util.NetUtils; import org.apache.excalibur.source.Source; import org.apache.excalibur.source.SourceException; import org.apache.excalibur.source.SourceParameters; import org.apache.excalibur.source.SourceResolver; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Verify if a user can be authenticated. * * @version $Id$ */ public class PipelineSecurityHandler extends AbstractSecurityHandler implements Serviceable, Disposable { /** The service manager. */ protected ServiceManager manager; /** The source resolver. */ protected SourceResolver resolver; /** Configuration. */ protected Configuration config; /** Context. */ protected Context context; /** * @see org.apache.avalon.framework.context.Contextualizable#contextualize(org.apache.avalon.framework.context.Context) */ public void contextualize(final Context aContext) throws ContextException { super.contextualize(aContext); this.context = aContext; } /** * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration) */ public void configure(final Configuration conf) throws ConfigurationException { super.configure(conf); this.config = conf; } /** * Check if this is a valid document. * A valid document has "authentication" as the root node and * at least one child element "ID". * @param doc The document read by the pipeline. * @return The value of the ID element or null if the document is not valid */ protected String isValidAuthenticationDocument(final Document doc) { String validId = null; final Element child = doc.getDocumentElement(); if ( child.getNodeName().equals("authentication") ) { // now authentication must have one child ID if (child.hasChildNodes()) { final NodeList children = child.getChildNodes(); boolean found = false; int i = 0; Node current = null; while (!found && i < children.getLength()) { current = children.item(i); if (current.getNodeType() == Node.ELEMENT_NODE && current.getNodeName().equals("ID")) { found = true; } else { i++; } } // now the last check: ID must have a TEXT child if (found) { current.normalize(); // join text nodes if (current.hasChildNodes() && current.getChildNodes().getLength() == 1 && current.getFirstChild().getNodeType() == Node.TEXT_NODE) { final String value = current.getFirstChild().getNodeValue().trim(); if (value.length() > 0) { validId = value; } } } } } return validId; } /** * @see org.apache.cocoon.auth.SecurityHandler#login(java.util.Map) */ public User login(final Map loginContext) throws Exception { String authenticationResourceName = this.config.getChild("authentication-resource").getValue(); // append parameters Parameters p = (Parameters) loginContext.get(ApplicationManager.LOGIN_CONTEXT_PARAMETERS_KEY); if ( p != null ) { final StringBuffer b = new StringBuffer(authenticationResourceName); boolean hasParams = (authenticationResourceName.indexOf('?') != -1); final String[] names = p.getNames(); for(int i=0;i<names.length;i++) { final String key = names[i]; final String value = p.getParameter(key); if ( hasParams ) { b.append('&'); } else { b.append('?'); hasParams = true; } b.append(key).append('=').append(NetUtils.encode(value, "utf-8")); } authenticationResourceName = b.toString(); } User user = null; Document doc = null; // invoke the source Source source = null; try { source = SourceUtil.getSource(authenticationResourceName, null, null, this.resolver); doc = SourceUtil.toDOM(source); } catch (SourceException se) { throw SourceUtil.handle(se); } finally { this.resolver.release(source); } // test if authentication was successful String validId = null; if (doc != null) { validId = this.isValidAuthenticationDocument( doc ); if ( validId != null ) { user = new PipelineSHUser( doc, validId ); } } // TODO - What do we do, if authentication fails? return user; } /** * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager) */ public void service(final ServiceManager aManager) throws ServiceException { this.manager = aManager; this.resolver = (SourceResolver) this.manager.lookup(SourceResolver.ROLE); } /** * @see org.apache.avalon.framework.activity.Disposable#dispose() */ public void dispose() { if ( this.manager != null ){ this.manager.release( this.resolver ); this.manager = null; this.resolver = null; } } /** * @see org.apache.cocoon.auth.SecurityHandler#logout(java.util.Map, org.apache.cocoon.auth.User) */ public void logout(final Map logoutContext, final User user) { final String logoutResourceName = this.config.getChild("logout-resource").getValue(null); if (logoutResourceName != null) { // invoke the source Source source = null; try { // This allows arbitrary business logic to be called. Whatever is returned // is ignored. source = SourceUtil.getSource(logoutResourceName, null, null, this.resolver); SourceUtil.toDOM(source); } catch (Exception ignore) { this.getLogger().warn("Exception during logout of user: " + user.getId(), ignore); } finally { this.resolver.release(source); } } } /** * The internal user class. */ public static class PipelineSHUser extends StandardUser { /** The document delivered by the pipeline. */ protected final Document userInfo; /** The cached list of roles for this user. */ protected List roles; /** * Create a new user object. * @param info The pipeline document. * @param id The unique id of the user. */ public PipelineSHUser(final Document info, final String id) { super(id); this.userInfo = info; this.calculateContextInfo(); } /** * Return the pipeline document. * @return The document. */ public Document getUserInfo() { return this.userInfo; } /** * Internal method that calculates the context information. All * key-value pairs contained in the document are added as * attributes to the user object. */ protected void calculateContextInfo() { SourceParameters parameters = new SourceParameters(); // add all elements from inside the handler data this.addParametersFromAuthenticationXML("data", parameters); // add all top level elements from authentication this.addParametersFromAuthenticationXML(null, parameters); Parameters pars = parameters.getFirstParameters(); String[] names = pars.getNames(); if (names != null) { String key; String value; for(int i=0;i<names.length;i++) { key = names[i]; value = pars.getParameter(key, null); if (value != null) { this.setAttribute(key, value); } } } } /** * Convert the authentication XML of a handler to parameters. * The XML is flat and consists of elements which all have exactly one text node: * <parone>value_one<parone> * <partwo>value_two<partwo> * A parameter can occur more than once with different values. * @param childElementName The name of the element to search in. * @param parameters The found key-value pair is added to this parameters object. */ private void addParametersFromAuthenticationXML(final String childElementName, final SourceParameters parameters) { Element root = this.userInfo.getDocumentElement(); if ( childElementName != null ) { NodeList l = root.getElementsByTagName(childElementName); if ( l.getLength() > 0 ) { root = (Element)l.item(0); } else { root = null; } } if (root != null) { NodeList childs = root.getChildNodes(); if (childs != null) { Node current; for(int i = 0; i < childs.getLength(); i++) { current = childs.item(i); // only element nodes if (current.getNodeType() == Node.ELEMENT_NODE) { current.normalize(); NodeList valueChilds = current.getChildNodes(); String key; StringBuffer valueBuffer; String value; key = current.getNodeName(); valueBuffer = new StringBuffer(); for(int m = 0; m < valueChilds.getLength(); m++) { current = valueChilds.item(m); // attention: current is reused here! if (current.getNodeType() == Node.TEXT_NODE) { // only text nodes if (valueBuffer.length() > 0) { valueBuffer.append(' '); } valueBuffer.append(current.getNodeValue()); } } value = valueBuffer.toString().trim(); if (key != null && value != null && value.length() > 0) { parameters.setParameter(key, value); } } } } } } } }