1   /*
2    * Copyright 2003 - 2013 The eFaps Team
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Revision:        $Rev$
17   * Last Changed:    $Date$
18   * Last Changed By: $Author$
19   */
20  
21  package org.efaps.jaas;
22  
23  import java.lang.reflect.InvocationTargetException;
24  import java.lang.reflect.Method;
25  import java.util.HashSet;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import javax.security.auth.callback.Callback;
30  import javax.security.auth.callback.CallbackHandler;
31  import javax.security.auth.callback.NameCallback;
32  import javax.security.auth.callback.PasswordCallback;
33  import javax.security.auth.callback.TextOutputCallback;
34  import javax.security.auth.callback.UnsupportedCallbackException;
35  import javax.security.auth.login.LoginContext;
36  import javax.security.auth.login.LoginException;
37  
38  import org.efaps.admin.user.Group;
39  import org.efaps.admin.user.JAASSystem;
40  import org.efaps.admin.user.Person;
41  import org.efaps.admin.user.Role;
42  import org.efaps.util.EFapsException;
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  
46  /**
47   * The login handler is used to handle the interface between JAAS and eFaps.
48   * With the name and password of the user method {@link #checkLogin} could be
49   * used to test if the user is allowed to login. The method returns then the
50   * related person to the given name and password (if found) or <i>null</i> (if
51   * not found)..
52   *
53   * @author The eFaps Team
54   * @version $Id$
55   */
56  public class LoginHandler
57  {
58      /**
59       * Logging instance used to give logging information of this class.
60       */
61      private static Logger LOG = LoggerFactory.getLogger(LoginHandler.class);
62  
63      /**
64       * The name of the application used to create a new login context. The
65       * default value is <code>eFaps</code>.
66       *
67       * @see #checkLogin(String, String)
68       */
69      private String applicationName = "eFaps";
70  
71      /**
72       * Constructor to initialize the login handler. If <i>null</i> is given to
73       * the application name, the default value defined in {@link #applicationName}
74       * is used.
75       *
76       * @param _application  application name of the JAAS configuration
77       */
78      public LoginHandler(final String _application)
79      {
80          if (_application != null) {
81              this.applicationName = _application;
82          }
83      }
84  
85      /**
86       * The instance method checks if for the given user the password is
87       * correct. The test itself is done with the JAAS module from Java.<br/> If
88       * a person is found and successfully logged in, the last login information
89       * from the person is updated to current time stamp.
90       *
91       * @param _name     name of the person name to check
92       * @param _passwd   password of the person to check
93       * @return found person
94       * @see #getPerson(LoginContext)
95       * @see #createPerson(LoginContext)
96       * @see #updatePerson(LoginContext, Person)
97       * @see #updateRoles(LoginContext, Person)
98       * @see #updateGroups(LoginContext, Person)
99       */
100     public Person checkLogin(final String _name,
101                              final String _passwd)
102     {
103         Person person = null;
104         try {
105             final LoginCallbackHandler callback = new LoginCallbackHandler(ActionCallback.Mode.LOGIN, _name, _passwd);
106             final LoginContext login = new LoginContext(getApplicationName(), callback);
107             login.login();
108 
109             person = getPerson(login);
110 
111             if (person == null) {
112                 person = createPerson(login);
113             }
114 
115             if (person != null) {
116                 updatePerson(login, person);
117 
118                 person.cleanUp();
119 
120                 updateRoles(login, person);
121                 updateGroups(login, person);
122                 updateCompanies(login, person);
123 
124                 person.updateLastLogin();
125             }
126         } catch (final EFapsException e) {
127             LoginHandler.LOG.error("login failed for '" + _name + "'", e);
128         } catch (final LoginException e) {
129             LoginHandler.LOG.error("login failed for '" + _name + "'", e);
130         }
131         return person;
132     }
133 
134     /**
135      * For the given JAAS login context the person inside eFaps is searched. If
136      * more than one person is related to the JAAS login context, an exception
137      * is thrown. If no person is found, <code>null</code> is returned.
138      *
139      * @param _login    JAAS login context
140      * @return <i>null</i> if no person in eFaps is found for given JAAS login
141      *         context
142      * @throws EFapsException   if more than one person for given JAAS login
143      *                          context is found or a method of the principals
144      *                          inside the JAAS login contexts could not be
145      *                          executed.
146      */
147     protected Person getPerson(final LoginContext _login)
148         throws EFapsException
149     {
150         Person person = null;
151         for (final JAASSystem system : JAASSystem.getAllJAASSystems()) {
152             final Set<?> users = _login.getSubject().getPrincipals(system.getPersonJAASPrincipleClass());
153 
154             for (final Object persObj : users) {
155                 try {
156                     final String persKey = (String) system.getPersonMethodKey().invoke(persObj);
157 
158                     final Person foundPerson = Person.getWithJAASKey(system, persKey);
159                     if (foundPerson == null) {
160                         person.assignToJAASSystem(system, persKey);
161                     } else if (person == null) {
162                         person = foundPerson;
163                     } else if (person.getId() != foundPerson.getId()) {
164                         LoginHandler.LOG.error("For JAAS system " + system.getName() + " "
165                             + "person with key '" + persKey + "' is not unique!"
166                             + "Have found person '" + person.getName() + "' " + "(id = "
167                             + person.getId() + ") and person " + "'"
168                             + foundPerson.getName() + "' " + "(id = " + foundPerson.getId()
169                             + ").");
170                         throw new EFapsException(LoginHandler.class, "notFound", persKey);
171                     }
172                 } catch (final IllegalAccessException e) {
173                     LoginHandler.LOG.error("could not execute person key method for system " + system.getName(), e);
174                     throw new EFapsException(LoginHandler.class, "IllegalAccessException", e);
175                 } catch (final IllegalArgumentException e) {
176                     LoginHandler.LOG.error("could not execute person key method for system " + system.getName(), e);
177                     throw new EFapsException(LoginHandler.class, "IllegalArgumentException", e);
178                 } catch (final InvocationTargetException e) {
179                     LoginHandler.LOG.error("could not execute person key method for system " + system.getName(), e);
180                     throw new EFapsException(LoginHandler.class, "InvocationTargetException", e);
181                 }
182             }
183         }
184         return person;
185     }
186 
187     /**
188      * The person represented in the JAAS login context is created and
189      * associated to eFaps. If the person is defined in more than one JAAS
190      * system, the person is also associated to the other JAAS systems.
191      *
192      * @param _login    JAAS login context
193      * @return Java instance of newly created person
194      * @throws EFapsException if a method of the principals inside the JAAS
195      *                        login contexts could not be executed.
196      */
197     protected Person createPerson(final LoginContext _login)
198         throws EFapsException
199     {
200         Person person = null;
201 
202         for (final JAASSystem system : JAASSystem.getAllJAASSystems()) {
203             final Set<?> users = _login.getSubject().getPrincipals(system.getPersonJAASPrincipleClass());
204             for (final Object persObj : users) {
205                 try {
206                     final String persKey = (String) system.getPersonMethodKey().invoke(persObj);
207                     final String persName = (String) system.getPersonMethodName().invoke(persObj);
208 
209                     if (person == null) {
210                         person = Person.createPerson(system, persKey, persName);
211                     } else {
212                         person.assignToJAASSystem(system, persKey);
213                     }
214                 } catch (final IllegalAccessException e) {
215                     LoginHandler.LOG.error("could not execute a person method for system " + system.getName(), e);
216                     throw new EFapsException(LoginHandler.class, "IllegalAccessException", e);
217                 } catch (final IllegalArgumentException e) {
218                     LoginHandler.LOG.error("could not execute a person method for system " + system.getName(), e);
219                     throw new EFapsException(LoginHandler.class, "IllegalArgumentException", e);
220                 } catch (final InvocationTargetException e) {
221                     LoginHandler.LOG.error("could not execute a person method for system " + system.getName(), e);
222                     throw new EFapsException(LoginHandler.class, "InvocationTargetException", e);
223                 }
224             }
225         }
226         return person;
227     }
228 
229     /**
230      * The person information inside eFaps is update with information from JAAS
231      * login context.
232      *
233      * @param _login    JAAS login context
234      * @param _person   Java person instance inside eFaps to update
235      * @throws EFapsException if a method of the principals inside the JAAS
236      *                        login contexts could not be executed or the
237      *                        person could not be updated from the values in
238      *                        the JAAS login context.
239      */
240     protected void updatePerson(final LoginContext _login,
241                                 final Person _person)
242         throws EFapsException
243     {
244         for (final JAASSystem system : JAASSystem.getAllJAASSystems()) {
245             final Set<?> users = _login.getSubject().getPrincipals(system.getPersonJAASPrincipleClass());
246             for (final Object persObj : users) {
247                 try {
248                     for (final Map.Entry<Person.AttrName, Method> entry
249                             : system.getPersonMethodAttributes().entrySet()) {
250                         _person.updateAttrValue(entry.getKey(), (String) entry.getValue().invoke(persObj));
251                     }
252                 } catch (final IllegalAccessException e) {
253                     LoginHandler.LOG.error("could not execute a person method for system " + system.getName(), e);
254                     throw new EFapsException(LoginHandler.class, "IllegalAccessException", e);
255                 } catch (final IllegalArgumentException e) {
256                     LoginHandler.LOG.error("could not execute a person method for system " + system.getName(), e);
257                     throw new EFapsException(LoginHandler.class, "IllegalArgumentException", e);
258                 } catch (final InvocationTargetException e) {
259                     LoginHandler.LOG.error("could not execute a person method for system " + system.getName(), e);
260                     throw new EFapsException(LoginHandler.class, "InvocationTargetException", e);
261                 }
262             }
263         }
264         _person.commitAttrValuesInDB();
265     }
266 
267     /**
268      * The roles of the given person are updated with the information from the
269      * JAAS login context.
270      *
271      * @param _login    JAAS login context
272      * @param _person   person for which the roles must be updated
273      * @throws EFapsException if a method of the principals inside the JAAS
274      *                        login contexts could not be executed or the roles
275      *                        for the given person could not be set.
276      */
277     protected void updateRoles(final LoginContext _login,
278                                final Person _person)
279         throws EFapsException
280     {
281         for (final JAASSystem system : JAASSystem.getAllJAASSystems()) {
282             if (system.getRoleJAASPrincipleClass() != null) {
283                 final Set<?> rolesJaas = _login.getSubject().getPrincipals(system.getRoleJAASPrincipleClass());
284                 final Set<Role> rolesEfaps = new HashSet<Role>();
285                 for (final Object roleObj : rolesJaas) {
286                     try {
287                         final String roleKey = (String) system.getRoleMethodKey().invoke(roleObj);
288                         final Role roleEfaps = Role.getWithJAASKey(system, roleKey);
289                         if (roleEfaps != null) {
290                             rolesEfaps.add(roleEfaps);
291                         }
292                     } catch (final IllegalAccessException e) {
293                         LoginHandler.LOG.error("could not execute role key method for system " + system.getName(), e);
294                     } catch (final IllegalArgumentException e) {
295                         LoginHandler.LOG.error("could not execute role key method for system " + system.getName(), e);
296                     } catch (final InvocationTargetException e) {
297                         LoginHandler.LOG.error("could not execute role key method for system " + system.getName(), e);
298                     }
299                 }
300                 _person.setRoles(system, rolesEfaps);
301             }
302         }
303     }
304 
305     /**
306      * The groups of the given person are updated with the information from the
307      * JAAS login context.
308      *
309      * @param _login    JAAS login context
310      * @param _person   person for which the groups must be updated
311      * @throws EFapsException if a method of the principals inside the JAAS
312      *                        login contexts could not be executed or the
313      *                        groups for the given person could not be set.
314      */
315     protected void updateGroups(final LoginContext _login,
316                                 final Person _person)
317         throws EFapsException
318     {
319         for (final JAASSystem system : JAASSystem.getAllJAASSystems()) {
320             if (system.getGroupJAASPrincipleClass() != null) {
321                 final Set<?> groupsJaas = _login.getSubject().getPrincipals(system.getGroupJAASPrincipleClass());
322                 final Set<Group> groupsEfaps = new HashSet<Group>();
323                 for (final Object groupObj : groupsJaas) {
324                     try {
325                         final String groupKey = (String) system.getGroupMethodKey().invoke(groupObj);
326                         final Group groupEfaps = Group.getWithJAASKey(system, groupKey);
327                         if (groupEfaps != null) {
328                             groupsEfaps.add(groupEfaps);
329                         }
330                     } catch (final IllegalAccessException e) {
331                         LoginHandler.LOG.error("could not execute group key method for system " + system.getName(), e);
332                     } catch (final IllegalArgumentException e) {
333                         LoginHandler.LOG.error("could not execute group key method for system " + system.getName(), e);
334                     } catch (final InvocationTargetException e) {
335                         LoginHandler.LOG.error("could not execute group key method for system " + system.getName(), e);
336                     }
337                 }
338                 _person.setGroups(system, groupsEfaps);
339             }
340         }
341     }
342 
343     /**
344      * The groups of the given person are updated with the information from the
345      * JAAS login context.
346      * TODO: no real check or update is done
347      * @param _login    JAAS login context
348      * @param _person   person for which the groups must be updated
349      * @throws EFapsException if a method of the principals inside the JAAS
350      *                        login contexts could not be executed or the
351      *                        groups for the given person could not be set.
352      */
353     protected void updateCompanies(final LoginContext _login,
354                                    final Person _person)
355         throws EFapsException
356     {
357         if (!JAASSystem.getAllJAASSystems().isEmpty()) {
358             _person.setCompanies(JAASSystem.getAllJAASSystems().iterator().next(), _person.getCompaniesFromDB(null));
359         }
360     }
361 
362     /**
363      * This is the getter method for instance variable {@link #applicationName}.
364      *
365      * @return the value of the instance variable {@link #applicationName}.
366      * @see #applicationName
367      */
368     public String getApplicationName()
369     {
370         return this.applicationName;
371     }
372 
373     /**
374      * Class used to handle the call to the JAAS login handler. It's used to
375      * return the name and password on request from the implementing login
376      * modules.
377      */
378     protected class LoginCallbackHandler
379         implements CallbackHandler
380     {
381         /**
382          * The user name to test is stored in this instance variable.
383          */
384         private final String name;
385 
386         /**
387          * The password used from the user is stored in this instance variable.
388          */
389         private final String password;
390 
391         /**
392          * The action mode for which the login must be made is stored in this
393          * instance variable (e.g. login, information about all persons, etc.)
394          */
395         private final ActionCallback.Mode mode;
396 
397         /**
398          * Constructor initializing the action, name and password in this call
399          * back handler.
400          *
401          * @param _mode     defines mode for which the login is made
402          * @param _name     name of the login user
403          * @param _passwd   password of the login user
404          * @see #mode
405          * @see #name
406          * @see #password
407          */
408         protected LoginCallbackHandler(final ActionCallback.Mode _mode,
409                                        final String _name,
410                                        final String _passwd)
411         {
412             this.mode = _mode;
413             this.name = _name;
414             this.password = _passwd;
415         }
416 
417         /**
418          * The handler sets for instances of {@link NameCallback} the given
419          * {@link #name} and for instances of {@link PasswordCallback} the given
420          * {@link #password}. {@link TextOutputCallback} instances are ignored.
421          *
422          * @param _callbacks    callback instances to handle
423          * @throws UnsupportedCallbackException for all {@link Callback}
424          *                  instances which are not {@link NameCallback},
425          *                  {@link PasswordCallback} or
426          *                  {@link TextOutputCallback}
427          */
428         public void handle(final Callback[] _callbacks)
429             throws UnsupportedCallbackException
430         {
431             for (int i = 0; i < _callbacks.length; i++) {
432                 if (_callbacks[i] instanceof ActionCallback) {
433                     final ActionCallback ac = (ActionCallback) _callbacks[i];
434                     ac.setMode(this.mode);
435                 } else if (_callbacks[i] instanceof NameCallback) {
436                     final NameCallback nc = (NameCallback) _callbacks[i];
437                     nc.setName(this.name);
438                 } else if (_callbacks[i] instanceof PasswordCallback) {
439                     if (this.password != null) {
440                         final PasswordCallback pc = (PasswordCallback) _callbacks[i];
441                         pc.setPassword(this.password.toCharArray());
442                     }
443                 } else if (!(_callbacks[i] instanceof TextOutputCallback)) {
444                     throw new UnsupportedCallbackException(_callbacks[i], "Unrecognized Callback");
445                 }
446             }
447         }
448     }
449 }