001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.auth.impl;
018:
019: import java.util.List;
020: import java.util.Map;
021:
022: import org.apache.avalon.framework.activity.Disposable;
023: import org.apache.avalon.framework.configuration.Configuration;
024: import org.apache.avalon.framework.configuration.ConfigurationException;
025: import org.apache.avalon.framework.context.Context;
026: import org.apache.avalon.framework.context.ContextException;
027: import org.apache.avalon.framework.parameters.Parameters;
028: import org.apache.avalon.framework.service.ServiceException;
029: import org.apache.avalon.framework.service.ServiceManager;
030: import org.apache.avalon.framework.service.Serviceable;
031: import org.apache.cocoon.components.source.SourceUtil;
032: import org.apache.cocoon.util.NetUtils;
033: import org.apache.excalibur.source.Source;
034: import org.apache.excalibur.source.SourceException;
035: import org.apache.excalibur.source.SourceParameters;
036: import org.apache.excalibur.source.SourceResolver;
037: import org.apache.cocoon.auth.AbstractSecurityHandler;
038: import org.apache.cocoon.auth.ApplicationManager;
039: import org.apache.cocoon.auth.StandardUser;
040: import org.apache.cocoon.auth.User;
041: import org.w3c.dom.Document;
042: import org.w3c.dom.Element;
043: import org.w3c.dom.Node;
044: import org.w3c.dom.NodeList;
045:
046: /**
047: * Verify if a user can be authenticated.
048: *
049: * @version $Id: PipelineSecurityHandler.java 433543 2006-08-22 06:22:54Z crossley $
050: */
051: public class PipelineSecurityHandler extends AbstractSecurityHandler
052: implements Serviceable, Disposable {
053:
054: /** The service manager. */
055: protected ServiceManager manager;
056:
057: /** The source resolver. */
058: protected SourceResolver resolver;
059:
060: /** Configuration. */
061: protected Configuration config;
062:
063: /** Context. */
064: protected Context context;
065:
066: /**
067: * @see org.apache.avalon.framework.context.Contextualizable#contextualize(org.apache.avalon.framework.context.Context)
068: */
069: public void contextualize(final Context aContext)
070: throws ContextException {
071: super .contextualize(aContext);
072: this .context = aContext;
073: }
074:
075: /**
076: * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
077: */
078: public void configure(final Configuration conf)
079: throws ConfigurationException {
080: super .configure(conf);
081: this .config = conf;
082: }
083:
084: /**
085: * Check if this is a valid document.
086: * A valid document has "authentication" as the root node and
087: * at least one child element "ID".
088: * @param doc The document read by the pipeline.
089: * @return The value of the ID element or null if the document is not valid
090: */
091: protected String isValidAuthenticationDocument(final Document doc) {
092: String validId = null;
093:
094: final Element child = doc.getDocumentElement();
095:
096: if (child.getNodeName().equals("authentication")) {
097:
098: // now authentication must have one child ID
099: if (child.hasChildNodes()) {
100: final NodeList children = child.getChildNodes();
101: boolean found = false;
102: int i = 0;
103: Node current = null;
104:
105: while (!found && i < children.getLength()) {
106: current = children.item(i);
107: if (current.getNodeType() == Node.ELEMENT_NODE
108: && current.getNodeName().equals("ID")) {
109: found = true;
110: } else {
111: i++;
112: }
113: }
114:
115: // now the last check: ID must have a TEXT child
116: if (found) {
117: current.normalize(); // join text nodes
118: if (current.hasChildNodes()
119: && current.getChildNodes().getLength() == 1
120: && current.getFirstChild().getNodeType() == Node.TEXT_NODE) {
121:
122: final String value = current.getFirstChild()
123: .getNodeValue().trim();
124: if (value.length() > 0) {
125: validId = value;
126: }
127: }
128: }
129: }
130:
131: }
132: return validId;
133: }
134:
135: /**
136: * @see org.apache.cocoon.auth.SecurityHandler#login(java.util.Map)
137: */
138: public User login(final Map loginContext) throws Exception {
139: String authenticationResourceName = this .config.getChild(
140: "authentication-resource").getValue();
141:
142: // append parameters
143: Parameters p = (Parameters) loginContext
144: .get(ApplicationManager.LOGIN_CONTEXT_PARAMETERS_KEY);
145: if (p != null) {
146: final StringBuffer b = new StringBuffer(
147: authenticationResourceName);
148: boolean hasParams = (authenticationResourceName
149: .indexOf('?') != -1);
150: final String[] names = p.getNames();
151: for (int i = 0; i < names.length; i++) {
152: final String key = names[i];
153: final String value = p.getParameter(key);
154: if (hasParams) {
155: b.append('&');
156: } else {
157: b.append('?');
158: hasParams = true;
159: }
160: b.append(key).append('=').append(
161: NetUtils.encode(value, "utf-8"));
162: }
163: authenticationResourceName = b.toString();
164: }
165: User user = null;
166: Document doc = null;
167:
168: // invoke the source
169: Source source = null;
170: try {
171: source = SourceUtil.getSource(authenticationResourceName,
172: null, null, this .resolver);
173: doc = SourceUtil.toDOM(source);
174: } catch (SourceException se) {
175: throw SourceUtil.handle(se);
176: } finally {
177: this .resolver.release(source);
178: }
179:
180: // test if authentication was successful
181: String validId = null;
182: if (doc != null) {
183: validId = this .isValidAuthenticationDocument(doc);
184:
185: if (validId != null) {
186: user = new PipelineSHUser(doc, validId);
187: }
188: }
189: // TODO - What do we do, if authentication fails?
190:
191: return user;
192: }
193:
194: /**
195: * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
196: */
197: public void service(final ServiceManager aManager)
198: throws ServiceException {
199: this .manager = aManager;
200: this .resolver = (SourceResolver) this .manager
201: .lookup(SourceResolver.ROLE);
202: }
203:
204: /**
205: * @see org.apache.avalon.framework.activity.Disposable#dispose()
206: */
207: public void dispose() {
208: if (this .manager != null) {
209: this .manager.release(this .resolver);
210: this .manager = null;
211: this .resolver = null;
212: }
213: }
214:
215: /**
216: * @see org.apache.cocoon.auth.SecurityHandler#logout(java.util.Map, org.apache.cocoon.auth.User)
217: */
218: public void logout(final Map logoutContext, final User user) {
219: final String logoutResourceName = this .config.getChild(
220: "logout-resource").getValue(null);
221: if (logoutResourceName != null) {
222: // invoke the source
223: Source source = null;
224: try {
225: // This allows arbitrary business logic to be called. Whatever is returned
226: // is ignored.
227: source = SourceUtil.getSource(logoutResourceName, null,
228: null, this .resolver);
229: SourceUtil.toDOM(source);
230: } catch (Exception ignore) {
231: this .getLogger().warn(
232: "Exception during logout of user: "
233: + user.getId(), ignore);
234: } finally {
235: this .resolver.release(source);
236: }
237: }
238: }
239:
240: /**
241: * The internal user class.
242: */
243: public static class PipelineSHUser extends StandardUser {
244:
245: /** The document delivered by the pipeline. */
246: protected final Document userInfo;
247: /** The cached list of roles for this user. */
248: protected List roles;
249:
250: /**
251: * Create a new user object.
252: * @param info The pipeline document.
253: * @param id The unique id of the user.
254: */
255: public PipelineSHUser(final Document info, final String id) {
256: super (id);
257: this .userInfo = info;
258: this .calculateContextInfo();
259: }
260:
261: /**
262: * Return the pipeline document.
263: * @return The document.
264: */
265: public Document getUserInfo() {
266: return this .userInfo;
267: }
268:
269: /**
270: * Internal method that calculates the context information. All
271: * key-value pairs contained in the document are added as
272: * attributes to the user object.
273: */
274: protected void calculateContextInfo() {
275: SourceParameters parameters = new SourceParameters();
276:
277: // add all elements from inside the handler data
278: this
279: .addParametersFromAuthenticationXML("/data",
280: parameters);
281:
282: // add all top level elements from authentication
283: this .addParametersFromAuthenticationXML(null, parameters);
284:
285: Parameters pars = parameters.getFirstParameters();
286: String[] names = pars.getNames();
287: if (names != null) {
288: String key;
289: String value;
290: for (int i = 0; i < names.length; i++) {
291: key = names[i];
292: value = pars.getParameter(key, null);
293: if (value != null) {
294: this .setAttribute(key, value);
295: }
296: }
297: }
298: }
299:
300: /**
301: * Convert the authentication XML of a handler to parameters.
302: * The XML is flat and consists of elements which all have exactly one text node:
303: * <parone>value_one<parone>
304: * <partwo>value_two<partwo>
305: * A parameter can occur more than once with different values.
306: * @param childElementName The name of the element to search in.
307: * @param parameters The found key-value pair is added to this parameters object.
308: */
309: private void addParametersFromAuthenticationXML(
310: final String childElementName,
311: final SourceParameters parameters) {
312: Element root = this .userInfo.getDocumentElement();
313: if (childElementName != null) {
314: NodeList l = root
315: .getElementsByTagName(childElementName);
316: if (l.getLength() > 0) {
317: root = (Element) l.item(0);
318: } else {
319: root = null;
320: }
321: }
322: if (root != null) {
323: NodeList childs = root.getChildNodes();
324: if (childs != null) {
325: Node current;
326: for (int i = 0; i < childs.getLength(); i++) {
327: current = childs.item(i);
328:
329: // only element nodes
330: if (current.getNodeType() == Node.ELEMENT_NODE) {
331: current.normalize();
332: NodeList valueChilds = current
333: .getChildNodes();
334: String key;
335: StringBuffer valueBuffer;
336: String value;
337:
338: key = current.getNodeName();
339: valueBuffer = new StringBuffer();
340: for (int m = 0; m < valueChilds.getLength(); m++) {
341: current = valueChilds.item(m); // attention: current is reused here!
342: if (current.getNodeType() == Node.TEXT_NODE) { // only text nodes
343: if (valueBuffer.length() > 0) {
344: valueBuffer.append(' ');
345: }
346: valueBuffer.append(current
347: .getNodeValue());
348: }
349: }
350: value = valueBuffer.toString().trim();
351: if (key != null && value != null
352: && value.length() > 0) {
353: parameters.setParameter(key, value);
354: }
355: }
356: }
357: }
358: }
359: }
360: }
361: }
|