001/* 
002    Licensed to the Apache Software Foundation (ASF) under one
003    or more contributor license agreements.  See the NOTICE file
004    distributed with this work for additional information
005    regarding copyright ownership.  The ASF licenses this file
006    to you under the Apache License, Version 2.0 (the
007    "License"); you may not use this file except in compliance
008    with the License.  You may obtain a copy of the License at
009
010       http://www.apache.org/licenses/LICENSE-2.0
011
012    Unless required by applicable law or agreed to in writing,
013    software distributed under the License is distributed on an
014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015    KIND, either express or implied.  See the License for the
016    specific language governing permissions and limitations
017    under the License.  
018 */
019package org.apache.wiki.auth.user;
020
021import org.apache.commons.lang3.StringUtils;
022
023import jakarta.servlet.http.HttpServletRequest;
024import java.io.Serializable;
025import java.util.ArrayList;
026import java.util.Date;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030
031
032/**
033 * Default implementation for representing wiki user information, such as the login name, full name, wiki name, and e-mail address.
034 *
035 * @since 2.3
036 */
037public final class DefaultUserProfile implements UserProfile {
038
039    private static final long serialVersionUID = -5600466893735300647L;
040    private static final String EMPTY_STRING = "";
041    private static final String WHITESPACE = "\\s";
042
043    private final Map< String, Serializable > attributes = new HashMap<>();
044    private Date created;
045    private String email;
046    private String fullname;
047    private Date lockExpiry;
048    private String loginName;
049    private Date modified;
050    private String password;
051    private String uid;
052    private String wikiname;
053    //oldest should be in position zero, size bounded via configuration
054    private List<String> previousHashedCredentials = new ArrayList<>();
055
056    public List<String> getPreviousHashedCredentials() {
057        return previousHashedCredentials;
058    }
059
060    /**
061     * Package constructor to allow direct instantiation only from package related classes (i.e., AbstractUserDatabase).
062     */
063    DefaultUserProfile() {}
064
065    /**
066     * {@inheritDoc}
067     */
068    @Override
069    public boolean equals( final Object o ) {
070        if ( o instanceof UserProfile ) {
071            final DefaultUserProfile u = ( DefaultUserProfile )o;
072            return  same( fullname, u.fullname ) &&
073                    //same( password, u.password ) &&
074                    //Note: this used to compare the password for some reason, but that was causing
075                    //issues when the user wanted to change their password. since this is called
076                    //from DefaultUserManager#setProfile
077                    same( loginName, u.loginName ) &&
078                    same( StringUtils.lowerCase( email ), StringUtils.lowerCase( u.email ) ) &&
079                    same( wikiname, u.wikiname );
080        }
081
082        return false;
083    }
084
085    @Override
086    public int hashCode() {
087        return ( fullname  != null ? fullname.hashCode()  : 0 ) ^
088               ( password  != null ? password.hashCode()  : 0 ) ^
089               ( loginName != null ? loginName.hashCode() : 0 ) ^
090               ( wikiname  != null ? wikiname.hashCode()  : 0 ) ^
091               ( email     != null ? StringUtils.lowerCase( email ).hashCode() : 0 );
092    }
093
094    /**
095     * Returns the creation date
096     *
097     * @return the creation date
098     * @see org.apache.wiki.auth.user.UserProfile#getCreated()
099     */
100    @Override
101    public Date getCreated()
102    {
103        return created;
104    }
105
106    /**
107     * Returns the user's e-mail address.
108     *
109     * @return the e-mail address
110     */
111    @Override
112    public String getEmail()
113    {
114        return email;
115    }
116
117    /**
118     * Returns the user's full name.
119     *
120     * @return the full name
121     */
122    @Override
123    public String getFullname()
124    {
125        return fullname;
126    }
127
128    /**
129     * Returns the last-modified date.
130     *
131     * @return the last-modified date
132     * @see org.apache.wiki.auth.user.UserProfile#getLastModified()
133     */
134    @Override
135    public Date getLastModified()
136    {
137        return modified;
138    }
139
140    /**
141     * Returns the user's login name.
142     * @return the login name
143     */
144    @Override
145    public String getLoginName()
146    {
147        return loginName;
148    }
149
150    /**
151     * Returns the user password for use with custom authentication. Note that the password field is not meaningful for container
152     * authentication; the user's private credentials are generally stored elsewhere. While it depends on the {@link UserDatabase}
153     * implementation, in most cases the value returned by this method will be a password hash, not the password itself.
154     *
155     * @return the password
156     */
157    @Override
158    public String getPassword()
159    {
160        return password;
161    }
162
163    /**
164     * Returns the user's wiki name.
165     *
166     * @return the wiki name.
167     */
168    @Override
169    public String getWikiName()
170    {
171        return wikiname;
172    }
173
174    /**
175     * Returns <code>true</code> if the user profile is new. This implementation checks whether {@link #getLastModified()} returns
176     * <code>null</code> to determine the status.
177     *
178     * @see org.apache.wiki.auth.user.UserProfile#isNew()
179     */
180    @Override
181    public boolean isNew()
182    {
183        return  modified == null;
184    }
185
186    /**
187     * @param date the creation date
188     * @see org.apache.wiki.auth.user.UserProfile#setCreated(java.util.Date)
189     */
190    @Override
191    public void setCreated( final Date date )
192    {
193        created = date;
194    }
195
196    /**
197     * Sets the user's e-mail address.
198     *
199     * @param email the e-mail address
200     */
201    @Override
202    public void setEmail( final String email )
203    {
204        this.email = email;
205    }
206
207    /**
208     * Sets the user's full name. For example, "Janne Jalkanen."
209     *
210     * @param arg the full name
211     */
212    @Override
213    public void setFullname( final String arg ) {
214        fullname = arg;
215
216        // Compute wiki name
217        if ( fullname != null ) {
218            wikiname = fullname.replaceAll( WHITESPACE, EMPTY_STRING );
219        }
220    }
221
222    /**
223     * Sets the last-modified date.
224     *
225     * @param date the last-modified date
226     * @see org.apache.wiki.auth.user.UserProfile#setLastModified(java.util.Date)
227     */
228    @Override
229    public void setLastModified( final Date date )
230    {
231        modified = date;
232    }
233
234    /**
235     * Sets the name by which the user logs in. The login name is used as the username for custom authentication (see
236     * {@link org.apache.wiki.auth.AuthenticationManager#login(org.apache.wiki.api.core.Session,HttpServletRequest, String, String)}).
237     * The login name is typically a short name ("jannej"). In contrast, the wiki name is typically of type
238     * FirstnameLastName ("JanneJalkanen").
239     *
240     * @param name the login name
241     */
242    @Override
243    public void setLoginName( final String name )
244    {
245        loginName = name;
246    }
247
248    /**
249     * Sets the user's password for use with custom authentication. It is <em>not</em> the responsibility of implementing classes to hash
250     * the password; that responsibility is borne by the UserDatabase implementation during save operations (see
251     * {@link UserDatabase#save(UserProfile)}). Note that the password field is not meaningful for container authentication; the user's
252     * private credentials are generally stored elsewhere.
253     *
254     * @param arg the password
255     */
256    @Override
257    public void setPassword( final String arg )
258    {
259        password = arg;
260    }
261
262    /**
263     * Returns a string representation of this user profile.
264     *
265     * @return the string
266     */
267    @Override
268    public String toString()
269    {
270        return "[DefaultUserProfile: '" + getFullname() + "']";
271    }
272
273    /**
274     * Private method that compares two objects and determines whether they are equal. Two nulls are considered equal.
275     *
276     * @param arg1 the first object
277     * @param arg2 the second object
278     * @return the result of the comparison
279     */
280    private boolean same( final Object arg1, final Object arg2 ) {
281        if( arg1 == null && arg2 == null ) {
282            return true;
283        }
284        if( arg1 == null || arg2 == null ) {
285            return false;
286        }
287        return arg1.equals( arg2 );
288    }
289
290    //--------------------------- Attribute and lock interface implementations ---------------------------
291    
292    /**
293     * {@inheritDoc}
294     */
295    @Override
296    public Map< String, Serializable > getAttributes()
297    {
298        return attributes;
299    }
300
301    /**
302     * {@inheritDoc}
303     */
304    @Override
305    public Date getLockExpiry()
306    {
307        return isLocked() ? lockExpiry : null;
308    }
309    
310    /**
311     * {@inheritDoc}
312     */
313    @Override
314    public String getUid()
315    {
316        return uid;
317    }
318
319    /**
320     * {@inheritDoc}
321     */
322    @Override
323    public boolean isLocked() {
324        final boolean locked = lockExpiry != null && System.currentTimeMillis() < lockExpiry.getTime();
325        // Clear the lock if it's expired already
326        if( !locked && lockExpiry != null ) {
327            lockExpiry = null;
328        }
329        return locked;
330
331    }
332
333    /**
334     * {@inheritDoc}
335     */
336    @Override
337    public void setLockExpiry( final Date expiry )
338    {
339        this.lockExpiry = expiry;
340    }
341    
342    /**
343     * {@inheritDoc}
344     */
345    @Override
346    public void setUid( final String uid )
347    {
348        this.uid = uid;
349    }
350
351}