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