View Javadoc

1   /*
2    * Copyright (c) 2004 UNINETT FAS
3    *
4    * This program is free software; you can redistribute it and/or modify it
5    * under the terms of the GNU General Public License as published by the Free
6    * Software Foundation; either version 2 of the License, or (at your option)
7    * any later version.
8    *
9    * This program is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11   * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12   * more details.
13   *
14   * You should have received a copy of the GNU General Public License along with
15   * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16   * Place - Suite 330, Boston, MA 02111-1307, USA.
17   */
18  
19  package no.feide.moria.webservices.v2_2;
20  
21  import java.util.ArrayList;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Properties;
26  import java.util.Random;
27  import java.util.Set;
28  
29  import no.feide.moria.controller.AuthenticationException;
30  import no.feide.moria.controller.AuthorizationException;
31  import no.feide.moria.controller.DirectoryUnavailableException;
32  import no.feide.moria.controller.InoperableStateException;
33  import no.feide.moria.controller.MoriaController;
34  import no.feide.moria.log.MessageLogger;
35  import no.feide.moria.servlet.RequestUtil;
36  import no.feide.moria.servlet.soap.AuthenticationFailedException;
37  import no.feide.moria.servlet.soap.AuthenticationUnavailableException;
38  import no.feide.moria.servlet.soap.AuthorizationFailedException;
39  import no.feide.moria.servlet.soap.IllegalInputException;
40  import no.feide.moria.servlet.soap.InternalException;
41  import no.feide.moria.servlet.soap.UnknownTicketException;
42  
43  import org.apache.axis.MessageContext;
44  import org.apache.axis.session.Session;
45  import org.apache.axis.transport.http.AxisHttpSession;
46  import org.apache.log4j.Level;
47  
48  /***
49   * Implements the Moria2 v2.1 SOAP interface.
50   * @see no.feide.moria.webservices.v2_2.Authentication
51   */
52  public final class AuthenticationImpl
53  implements Authentication {
54  
55      /***
56       * The message logger.
57       */
58      private MessageLogger messageLogger;
59  
60      /***
61       * Log message for <code>AuthorizationException</code>s.
62       */
63      private static final String AUTHZ_EX_MESSAGE = "Authorization failed. Throwing RemoteException to service: ";
64  
65      /***
66       * Log message for <code>AuthenticationException</code>s.
67       */
68      private static final String AUTHN_EX_MSG = "Authentication failed. Throwing RemoteException to service: ";
69  
70      /***
71       * Log message for <code>DirectoryUnavailableException</code>s.
72       */
73      private static final String DIR_UNAV_EX_MSG = "Directory unavailable. Throwing RemoteException to service: ";
74  
75      /***
76       * Log message for <code>MoriaControllerException</code>s.
77       */
78      private static final String MORIACTRL_EX_MESSAGE = "Exception from MoriaController. Throwing RemoteException to service: ";
79  
80      /***
81       * Log message for <code>InoperableStateException</code>s.
82       */
83      private static final String INOP_STATE_EX_MSG = "Controller in inoperable state. Throwing RemoteException to service: ";
84  
85      /***
86       * Log message for <code>UnknownTicketException</code>s.
87       */
88      private static final String UNKNOWN_TICKET_EX_MSG = "Ticket is unknown. Throwing RemoteException to service: ";
89  
90  
91      /***
92       * Default constructor. Initializes the logger.
93       */
94      public AuthenticationImpl() {
95  
96          messageLogger = new MessageLogger(AuthenticationImpl.class);
97      }
98  
99  
100     /***
101      * @see no.feide.moria.webservices.v2_2.Authentication#initiateAuthentication(java.lang.String[],
102      *      java.lang.String, java.lang.String, boolean)
103      */
104     public String initiateAuthentication(final String[] attributes, final String returnURLPrefix, final String returnURLPostfix, final boolean forceInteractiveAuthentication)
105     throws AuthorizationFailedException, IllegalInputException,
106     InternalException {
107 
108         /* Axis message context containg request data. */
109         MessageContext messageContext = MessageContext.getCurrentContext();
110         String servicePrincipal = messageContext.getUsername();
111 
112         String urlPrefix = null;
113         Session genericSession = messageContext.getSession();
114 
115         if (genericSession instanceof AxisHttpSession) {
116             AxisHttpSession axisHttpSession = (AxisHttpSession) genericSession;
117             Properties properties = (Properties) axisHttpSession.getRep().getServletContext().getAttribute("no.feide.moria.web.config");
118             urlPrefix = (properties.getProperty(RequestUtil.PROP_LOGIN_URL_PREFIX) + "?" + properties.getProperty(RequestUtil.PROP_LOGIN_TICKET_PARAM) + "=");
119         }
120 
121         try {
122 
123             return urlPrefix + MoriaController.initiateAuthentication(attributes, returnURLPrefix, returnURLPostfix, forceInteractiveAuthentication, servicePrincipal);
124 
125         } catch (AuthorizationException e) {
126 
127             // Client service did something it was not authorized to do.
128             messageLogger.logWarn(AUTHZ_EX_MESSAGE + servicePrincipal, e);
129             throw new AuthorizationFailedException(e.getMessage());
130 
131         } catch (no.feide.moria.controller.IllegalInputException e) {
132 
133             // Illegal input from client service.
134             messageLogger.logWarn(MORIACTRL_EX_MESSAGE + servicePrincipal, e);
135             throw new IllegalInputException(e.getMessage());
136 
137         } catch (InoperableStateException e) {
138 
139             // Moria is in an inoperable state.
140             messageLogger.logCritical(INOP_STATE_EX_MSG + servicePrincipal, e);
141             throw new InternalException(e.getMessage());
142 
143         }
144     }
145 
146 
147     /***
148      * @see no.feide.moria.webservices.v2_2.Authentication#directNonInteractiveAuthentication(java.lang.String[],
149      *      java.lang.String, java.lang.String)
150      */
151     public Attribute[] directNonInteractiveAuthentication(final String[] attributes, final String username, final String password)
152     throws AuthorizationFailedException, AuthenticationFailedException,
153     AuthenticationUnavailableException, IllegalInputException,
154     InternalException {
155 
156         /* Axis message context containg request data. */
157         MessageContext messageContext = MessageContext.getCurrentContext();
158         String servicePrincipal = messageContext.getUsername();
159 
160         try {
161             Map returnAttributes = MoriaController.directNonInteractiveAuthentication(attributes, username, password, servicePrincipal);
162             return mapToAttributeArray(returnAttributes, null);
163         } catch (AuthorizationException e) {
164 
165             // Client service did something it was not authorized to do.
166             messageLogger.logWarn(AUTHZ_EX_MESSAGE + servicePrincipal, e);
167             throw new AuthorizationFailedException(e.getMessage());
168 
169         } catch (AuthenticationException e) {
170 
171             // User failed authentication.
172             messageLogger.logWarn(AUTHN_EX_MSG + servicePrincipal, e);
173             throw new AuthenticationFailedException(e.getMessage());
174 
175         } catch (DirectoryUnavailableException e) {
176 
177             // Authentication server was unavailable.
178             messageLogger.logWarn(DIR_UNAV_EX_MSG + servicePrincipal, e);
179             throw new AuthenticationUnavailableException(e.getMessage());
180 
181         } catch (no.feide.moria.controller.IllegalInputException e) {
182 
183             // Illegal input from client service.
184             messageLogger.logWarn(MORIACTRL_EX_MESSAGE + servicePrincipal, e);
185             throw new IllegalInputException(e.getMessage());
186 
187         } catch (InoperableStateException e) {
188 
189             // Moria is in an inoperable state.
190             messageLogger.logCritical(INOP_STATE_EX_MSG + servicePrincipal, e);
191             throw new InternalException(e.getMessage());
192 
193         }
194     }
195 
196 
197     /***
198      * @see no.feide.moria.webservices.v2_2.Authentication#proxyAuthentication(java.lang.String[],
199      *      java.lang.String)
200      */
201     public Attribute[] proxyAuthentication(final String[] attributes, final String proxyTicket)
202     throws AuthorizationFailedException, IllegalInputException,
203     InternalException, UnknownTicketException {
204 
205         /* Axis message context containg request data. */
206         MessageContext messageContext = MessageContext.getCurrentContext();
207         String servicePrincipal = messageContext.getUsername();
208 
209         try {
210 
211             Map returnAttributes = MoriaController.proxyAuthentication(attributes, proxyTicket, servicePrincipal);
212             return mapToAttributeArray(returnAttributes, proxyTicket);
213 
214         } catch (AuthorizationException e) {
215 
216             // Client service did something it was not authorized to do.
217             messageLogger.logWarn(AUTHZ_EX_MESSAGE + servicePrincipal, e);
218             throw new AuthorizationFailedException(e.getMessage());
219 
220         } catch (no.feide.moria.controller.IllegalInputException e) {
221 
222             // Illegal input from client service.
223             messageLogger.logWarn(MORIACTRL_EX_MESSAGE + servicePrincipal, e);
224             throw new IllegalInputException(e.getMessage());
225 
226         } catch (InoperableStateException e) {
227 
228             // Moria is in an inoperable state.
229             messageLogger.logCritical(INOP_STATE_EX_MSG + servicePrincipal, e);
230             throw new InternalException(e.getMessage());
231 
232         } catch (no.feide.moria.controller.UnknownTicketException e) {
233 
234             // An unknown ticket was used by the client service.
235             messageLogger.logWarn(UNKNOWN_TICKET_EX_MSG + servicePrincipal, e);
236             throw new UnknownTicketException(e.getMessage());
237 
238         }
239     }
240 
241 
242     /***
243      * @see no.feide.moria.webservices.v2_2.Authentication#getProxyTicket(java.lang.String,
244      *      java.lang.String)
245      */
246     public String getProxyTicket(final String ticketGrantingTicket, final String proxyServicePrincipal)
247     throws AuthorizationFailedException, IllegalInputException,
248     InternalException, UnknownTicketException {
249 
250         /* Axis message context containg request data. */
251         MessageContext messageContext = MessageContext.getCurrentContext();
252         String servicePrincipal = messageContext.getUsername();
253 
254         try {
255 
256             return MoriaController.getProxyTicket(ticketGrantingTicket, proxyServicePrincipal, servicePrincipal);
257 
258         } catch (AuthorizationException e) {
259 
260             // Client service did something it was not supposed to do.
261             messageLogger.logWarn(AUTHZ_EX_MESSAGE + servicePrincipal, e);
262             throw new AuthorizationFailedException(e.getMessage());
263 
264         } catch (no.feide.moria.controller.IllegalInputException e) {
265 
266             // Illegal input from client service.
267             messageLogger.logWarn(MORIACTRL_EX_MESSAGE + servicePrincipal, e);
268             throw new IllegalInputException(e.getMessage());
269 
270         } catch (InoperableStateException e) {
271 
272             // Moria is in an inoperable state.
273             messageLogger.logCritical(INOP_STATE_EX_MSG + servicePrincipal, e);
274             throw new InternalException(e.getMessage());
275 
276         } catch (no.feide.moria.controller.UnknownTicketException e) {
277 
278             // Client service used an unknown ticket.
279             messageLogger.logWarn(UNKNOWN_TICKET_EX_MSG + servicePrincipal, e);
280             throw new UnknownTicketException(e.getMessage());
281 
282         }
283     }
284 
285 
286     /***
287      * @see no.feide.moria.webservices.v2_2.Authentication#getUserAttributes(java.lang.String)
288      */
289     public Attribute[] getUserAttributes(final String serviceTicket)
290     throws AuthorizationFailedException, IllegalInputException,
291     InternalException, UnknownTicketException {
292 
293         /* Axis message context containg request data. */
294         MessageContext messageContext = MessageContext.getCurrentContext();
295         String servicePrincipal = messageContext.getUsername();
296 
297         try {
298             Map returnAttributes = MoriaController.getUserAttributes(serviceTicket, servicePrincipal);
299             
300             // Prepare debug output.
301             if (messageLogger.isEnabledFor(Level.DEBUG)) {
302                 String attributeNames = "";
303                 Set keySet = returnAttributes.keySet();
304                 if (keySet != null) {
305                     Iterator i = keySet.iterator();
306                     while (i.hasNext())
307                         attributeNames = attributeNames + i.next() + ", ";
308                     if (attributeNames.length() > 0)
309                         attributeNames = attributeNames.substring(0, attributeNames.length() - 2);
310                 }
311                 messageLogger.logDebug("Returned attributes [" + attributeNames + "] to service '" + servicePrincipal + "'", serviceTicket);
312             }
313             
314             return mapToAttributeArray(returnAttributes, serviceTicket);
315         } catch (no.feide.moria.controller.IllegalInputException e) {
316 
317             // Illegal input used by service.
318             messageLogger.logWarn(MORIACTRL_EX_MESSAGE + servicePrincipal, e);
319             throw new IllegalInputException(e.getMessage());
320 
321         } catch (InoperableStateException e) {
322 
323             // Moria is in an inoperable state!
324             messageLogger.logCritical(INOP_STATE_EX_MSG + servicePrincipal, e);
325             throw new InternalException(e.getMessage());
326 
327         } catch (no.feide.moria.controller.UnknownTicketException e) {
328 
329             // An unknown ticket was used.
330             messageLogger.logWarn(UNKNOWN_TICKET_EX_MSG + servicePrincipal, e);
331             throw new UnknownTicketException(e.getMessage());
332 
333         } catch (AuthorizationException e) {
334 
335             // Service was not authorized for this operation.
336             messageLogger.logWarn("Service not allowed for organization. Throwing RemoteException to service: ", e);
337             throw new AuthorizationFailedException(e.getMessage());
338         }
339 
340     }
341 
342 
343     /***
344      * @see no.feide.moria.webservices.v2_2.Authentication#verifyUserExistence(java.lang.String)
345      */
346     public boolean verifyUserExistence(final String username)
347     throws AuthorizationFailedException, AuthenticationUnavailableException,
348     IllegalInputException, InternalException {
349 
350         /* Axis message context containg request data. */
351         MessageContext messageContext = MessageContext.getCurrentContext();
352         String servicePrincipal = messageContext.getUsername();
353 
354         try {
355 
356             return MoriaController.verifyUserExistence(username, servicePrincipal);
357 
358         } catch (AuthorizationException e) {
359 
360             // Client service did something it was not supposed to do.
361             messageLogger.logWarn(AUTHZ_EX_MESSAGE + servicePrincipal, e);
362             throw new AuthorizationFailedException(e.getMessage());
363 
364         } catch (DirectoryUnavailableException e) {
365 
366             // Authentication server is unavailable.
367             messageLogger.logWarn(DIR_UNAV_EX_MSG + servicePrincipal, e);
368             throw new AuthenticationUnavailableException(e.getMessage());
369 
370         } catch (no.feide.moria.controller.IllegalInputException e) {
371 
372             // Illegal input from client service.
373             messageLogger.logWarn(MORIACTRL_EX_MESSAGE + servicePrincipal, e);
374             throw new IllegalInputException(e.getMessage());
375 
376         } catch (InoperableStateException e) {
377 
378             // Moria is in an inoperable state.
379             messageLogger.logCritical(INOP_STATE_EX_MSG + servicePrincipal, e);
380             throw new InternalException(e.getMessage());
381 
382         }
383     }
384 
385 
386     /***
387      * Utility method to convert a <code>Map</code> to an array of
388      * <code>Attribute</code>s.
389      * @param map
390      *            The <code>Map</code> to be converted.
391      * @param activeTicketId
392      *            Optional variable for logging purposes.
393      * @return Array of <code>Attribute</code> objects.
394      */
395     private Attribute[] mapToAttributeArray(final Map map, final String activeTicketId) {
396 
397         /* Get iterator for map keys. */
398         Iterator iterator = map.keySet().iterator();
399 
400         /* List to hold finished Attributes while processing map. */
401         List attributeList = new ArrayList();
402 
403         /* Iterate over keys in map. */
404         while (iterator.hasNext()) {
405             Object key = iterator.next();
406             Object value = map.get(key);
407 
408             // Create a new attribute and randomly set the separator value.
409             Attribute attribute = new Attribute();
410             Random randomGenerator = new Random();
411             String separator = "_";
412             for (int i=0; i<4; i++)
413                 separator = separator + (char) (97+randomGenerator.nextInt(25));
414             separator = separator + "_";
415             attribute.setSeparator(separator);
416 
417             /* Check that key is a String, if not will ignore the whole entry. */
418             if (key instanceof String) {
419                 /* Add the key as name of attribute. */
420                 attribute.setName((String) key);
421 
422                 /*
423                  * Check type of value. If not String or String[] we don't add
424                  * it to the attribute list resulting in the whole entry beeing
425                  * ignored.
426                  */
427                 if (value instanceof String) {
428 
429                     // Create one-element String[] of value before setting.
430                     attribute.setValues((String) value);
431                     attributeList.add(attribute);
432 
433                 } else if (value instanceof String[]) {
434 
435                     // Encode the attribute values from String[] to String.
436                     attribute.setValues(encodeValues(separator, (String[]) value));
437                     attributeList.add(attribute);
438 
439                 } else if (value != null) {
440                     messageLogger.logInfo("Attribute value not String or String[]. Entry not added to Attribute[]. ", activeTicketId);
441                 }
442             } else if (value != null) {
443                 messageLogger.logInfo("Attribute key not String. Entry not added to Attribute[]", activeTicketId);
444             }
445         }
446         return (Attribute[]) attributeList.toArray(new Attribute[] {});
447     }
448 
449 
450     /***
451      * Encode a <code>String</code> array into a single string, using the
452      * <code>separator</code> between attribute values. All occurrences of
453      * <code>separator</code> in the original attribute values are replaced by
454      * two <code>separator</code>s.
455      * @param separator
456      *            The separator to be used.
457      * @param values
458      *            The values to be encoded using <code>separator</code>.
459      * @return The encoded values.
460      */
461     private static String encodeValues(final String separator, final String[] values) {
462 
463         String encoded = new String();
464         for (int i = 0; i < values.length; i++)
465             encoded = encoded + values[i].replaceAll(separator, separator + separator) + separator;
466         encoded = encoded.substring(0, encoded.length() - separator.length());
467         return new String(encoded);
468 
469     }
470 
471 }