001: package org.esupportail.cas.server;
002:
003: import java.util.Iterator;
004: import java.util.LinkedList;
005: import java.util.List;
006: import java.util.Date;
007:
008: import java.io.File;
009: import java.io.UnsupportedEncodingException;
010: import java.lang.reflect.Constructor;
011: import java.lang.reflect.InvocationTargetException;
012: import java.net.URLDecoder;
013:
014: import javax.servlet.ServletRequest;
015:
016: import org.dom4j.Document;
017: import org.dom4j.DocumentException;
018: import org.dom4j.DocumentHelper;
019: import org.dom4j.Element;
020: import org.dom4j.io.SAXReader;
021: import org.dom4j.XPath;
022: import org.esupportail.cas.server.util.BasicHandler;
023: import org.esupportail.cas.server.util.MisconfiguredHandlerException;
024: import org.esupportail.cas.server.util.RedundantHandler;
025: import org.esupportail.cas.server.util.Server;
026: import org.esupportail.cas.server.util.log.Debug;
027: import org.esupportail.cas.server.util.log.Log;
028:
029: import edu.yale.its.tp.cas.auth.provider.WatchfulPasswordHandler;
030:
031: //---------------------------------------------------------
032:
033: /**
034: * This class permits via a Xml configuration file to call different specific handler
035: *
036: * @author Pascal Aubry <pascal.aubry at univ-rennes1.fr>
037: * @author Jean-Baptiste Daniel <danielj at sourceforge.net>
038: */
039:
040: public final class GenericHandler extends WatchfulPasswordHandler {
041:
042: /**
043: * the package release number.
044: */
045: private static final String CASGENERICHANDLER_RELEASE = "2.0.5-2";
046:
047: /**
048: * Debugging mode.
049: */
050: private static boolean debug = false;
051:
052: /**
053: * the list of all the specific handlers to try for authentication.
054: */
055: private static List handlers = null;
056:
057: /**
058: * the name of the configuration file.
059: */
060: private static String configFilename = null;
061:
062: /**
063: * a boolean to store wheter empty passwords can be accepted or not.
064: */
065: private static boolean acceptEmptyPasswords;
066:
067: /**
068: * Retrieve the name of the file storing the configuration.
069: *
070: * @return a string.
071: */
072: private static String getConfigFileName() {
073: java.net.URL resourceURL;
074:
075: if (configFilename == null) {
076: resourceURL = GenericHandler.class
077: .getResource("/../genericHandler.xml");
078: if (resourceURL == null) {
079: Log
080: .warn("Configuration file genericHandler.xml is missing!");
081: }
082: // if configuration file is missing an exception is thrown and the cas server crashes
083: configFilename = resourceURL.getFile();
084:
085: // decode string to allow paths with spaces (since version 2.0.4)
086: // J-Louis.RENAUD at univ-bpclermont.fr
087: try {
088: configFilename = URLDecoder.decode(configFilename,
089: "UTF-8");
090: } catch (UnsupportedEncodingException e) {
091: Log
092: .warn("Configuration filename could not be decoded!");
093: }
094: }
095: return configFilename;
096: }
097:
098: /**
099: * the last time the configuration file was modified
100: */
101: private static long configDate = 0;
102:
103: /**
104: * tell if the configuration file was modified.
105: *
106: * @return true if the file was modified since last configuration reading.
107: */
108: private static boolean configFileModified() {
109: return new File(getConfigFileName()).lastModified() > configDate;
110: }
111:
112: /**
113: * Scans an XML configuration file and builds the structure needed
114: * for authentication (a list of specific handlers).
115: *
116: * @throws MisconfiguredHandlerException MisconfiguredHandlerException
117: */
118: private void readConfigFile() throws MisconfiguredHandlerException {
119:
120: // create an empty list of handlers
121: List newHandlers = new LinkedList();
122:
123: Document config;
124: XPath xPathSelector;
125: Element authenticationElement;
126:
127: // create a SAXReader component
128: SAXReader saxReader = new SAXReader(false);
129:
130: // parse the configuration file
131: try {
132: config = saxReader.read(getConfigFileName());
133: } catch (DocumentException e) {
134: throw new MisconfiguredHandlerException(
135: "Configuration file is ill-formed: "
136: + e.getMessage());
137: }
138:
139: // check the root node
140: xPathSelector = DocumentHelper.createXPath("/authentication");
141: try {
142: authenticationElement = (Element) xPathSelector
143: .selectNodes(config).get(0);
144: } catch (IndexOutOfBoundsException e) {
145: throw new MisconfiguredHandlerException(
146: "Root <authentication> tag not found.");
147: }
148: boolean configDebug = Debug.elementDebugValue(
149: authenticationElement, false);
150: if (configDebug) {
151: Log.debug("Debugging mode set to \"on\".");
152: }
153: boolean newAcceptEmptyPasswords = authenticationElement
154: .attributeValue("empty_password_accepted", "off")
155: .equals("on");
156: if (configDebug) {
157: if (newAcceptEmptyPasswords) {
158: Log.debug("Empty passwords will be accepted.");
159: } else {
160: Log.debug("Empty passwords will not be accepted.");
161: }
162: }
163: // get the list of all the handlers
164: xPathSelector = DocumentHelper.createXPath("/authentication/*");
165: List handlerElements = xPathSelector.selectNodes(config);
166:
167: // check that at least one handler is declared
168: if (handlerElements.isEmpty()) {
169: throw new MisconfiguredHandlerException(
170: "No \"handler\" element found.");
171: }
172:
173: for (Iterator i = handlerElements.iterator(); i.hasNext();) {
174: Element handlerElement = (Element) i.next();
175:
176: // check that all the sub-elements are handlers
177: if (!handlerElement.getName().equals("handler")) {
178: throw new MisconfiguredHandlerException("Unknown \""
179: + handlerElement.getName()
180: + "\" XML element found.");
181: }
182: if (configDebug) {
183: Log.debug("Found a handler.");
184: }
185:
186: // extract the classname
187: Element handlerClassnameElement = handlerElement
188: .element("classname");
189: if (handlerClassnameElement == null) {
190: throw new MisconfiguredHandlerException(
191: "No \"classname\" element found for a handler.");
192: }
193: String handlerClassname = handlerClassnameElement
194: .getTextTrim();
195: if (handlerClassname.equals("")) {
196: throw new MisconfiguredHandlerException(
197: "Empty \"classname\" element found for a handler.");
198: }
199: if (configDebug) {
200: Log.debug("This is a \"" + handlerClassname
201: + "\" handler.");
202: }
203:
204: // try to load the class
205: Class handlerClass;
206: try {
207: handlerClass = Class.forName(handlerClassname);
208: } catch (ClassNotFoundException e) {
209: throw new MisconfiguredHandlerException("Class \""
210: + handlerClassname + "\" could not be loaded.");
211: }
212:
213: // get the constructor
214: Class[] handlerConstructorArgumentTypes = { Element.class,
215: Boolean.class };
216: Constructor handlerConstructor;
217: try {
218: handlerConstructor = handlerClass
219: .getConstructor(handlerConstructorArgumentTypes);
220: } catch (Exception e) {
221: throw new MisconfiguredHandlerException(
222: "The constructor of Class \""
223: + handlerClassname
224: + "\" could not be loaded because a \""
225: + e.getClass().getName()
226: + "\" exception was raised: "
227: + e.getMessage());
228: }
229:
230: // create an instance
231: Object[] handlerConstructorArguments = { handlerElement,
232: new Boolean(configDebug) };
233: BasicHandler handler;
234: try {
235: handler = (BasicHandler) handlerConstructor
236: .newInstance(handlerConstructorArguments);
237: } catch (InvocationTargetException e) {
238: throw new MisconfiguredHandlerException(e.getCause()
239: .getMessage());
240: } catch (Exception e) {
241: throw new MisconfiguredHandlerException("Class \""
242: + handlerClassname
243: + "\" could not be instanciated because a \""
244: + e.getClass().getName()
245: + "\" exception was raised: " + e.getMessage());
246: }
247:
248: newHandlers.add(handler);
249: }
250:
251: handlers = newHandlers;
252: debug = configDebug;
253: acceptEmptyPasswords = newAcceptEmptyPasswords;
254:
255: // while we created handlers and servers, we set the debugging mode
256: // on each object depending on the confifuration. Now we set debugging mode
257: // to on for redundant handlers having servers with debugging mode set
258: // to on, and then the main handler if one specific handler (at least) has
259: // debugging mode set to on.
260: for (Iterator i = handlers.iterator(); i.hasNext();) {
261: BasicHandler handler = (BasicHandler) i.next();
262: if (handler instanceof RedundantHandler) {
263: for (Iterator j = ((RedundantHandler) handler)
264: .getServers().iterator(); j.hasNext();) {
265: Server server = (Server) j.next();
266: if (server.isDebug()) {
267: handler.setDebug(true);
268: }
269: }
270: }
271: if (handler.isDebug()) {
272: debug = true;
273: }
274: }
275:
276: // configuration read without any error
277: configDate = new Date().getTime();
278: }
279:
280: /**
281: * Try to authenticate a user by calling all the handlers found in
282: * the configuration file.
283: *
284: * @param request the current request
285: * @param username the username provided by the user
286: * @param password the password provided by the user
287: *
288: * @return true on success, false otherwise.
289: */
290: public synchronized boolean authenticate(
291: final ServletRequest request, final String username,
292: final String password) {
293: if (!super .authenticate(request, username, password)) {
294: Log
295: .warn("Authentication process was blocked by WatchfullPasswordHandler.");
296: return false;
297: }
298:
299: if (configFileModified()) {
300: Log.info(new String("ESUP-Portail Generic Handler "
301: + CASGENERICHANDLER_RELEASE
302: + ", reading configuration file..."));
303: try {
304: readConfigFile();
305: Log.info("Configuration file read without any error.");
306: } catch (MisconfiguredHandlerException e) {
307: Log
308: .error("An error occured while reading the configuration file:");
309: Log.error(e.getMessage());
310: if (handlers == null) {
311: Log
312: .warn("No previous configuration, authentication will always fail.");
313: } else {
314: Log.warn("Keeping the previous configuration.");
315: }
316: }
317: }
318:
319: // as the debug flag is set by readConfigFile(), a trace can be written only at this point
320: if (debug) {
321: Log.traceBegin();
322: }
323:
324: if (handlers == null) {
325: Log.warn("No authentication handler found.");
326: }
327:
328: if (username.equals("")) {
329: if (debug) {
330: Log
331: .debug("Empty username, handlers will not be called.");
332: }
333: } else if (password.equals("") && !acceptEmptyPasswords) {
334: if (debug) {
335: Log
336: .debug("Empty password, handlers will not be called.");
337: }
338: } else if (handlers != null) {
339: for (Iterator i = handlers.iterator(); i.hasNext();) {
340: BasicHandler handler = (BasicHandler) i.next();
341: switch (handler.authenticate(username, password)) {
342: case BasicHandler.SUCCEEDED:
343: Log.info("Authentication succeeded for user `"
344: + username + "'.");
345: if (debug) {
346: Log.traceEnd(String.valueOf(true));
347: }
348: return true;
349: case BasicHandler.FAILED_STOP:
350: Log.info("Authentication failed for user `"
351: + username + "'.");
352: if (debug) {
353: Log.traceEnd(String.valueOf(false));
354: }
355: return false;
356: default: // BasicHandler.FAILED_CONTINUE
357: }
358: }
359: }
360: Log.info("Authentication failed for user `" + username + "'.");
361: if (debug) {
362: Log.traceEnd(String.valueOf(false));
363: }
364: return false;
365: }
366:
367: }
|