001package org.apache.turbine.services.security;
002
003
004/*
005 * Licensed to the Apache Software Foundation (ASF) under one
006 * or more contributor license agreements.  See the NOTICE file
007 * distributed with this work for additional information
008 * regarding copyright ownership.  The ASF licenses this file
009 * to you under the Apache License, Version 2.0 (the
010 * "License"); you may not use this file except in compliance
011 * with the License.  You may obtain a copy of the License at
012 *
013 *   http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing,
016 * software distributed under the License is distributed on an
017 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
018 * KIND, either express or implied.  See the License for the
019 * specific language governing permissions and limitations
020 * under the License.
021 */
022
023
024import java.util.List;
025import java.util.stream.Collectors;
026
027import org.apache.commons.configuration2.Configuration;
028import org.apache.fulcrum.factory.FactoryService;
029import org.apache.fulcrum.security.acl.AccessControlList;
030import org.apache.fulcrum.security.model.turbine.TurbineUserManager;
031import org.apache.fulcrum.security.model.turbine.entity.TurbineUser;
032import org.apache.fulcrum.security.util.DataBackendException;
033import org.apache.fulcrum.security.util.EntityExistsException;
034import org.apache.fulcrum.security.util.PasswordMismatchException;
035import org.apache.fulcrum.security.util.UnknownEntityException;
036import org.apache.fulcrum.security.util.UserSet;
037import org.apache.logging.log4j.LogManager;
038import org.apache.logging.log4j.Logger;
039import org.apache.turbine.om.security.TurbineUserDelegate;
040import org.apache.turbine.om.security.User;
041import org.apache.turbine.services.InitializationException;
042import org.apache.turbine.services.ServiceManager;
043import org.apache.turbine.services.TurbineServices;
044import org.apache.turbine.util.ObjectUtils;
045
046/**
047 * Default user manager.
048 *
049 * The user manager wraps Fulcrum security user objects into
050 * Turbine-specific ones.
051 *
052 *
053 * <ol>
054 * <li>either in a method with the same name (and very similar signature)</li>
055 * <li>or mapped to method names as listed below:
056 *
057 * <ul>
058 * <li>method(s) in this manager -&gt; Fulcrum manager method(s)
059 * <li>{@link #createAccount(User, String)}createAccount -&gt; addUser(User, String)
060 * <li>{@link #removeAccount(User)} -&gt; removeUser(User)
061 * <li>{@link #store(User)} -&gt; saveUser(User)
062 * <li>{@link #retrieve(String)} and {@link #retrieve(String, String)} -&gt; getUser(String), getUser(String, String)
063 * <li>{@link #retrieveList(Object)} -&gt; getAllUsers()
064 * <li>{@link #accountExists(String)}, {@link #accountExists(User)} -&gt; checkExists(String), checkExists(User)
065 * </ul>
066 *
067 * </li>
068 * </ol>
069 *
070 * In this way all public methods of Fulcrum {@link TurbineUserManager} interface are used by reference of the Fulcrum delegate {@link #umDelegate}
071 * and wrapped by this manager.
072 *
073 * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
074 * @version $Id: PassiveUserManager.java 1096130 2011-04-23 10:37:19Z ludwig $
075 */
076public class DefaultUserManager implements UserManager
077{
078    /** Fulcrum user manager instance to delegate to */
079    private TurbineUserManager umDelegate = null;
080
081    private FactoryService factoryService = null;
082
083    /** The user class, which the UserManager uses as wrapper for Fulcrum {@link TurbineUser} */
084    private String userWrapperClass;
085
086
087    /** Logging */
088    private static final Logger log = LogManager.getLogger(DefaultUserManager.class);
089
090    /**
091     * Wrap a Fulcrum user object into a Turbine user object
092     *
093     * @param <U> user class
094     * @param user the user object to delegate to
095     *
096     * @return the wrapped object
097     */
098    protected <U extends User> U wrap(TurbineUser user)
099    {
100        @SuppressWarnings("unchecked")
101        U u = (U) getUserWrapper(user);
102        return u;
103    }
104
105    /**
106     * Exception could be ignored, as it is tested before in {@link #init(Configuration)}.
107     *
108     * @param <U> user class
109     * @param user the user object to wrap
110     * @return instance extending {@link User}
111     */
112    @SuppressWarnings("unchecked")
113        public <U extends User> U getUserWrapper(TurbineUser user)
114    {
115                try
116                {
117            Object params[] = new Object[] { user };
118            String signature[] = new String[] { TurbineUser.class.getName() };
119            return (U) factoryService.getInstance(getUserWrapperClass(), params, signature);
120                }
121                catch (Exception e)
122                {
123                        log.error("after init/late instantiation exception", e);
124                        return null; // (U)new DefaultUserImpl(user);
125                }
126        }
127
128    /**
129     * Get the wrapper class for user objects
130     *
131     * @return the wrapper class name
132     */
133    public String getUserWrapperClass()
134    {
135                return userWrapperClass;
136        }
137
138    /**
139     * Set the wrapper class for user objects
140     *
141     * @param userWrapperClass2 the wrapper class name
142     */
143    public void setUserWrapperClass(String userWrapperClass2)
144    {
145                userWrapperClass = userWrapperClass2;
146        }
147
148        /**
149     * Initializes the UserManager
150     *
151     * @param conf A Configuration object to init this Manager
152     */
153    @Override
154    public void init(Configuration conf) throws InitializationException
155    {
156        ServiceManager manager = TurbineServices.getInstance();
157        this.umDelegate = (TurbineUserManager)manager.getService(TurbineUserManager.ROLE);
158
159        String userWrapperClass = conf.getString(
160                SecurityService.USER_WRAPPER_KEY,
161                SecurityService.USER_WRAPPER_DEFAULT);
162
163        try
164        {
165                factoryService = (FactoryService)manager.getService(FactoryService.ROLE);
166
167            //  check instantiation
168                // should provide default constructor
169                TurbineUser turbineUser = umDelegate.getUserInstance();
170                                //(TurbineUser) factoryService.getInstance(userClass);
171            Object params[] = new Object[] { turbineUser };
172            String signature[] = new String[] { TurbineUser.class.getName() };
173
174            // Just check if exceptions would occur
175            factoryService.getInstance(userWrapperClass, params, signature);
176
177            this.setUserWrapperClass(userWrapperClass);
178        }
179        catch (Exception e)
180            {
181               throw new InitializationException("Failed to instantiate user wrapper class", e);
182            }
183    }
184
185
186        /**
187     * Check whether a specified user's account exists.
188     *
189     * The login name is used for looking up the account.
190     *
191     * @param user The user to be checked.
192     * @return true if the specified account exists
193     * @throws DataBackendException if there was an error accessing the data backend.
194     */
195    @Override
196    public boolean accountExists(User user)
197            throws DataBackendException
198    {
199        if (user == null) {
200            return false;
201        }
202        return umDelegate.checkExists(user.getUserDelegate());
203    }
204
205    /**
206     * Check whether a specified user's account exists.
207     *
208     * The login name is used for looking up the account.
209     *
210     * @param userName The name of the user to be checked.
211     * @return true if the specified account exists
212     * @throws DataBackendException if there was an error accessing the data backend.
213     */
214    @Override
215    public boolean accountExists(String userName)
216            throws DataBackendException
217    {
218        return umDelegate.checkExists(userName);
219    }
220
221    /**
222     * Retrieve a user from persistent storage using username as the
223     * key.
224     *
225     * @param username the name of the user.
226     * @return an User object.
227     * @throws UnknownEntityException if the user's record does not
228     *            exist in the database.
229     * @throws DataBackendException if there is a problem accessing the
230     *            storage.
231     */
232    @Override
233    public <U extends User> U retrieve(String username)
234            throws UnknownEntityException, DataBackendException
235    {
236        TurbineUser u = umDelegate.getUser(username);
237        return wrap(u);
238    }
239
240    /**
241     * Retrieve a set of users that meet the specified criteria.
242     *
243     * As the keys for the criteria, you should use the constants that
244     * are defined in {@link User} interface, plus the names
245     * of the custom attributes you added to your user representation
246     * in the data storage. Use verbatim names of the attributes -
247     * without table name prefix in case of DB implementation.
248     *
249     * @param criteria The criteria of selection.
250     * @return a List of users meeting the criteria.
251     * @throws DataBackendException if there is a problem accessing the
252     *         storage.
253     */
254    @Override
255    public List<? extends User> retrieveList(Object criteria)
256            throws DataBackendException
257    {
258        UserSet<org.apache.fulcrum.security.entity.User> uset = umDelegate.getAllUsers();
259
260        List<User> userList = uset.stream()
261                .map(u -> (TurbineUser) u)
262                .map(this::wrap)
263                .map(u -> (User)u)
264                .collect(Collectors.toList());
265
266        return userList;
267    }
268
269    /**
270     * Retrieve a user from persistent storage using username as the
271     * key, and authenticate the user. The implementation may chose
272     * to authenticate to the server as the user whose data is being
273     * retrieved.
274     *
275     * @param username the name of the user.
276     * @param password the user supplied password.
277     * @return an User object.
278     * @throws PasswordMismatchException if the supplied password was
279     *            incorrect.
280     * @throws UnknownEntityException if the user's record does not
281     *            exist in the database.
282     * @throws DataBackendException if there is a problem accessing the
283     *            storage.
284     */
285    @Override
286    public <U extends User> U retrieve(String username, String password)
287            throws PasswordMismatchException, UnknownEntityException,
288            DataBackendException
289    {
290        TurbineUser u = umDelegate.getUser(username, password);
291        return wrap(u);
292    }
293
294    /**
295     * Save an User object to persistent storage. User's record is
296     * required to exist in the storage.
297     *
298     * @param user an User object to store.
299     * @throws UnknownEntityException if the user's record does not
300     *            exist in the database.
301     * @throws DataBackendException if there is a problem accessing the
302     *            storage.
303     */
304    @Override
305    public void store(User user)
306            throws UnknownEntityException, DataBackendException
307    {
308        if (user == null) {
309            throw new UnknownEntityException("user is null");
310        }
311        try
312        {
313            user.setObjectdata(ObjectUtils.serializeMap(user.getPermStorage()));
314        }
315        catch (Exception e)
316        {
317            throw new DataBackendException("Could not serialize permanent storage", e);
318        }
319
320        umDelegate.saveUser(((TurbineUserDelegate)user).getUserDelegate());
321    }
322
323    /**
324     * Saves User data when the session is unbound. The user account is required
325     * to exist in the storage.
326     *
327     * LastLogin, AccessCounter, persistent pull tools, and any data stored
328     * in the permData hashtable that is not mapped to a column will be saved.
329     *
330     * @throws UnknownEntityException if the user's account does not
331     *            exist in the database.
332     * @throws DataBackendException if there is a problem accessing the
333     *            storage.
334     */
335    @Override
336    public void saveOnSessionUnbind(User user)
337            throws UnknownEntityException, DataBackendException
338    {
339        store(user);
340    }
341
342    /**
343     * Authenticate an User with the specified password. If authentication
344     * is successful the method returns nothing. If there are any problems,
345     * exception was thrown.
346     *
347     * @param user an User object to authenticate.
348     * @param password the user supplied password.
349     * @throws PasswordMismatchException if the supplied password was
350     *            incorrect.
351     * @throws UnknownEntityException if the user's record does not
352     *            exist in the database.
353     * @throws DataBackendException if there is a problem accessing the
354     *            storage.
355     */
356    @Override
357    public void authenticate(User user, String password)
358            throws PasswordMismatchException, UnknownEntityException,
359            DataBackendException
360    {
361        umDelegate.authenticate(user, password);
362    }
363
364    /**
365     * Creates new user account with specified attributes.
366     *
367     * @param user the object describing account to be created.
368     * @param initialPassword The password to use for the object creation
369     *
370     * @throws DataBackendException if there was an error accessing the data backend.
371     * @throws EntityExistsException if the user account already exists.
372     */
373    @Override
374    public void createAccount(User user, String initialPassword)
375            throws UnknownEntityException, EntityExistsException, DataBackendException
376    {
377        if (user == null) {
378            throw new UnknownEntityException("user is null");
379        }
380        umDelegate.addUser(user.getUserDelegate(), initialPassword);
381    }
382
383    /**
384     * Removes an user account from the system.
385     *
386     * @param user the object describing the account to be removed.
387     * @throws DataBackendException if there was an error accessing the data backend.
388     * @throws UnknownEntityException if the user account is not present.
389     */
390    @Override
391    public void removeAccount(User user)
392            throws UnknownEntityException, DataBackendException
393    {
394        if (user == null) {
395            throw new UnknownEntityException("user is null");
396        }
397        umDelegate.removeUser(user.getUserDelegate());
398    }
399
400    /**
401     * Change the password for an User.
402     *
403     * @param user an User to change password for.
404     * @param oldPassword the current password supplied by the user.
405     * @param newPassword the current password requested by the user.
406     * @throws PasswordMismatchException if the supplied password was
407     *            incorrect.
408     * @throws UnknownEntityException if the user's record does not
409     *            exist in the database.
410     * @throws DataBackendException if there is a problem accessing the
411     *            storage.
412     */
413    @Override
414    public void changePassword(User user, String oldPassword,
415                               String newPassword)
416            throws PasswordMismatchException, UnknownEntityException,
417            DataBackendException
418    {
419        if (user == null) {
420            throw new UnknownEntityException("user is null");
421        }
422        umDelegate.changePassword(
423                ((TurbineUserDelegate)user).getUserDelegate(),
424                oldPassword, newPassword);
425    }
426
427    /**
428     * Forcibly sets new password for an User.
429     *
430     * This is supposed by the administrator to change the forgotten or
431     * compromised passwords. Certain implementations of this feature
432     * would require administrative level access to the authenticating
433     * server / program.
434     *
435     * @param user an User to change password for.
436     * @param password the new password.
437     * @throws UnknownEntityException if the user's record does not
438     *            exist in the database.
439     * @throws DataBackendException if there is a problem accessing the
440     *            storage.
441     */
442    @Override
443    public void forcePassword(User user, String password)
444            throws UnknownEntityException, DataBackendException
445    {
446        if (user == null) {
447            throw new UnknownEntityException("user is null");
448        }
449        umDelegate.forcePassword(user.getUserDelegate(), password);
450    }
451
452    /**
453     * Constructs an User object to represent an anonymous user of the
454     * application.
455     *
456     * @return An anonymous Turbine User.
457     * @throws UnknownEntityException
458     *             if the anonymous User object couldn't be constructed.
459     */
460    @Override
461    public <U extends User> U getAnonymousUser() throws UnknownEntityException
462    {
463        TurbineUser u = umDelegate.getAnonymousUser();
464        return wrap(u);
465    }
466
467    /**
468     * Checks whether a passed user object matches the anonymous user pattern
469     * according to the configured user manager
470     *
471     * @param u a user object
472     *
473     * @return True if this is an anonymous user
474     *
475     */
476    @Override
477    public boolean isAnonymousUser(User u)
478    {
479        return umDelegate.isAnonymousUser(u);
480    }
481
482    /**
483     * Construct a blank User object.
484     *
485     * This method calls getUserClass, and then creates a new object using the
486     * default constructor.
487     *
488     * @return an object implementing User interface.
489     * @throws DataBackendException
490     *             if the object could not be instantiated.
491     */
492    @Override
493    public <U extends User> U getUserInstance() throws DataBackendException
494    {
495        TurbineUser u = umDelegate.getUserInstance();
496        return wrap(u);
497    }
498
499    /**
500     * Construct a blank User object.
501     *
502     * This method calls getUserClass, and then creates a new object using the
503     * default constructor.
504     *
505     * @param userName
506     *            The name of the user.
507     *
508     * @return an object implementing User interface.
509     * @throws DataBackendException
510     *             if the object could not be instantiated.
511     */
512    @Override
513    public <U extends User> U getUserInstance(String userName) throws DataBackendException
514    {
515        TurbineUser u = umDelegate.getUserInstance(userName);
516        return wrap(u);
517    }
518
519    /**
520     * Return a Class object representing the system's chosen implementation of
521     * of ACL interface.
522     *
523     * @return systems's chosen implementation of ACL interface.
524     * @throws UnknownEntityException
525     *             if the implementation of ACL interface could not be
526     *             determined, or does not exist.
527     */
528    @Override
529    public <A extends AccessControlList> A getACL(User user) throws UnknownEntityException
530    {
531        if (user == null) {
532            throw new UnknownEntityException("user is null");
533        }
534        return umDelegate.getACL(user.getUserDelegate());
535    }
536}