001package org.apache.fulcrum.security.spi;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021import org.apache.commons.lang3.StringUtils;
022import org.apache.fulcrum.security.UserManager;
023import org.apache.fulcrum.security.acl.AccessControlList;
024import org.apache.fulcrum.security.authenticator.Authenticator;
025import org.apache.fulcrum.security.entity.User;
026import org.apache.fulcrum.security.model.ACLFactory;
027import org.apache.fulcrum.security.util.DataBackendException;
028import org.apache.fulcrum.security.util.EntityExistsException;
029import org.apache.fulcrum.security.util.PasswordMismatchException;
030import org.apache.fulcrum.security.util.UnknownEntityException;
031
032/**
033 * This implementation keeps all objects in memory. This is mostly meant to help
034 * with testing and prototyping of ideas.
035 * 
036 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
037 * @version $Id$
038 */
039
040// TODO Need to load up Crypto component and actually encrypt passwords!
041
042public abstract class AbstractUserManager extends AbstractEntityManager implements UserManager
043{
044    /** ID **/
045    private static final long serialVersionUID = 1L;
046
047    /**
048     * @param user user to persist
049     * @param <T> User type
050     * @return a User object
051     * @throws DataBackendException if fail to connect
052     */
053    protected abstract <T extends User> T persistNewUser(T user) throws DataBackendException;
054
055    private ACLFactory aclFactory;
056    private Authenticator authenticator;
057
058    /* (non-Javadoc)
059     * @see org.apache.fulcrum.security.UserManager#getACL(org.apache.fulcrum.security.entity.User)
060     */
061    @Override
062        public <T extends AccessControlList> T getACL(User user) throws UnknownEntityException
063    {
064        return getACLFactory().getAccessControlList(user);
065    }
066
067    /**
068     * Check whether a specified user's account exists.
069     *
070     * The login name is used for looking up the account.
071     *
072     * @param user
073     *            The user to be checked.
074     * @return true if the specified account exists
075     * @throws DataBackendException
076     *             if there was an error accessing the data backend.
077     */
078    @Override
079        public boolean checkExists(User user) throws DataBackendException
080    {
081        return checkExists(user.getName());
082    }
083
084    /**
085     * Retrieve a user from persistent storage using username as the key, and
086     * authenticate the user. The implementation may chose to authenticate to
087     * the server as the user whose data is being retrieved.
088     *
089     * @param userName
090     *            the name of the user.
091     * @param password
092     *            the user supplied password.
093     * @return an User object.
094     * @exception PasswordMismatchException
095     *                if the supplied password was incorrect.
096     * @exception UnknownEntityException
097     *                if the user's account does not exist in the database.
098     * @exception DataBackendException
099     *                if there is a problem accessing the storage.
100     */
101    @Override
102        public <T extends User> T getUser(String userName, String password) throws PasswordMismatchException, UnknownEntityException, DataBackendException
103    {
104        T user = getUser(userName);
105        authenticate(user, password);
106        return user;
107    }
108
109    @Override
110        public <T extends User> T getUser(String name) throws DataBackendException, UnknownEntityException
111    {
112        @SuppressWarnings("unchecked")
113                T user = (T)getAllUsers().getByName(name);
114        if (user == null)
115        {
116            throw new UnknownEntityException("The specified user does not exist");
117        }
118        return user;
119    }
120
121    /**
122     * Retrieve a User object with specified Id.
123     *
124     * @param id
125     *            the id of the User.
126     *
127     * @return an object representing the User with specified id.
128     *
129     * @throws UnknownEntityException
130     *             if the user does not exist in the database.
131     * @throws DataBackendException
132     *             if there is a problem accessing the storage.
133     */
134    @Override
135        public <T extends User> T getUserById(Object id) throws DataBackendException, UnknownEntityException
136    {
137        @SuppressWarnings("unchecked")
138                T user = (T)getAllUsers().getById(id);
139        if (user == null)
140        {
141            throw new UnknownEntityException("The specified user does not exist");
142        }
143        return user;
144    }
145
146    /**
147     * Authenticate an User with the specified password. If authentication is
148     * successful the method returns nothing. If there are any problems,
149     * exception was thrown.
150     *
151     * @param user
152     *            an User object to authenticate.
153     * @param password
154     *            the user supplied password.
155     * @exception PasswordMismatchException
156     *                if the supplied password was incorrect.
157     * @exception UnknownEntityException
158     *                if the user's account does not exist in the database.
159     * @exception DataBackendException
160     *                if there is a problem accessing the storage.
161     */
162    @Override
163        public void authenticate(User user, String password) throws PasswordMismatchException, UnknownEntityException, DataBackendException
164    {
165        if (authenticator == null)
166        {
167            authenticator = (Authenticator) resolve(Authenticator.ROLE);
168
169        }
170        if (!authenticator.authenticate(user, password))
171        {
172            throw new PasswordMismatchException("Can not authenticate user.");
173        }
174    }
175
176    /**
177     * Change the password for an User. The user must have supplied the old
178     * password to allow the change.
179     *
180     * @param user
181     *            an User to change password for.
182     * @param oldPassword
183     *            The old password to verify
184     * @param newPassword
185     *            The new password to set
186     * @exception PasswordMismatchException
187     *                if the supplied password was incorrect.
188     * @exception UnknownEntityException
189     *                if the user's account does not exist in the database.
190     * @exception DataBackendException
191     *                if there is a problem accessing the storage.
192     */
193    @Override
194        public void changePassword(User user, String oldPassword, String newPassword) throws PasswordMismatchException, UnknownEntityException,
195            DataBackendException
196    {
197        if (!checkExists(user))
198        {
199            throw new UnknownEntityException("The account '" + user.getName() + "' does not exist");
200        }
201        if (!oldPassword.equals(user.getPassword()))
202        {
203            throw new PasswordMismatchException("The supplied old password for '" + user.getName() + "' was incorrect");
204        }
205        user.setPassword(newPassword);
206        // save the changes in the database immediately, to prevent the password
207        // being 'reverted' to the old value if the user data is lost somehow
208        // before it is saved at session's expiry.
209        saveUser(user);
210    }
211
212    /**
213     * Forcibly sets new password for an User.
214     *
215     * This is supposed by the administrator to change the forgotten or
216     * compromised passwords. Certain implementatations of this feature would
217     * require administrative level access to the authenticating server /
218     * program.
219     *
220     * @param user
221     *            an User to change password for.
222     * @param password
223     *            the new password.
224     * @exception UnknownEntityException
225     *                if the user's record does not exist in the database.
226     * @exception DataBackendException
227     *                if there is a problem accessing the storage.
228     */
229    @Override
230        public void forcePassword(User user, String password) throws UnknownEntityException, DataBackendException
231    {
232        if (!checkExists(user))
233        {
234            throw new UnknownEntityException("The account '" + user.getName() + "' does not exist");
235        }
236        user.setPassword(password);
237        // save the changes in the database immediately, to prevent the
238        // password being 'reverted' to the old value if the user data
239        // is lost somehow before it is saved at session's expiry.
240        saveUser(user);
241    }
242
243    /**
244     * Construct a blank User object.
245     *
246     * This method calls getUserClass, and then creates a new object using the
247     * default constructor.
248     *
249     * @return an object implementing User interface.
250     * @throws DataBackendException
251     *             if the object could not be instantiated.
252     */
253    @Override
254        public <T extends User> T getUserInstance() throws DataBackendException
255    {
256        try
257        {
258            @SuppressWarnings("unchecked")
259                        T user = (T) Class.forName(getClassName()).newInstance();
260            return user;
261        }
262        catch (Exception e)
263        {
264            throw new DataBackendException("Problem creating instance of class " + getClassName(), e);
265        }
266    }
267
268    /**
269     * Construct a blank User object.
270     *
271     * This method calls getUserClass, and then creates a new object using the
272     * default constructor.
273     *
274     * @param userName
275     *            The name of the user.
276     *
277     * @return an object implementing User interface.
278     *
279     * @throws DataBackendException
280     *             if the object could not be instantiated.
281     */
282    @Override
283        public <T extends User> T getUserInstance(String userName) throws DataBackendException
284    {
285        T user = getUserInstance();
286        user.setName(userName);
287        return user;
288    }
289
290    /**
291     * Creates new user account with specified attributes.
292     *
293     * @param user
294     *            the object describing account to be created.
295     * @param password
296     *            The password to use for the account.
297     *
298     * @throws DataBackendException
299     *             if there was an error accessing the data backend.
300     * @throws EntityExistsException
301     *             if the user account already exists.
302     */
303    @Override
304        public <T extends User> T addUser(T user, String password) throws DataBackendException, EntityExistsException
305    {
306        if (StringUtils.isEmpty(user.getName()))
307        {
308            throw new DataBackendException("Could not create " + "an user with empty name!");
309        }
310        if (checkExists(user))
311        {
312            throw new EntityExistsException("The account '" + user.getName() + "' already exists");
313        }
314        user.setPassword(password);
315        try
316        {
317            return persistNewUser(user);
318        }
319        catch (Exception e)
320        {
321            throw new DataBackendException("Failed to create account '" + user.getName() + "'", e);
322        }
323    }
324
325    /**
326     * @return Returns the ACLFactory.
327     */
328    public ACLFactory getACLFactory()
329    {
330        if (aclFactory == null)
331        {
332            aclFactory = (ACLFactory) resolve(ACLFactory.ROLE);
333        }
334        return aclFactory;
335    }
336
337}