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   * $Id: AuthenticationImpl.java,v 1.6 2006/02/14 15:16:02 catoolsen Exp $
19   */
20  
21  package no.feide.moria.webservices.v2_0;
22  
23  import java.rmi.RemoteException;
24  import java.util.ArrayList;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Properties;
29  import java.util.Set;
30  
31  import no.feide.moria.controller.AuthenticationException;
32  import no.feide.moria.controller.AuthorizationException;
33  import no.feide.moria.controller.DirectoryUnavailableException;
34  import no.feide.moria.controller.IllegalInputException;
35  import no.feide.moria.controller.InoperableStateException;
36  import no.feide.moria.controller.MoriaController;
37  import no.feide.moria.controller.UnknownTicketException;
38  import no.feide.moria.log.MessageLogger;
39  import no.feide.moria.servlet.RequestUtil;
40  
41  import org.apache.axis.MessageContext;
42  import org.apache.axis.session.Session;
43  import org.apache.axis.transport.http.AxisHttpSession;
44  import org.apache.log4j.Level;
45  
46  /***
47   * @author Bjørn Ola Smievoll <b.o@smievoll.no>
48   * @version $Revision: 1.6 $
49   */
50  public final class AuthenticationImpl implements Authentication {
51  
52      /*** Class wide logger. */
53      private MessageLogger messageLogger;
54  
55      /*** Log message for AuthorizationExceptions. */
56      private static final String AUTHZ_EX_MSG = "Authorization failed. Throwing RemoteException to service: ";
57  
58      /*** Log message for AuthenticationExceptions. */
59      private static final String AUTHN_EX_MSG = "AuthenticationImpl failed. Throwing RemoteException to service: ";
60  
61      /*** Log message for DirectoryUnavailableExceptions. */
62      private static final String DIR_UNAV_EX_MSG = "Directory unavailable. Throwing RemoteException to service: ";
63  
64      /*** Log message for IllegalInputExceptions. */
65      private static final String ILLEGAL_INPUT_EX_MSG = "Illegal input. Throwing RemoteException to service: ";
66  
67      /*** Log message for InoperableStateExceptions. */
68      private static final String INOP_STATE_EX_MSG = "Controller in inoperable state. Throwing RemoteException to service: ";
69  
70      /*** Log message for UnknownTicketExceptions. */
71      private static final String UNKNOWN_TICKET_EX_MSG = "Ticket is unknown. Throwing RemoteException to service: ";
72  
73      /***
74       * Default constructor.
75       * Initializes the logger.
76       */
77      public AuthenticationImpl() {
78          messageLogger = new MessageLogger(AuthenticationImpl.class);
79      }
80  
81      /***
82       * Initiates authentication.
83       *
84       * The initial call done by a service to start a login attempt.
85       *
86       * @param attributes
87       *          The attributes the service wants returned on login
88       * @param returnURLPrefix
89       *          The prefix of the url the user is to be returned to
90       * @param returnURLPostfix
91       *          The optional postfix of the return url
92       * @param forceInteractiveAuthentication
93       *          Whether or not cookie based authentication (SSO Light)
94       *          should be allowed.
95       * @return The Moria url the client is to be redirected to.
96       * @throws RemoteException
97       *          If anything fails during the call.
98       * @see no.feide.moria.webservices.v2_0.Authentication#initiateAuthentication(java.lang.String[],
99       *      java.lang.String, java.lang.String, boolean)
100      */
101     public String initiateAuthentication(final String[] attributes, final String returnURLPrefix, final String returnURLPostfix,
102             final boolean forceInteractiveAuthentication) throws RemoteException {
103 
104         /* Axis message context containg request data. */
105         MessageContext messageContext = MessageContext.getCurrentContext();
106         String servicePrincipal = messageContext.getUsername();
107 
108         String urlPrefix = null;
109         Session genericSession = messageContext.getSession();
110 
111         if (genericSession instanceof AxisHttpSession) {
112             AxisHttpSession axisHttpSession = (AxisHttpSession) genericSession;
113             Properties properties = (Properties) axisHttpSession.getRep().getServletContext().getAttribute(
114                     "no.feide.moria.web.config");
115             urlPrefix = (properties.getProperty(RequestUtil.PROP_LOGIN_URL_PREFIX) + "?"
116                     + properties.getProperty(RequestUtil.PROP_LOGIN_TICKET_PARAM) + "=");
117         }
118 
119         try {
120             return urlPrefix
121                     + MoriaController.initiateAuthentication(attributes, returnURLPrefix, returnURLPostfix,
122                             forceInteractiveAuthentication, servicePrincipal);
123         } catch (AuthorizationException ae) {
124             messageLogger.logWarn(AUTHZ_EX_MSG + servicePrincipal, ae);
125             throw new RemoteException(ae.getMessage());
126         } catch (IllegalInputException iie) {
127             messageLogger.logWarn(ILLEGAL_INPUT_EX_MSG + servicePrincipal, iie);
128             throw new RemoteException(iie.getMessage());
129         } catch (InoperableStateException ise) {
130             messageLogger.logCritical(INOP_STATE_EX_MSG + servicePrincipal, ise);
131             throw new RemoteException(ise.getMessage());
132         }
133     }
134 
135     /***
136      * Performs direct non-interactive authentication.
137      *
138      * A redirect- and html-less login method.  Only to be used in
139      * special cases where the client for some reason does not
140      * support the standard login procedure.  Inherently insecure as
141      * the service will have knowledge of the plaintext password.
142      *
143      * @param attributes
144      *          The attributes the service wants returned on login.
145      * @param username
146      *          The user name of the user to be authenticated.
147      * @param password
148      *          The password of the user to be authenticated.
149      * @return Array of attributes as requested.
150      * @throws RemoteException
151      *          If anything fails during the call.
152      * @see no.feide.moria.webservices.v2_0.Authentication#directNonInteractiveAuthentication(java.lang.String[],
153      *      java.lang.String, java.lang.String)
154      */
155     public Attribute[] directNonInteractiveAuthentication(final String[] attributes, final String username, final String password)
156             throws RemoteException {
157 
158         /* Axis message context containg request data. */
159         MessageContext messageContext = MessageContext.getCurrentContext();
160         String servicePrincipal = messageContext.getUsername();
161 
162         try {
163             Map returnAttributes = MoriaController.directNonInteractiveAuthentication(attributes, username, password,
164                     servicePrincipal);
165             return mapToAttributeArray(returnAttributes, null);
166         } catch (AuthenticationException ae) {
167             messageLogger.logWarn(AUTHN_EX_MSG + servicePrincipal, ae);
168             throw new RemoteException(ae.getMessage());
169         } catch (AuthorizationException ae) {
170             messageLogger.logWarn(AUTHZ_EX_MSG + servicePrincipal, ae);
171             throw new RemoteException(ae.getMessage());
172         } catch (DirectoryUnavailableException due) {
173             messageLogger.logWarn(DIR_UNAV_EX_MSG + servicePrincipal, due);
174             throw new RemoteException(due.getMessage());
175         } catch (IllegalInputException iie) {
176             messageLogger.logWarn(ILLEGAL_INPUT_EX_MSG + servicePrincipal, iie);
177             throw new RemoteException(iie.getMessage());
178         } catch (InoperableStateException ise) {
179             messageLogger.logCritical(INOP_STATE_EX_MSG + servicePrincipal, ise);
180             throw new RemoteException(ise.getMessage());
181         }
182     }
183 
184     /***
185      * Gets user attributes.
186      *
187      * Called by the service when the user returns after a successful
188      * login.
189      *
190      * @param serviceTicket
191      *          The ticket included in the return request issued by the client.
192      * @return Array of attributes as requested in initiateAuthentication.
193      * @throws RemoteException
194      *          If anything fails during the call.
195      * @see no.feide.moria.webservices.v2_0.Authentication#getUserAttributes(java.lang.String)
196      */
197     public Attribute[] getUserAttributes(final String serviceTicket) throws RemoteException {
198 
199         /* Axis message context containg request data. */
200         MessageContext messageContext = MessageContext.getCurrentContext();
201         String servicePrincipal = messageContext.getUsername();
202 
203         try {
204             Map returnAttributes = MoriaController.getUserAttributes(serviceTicket, servicePrincipal);
205             
206             // Prepare debug output.
207             if (messageLogger.isEnabledFor(Level.DEBUG)) {
208                 String attributeNames = "";
209                 Set keySet = returnAttributes.keySet();
210                 if (keySet != null) {
211                     Iterator i = keySet.iterator();
212                     while (i.hasNext())
213                         attributeNames = attributeNames + i.next() + ", ";
214                     if (attributeNames.length() > 0)
215                         attributeNames = attributeNames.substring(0, attributeNames.length() - 2);
216                 }
217                 messageLogger.logDebug("Returned attributes [" + attributeNames + "] to service '" + servicePrincipal + "'", serviceTicket);
218             }
219             
220             return mapToAttributeArray(returnAttributes, serviceTicket);
221         } catch (IllegalInputException iie) {
222             messageLogger.logWarn(ILLEGAL_INPUT_EX_MSG + servicePrincipal, iie);
223             throw new RemoteException(iie.getMessage());
224         } catch (InoperableStateException ise) {
225             messageLogger.logCritical(INOP_STATE_EX_MSG + servicePrincipal, ise);
226             throw new RemoteException(ise.getMessage());
227         } catch (UnknownTicketException ute) {
228             messageLogger.logWarn(UNKNOWN_TICKET_EX_MSG + servicePrincipal, ute);
229             throw new RemoteException(ute.getMessage());
230         } catch (AuthorizationException e) {
231             messageLogger.logWarn("Service not allowed for organization. Throwing RemoteException to service: ", e);
232             throw new RemoteException(e.getMessage());
233         }
234     }
235 
236     /***
237      * Verifies the existence of a given user in the underlying directories.
238      *
239      * @param username
240      *          The username to be validated.
241      * @return true if the user is found.
242      * @throws RemoteException
243      *          If anything fails during the call.
244      * @see no.feide.moria.webservices.v2_0.Authentication#verifyUserExistence(java.lang.String)
245      */
246     public boolean verifyUserExistence(final String username) throws RemoteException {
247 
248         /* Axis message context containg request data. */
249         MessageContext messageContext = MessageContext.getCurrentContext();
250         String servicePrincipal = messageContext.getUsername();
251 
252         try {
253             return MoriaController.verifyUserExistence(username, servicePrincipal);
254         } catch (AuthorizationException ae) {
255             messageLogger.logWarn(AUTHZ_EX_MSG + servicePrincipal, ae);
256             throw new RemoteException(ae.getMessage());
257         } catch (DirectoryUnavailableException due) {
258             messageLogger.logWarn(DIR_UNAV_EX_MSG + servicePrincipal, due);
259             throw new RemoteException(due.getMessage());
260         } catch (IllegalInputException iie) {
261             messageLogger.logWarn(ILLEGAL_INPUT_EX_MSG + servicePrincipal, iie);
262             throw new RemoteException(iie.getMessage());
263         } catch (InoperableStateException ise) {
264             messageLogger.logCritical(INOP_STATE_EX_MSG + servicePrincipal, ise);
265             throw new RemoteException(ise.getMessage());
266         }
267     }
268 
269     /***
270      * Converts a Map to an array of Attributes.
271      * Utility method.
272      *
273      * @param map
274      *          The Map to be converted.
275      * @param activeTicketId
276      *          Optional variable for logging purposes.
277      * @return Array of attribute objects.
278      */
279     private Attribute[] mapToAttributeArray(final Map map, final String activeTicketId) {
280 
281         /* Get iterator for map keys. */
282         Iterator iterator = map.keySet().iterator();
283 
284         /* List to hold finished Attributes while processing map. */
285         List attributeList = new ArrayList();
286 
287         /* Iterate over keys in map. */
288         while (iterator.hasNext()) {
289             Object key = iterator.next();
290             Object value = map.get(key);
291 
292             Attribute attribute = new Attribute();
293 
294             /* Check that key is a String, if not will ignore the whole entry. */
295             if (key instanceof String) {
296                 /* Add the key as name of attribute. */
297                 attribute.setName((String) key);
298 
299                 /*
300                  * Check type of value. If not String or String[] we don't add it to the attribute
301                  * list resulting in the whole entry beeing ignored.
302                  */
303                 if (value instanceof String) {
304                     /* Create one-element String[] of value before setting. */
305                     attribute.setValues(new String[] {(String) value});
306                     attributeList.add(attribute);
307                 } else if (value instanceof String[]) {
308                     attribute.setValues((String[]) value);
309                     attributeList.add(attribute);
310                 } else if (value != null) {
311                     messageLogger.logInfo("Attribute value not String or String[]. Entry not added to Attribute[]. ",
312                             activeTicketId);
313                 }
314             } else if (value != null) {
315                 messageLogger.logInfo("Attribute key not String. Entry not added to Attribute[]", activeTicketId);
316             }
317         }
318         return (Attribute[]) attributeList.toArray(new Attribute[] {});
319     }
320 }