1 /*
2  * Copyright (C) 2004-2008 Jive Software. All rights reserved.
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 
17 package org.jivesoftware.openfire.user;
18 
19 import org.jivesoftware.openfire.XMPPServer;
20 import org.jivesoftware.openfire.auth.AuthFactory;
21 import org.jivesoftware.openfire.auth.ConnectionException;
22 import org.jivesoftware.openfire.auth.InternalUnauthenticatedException;
23 import org.jivesoftware.openfire.event.UserEventDispatcher;
24 import org.jivesoftware.openfire.roster.Roster;
25 import org.jivesoftware.util.StringUtils;
26 import org.jivesoftware.util.cache.CacheSizes;
27 import org.jivesoftware.util.cache.Cacheable;
28 import org.jivesoftware.util.cache.CannotCalculateSizeException;
29 import org.jivesoftware.util.cache.ExternalizableUtil;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32 import org.xmpp.resultsetmanagement.Result;
33 
34 import java.io.Externalizable;
35 import java.io.IOException;
36 import java.io.ObjectInput;
37 import java.io.ObjectOutput;
38 import java.util.*;
39 
40 /**
41  * Encapsulates information about a user.
42  *
43  * New users are created using {@link UserManager#createUser(String, String, String, String)}.
44  *
45  * The currently-installed {@link UserProvider} is used for setting all other user data and some operations may not be
46  * supported depending on the capabilities of the {@link UserProvider}.
47  *
48  * All user properties are loaded on demand from the currently-installed
49  * {@link org.jivesoftware.openfire.user.property.UserPropertyProvider}.
50  *
51  * @author Matt Tucker
52  */
53 public class User implements Cacheable, Externalizable, Result {
54 
55     private static final Logger Log = LoggerFactory.getLogger(User.class);
56 
57     // The name of the name visible property
58     private static final String NAME_VISIBLE_PROPERTY = "name.visible";
59     // The name of the email visible property
60     private static final String EMAIL_VISIBLE_PROPERTY = "email.visible";
61 
62     private String username;
63     private String salt;
64     private String storedKey;
65     private String serverKey;
66     private int iterations;
67     private String name;
68     private String email;
69     private Date creationDate;
70     private Date modificationDate;
71 
72     private Map<String,String> properties = null;
73 
74     /**
75      * Returns the value of the specified property for the given username. This method is
76      * an optimization to avoid loading a user to get a specific property.
77      *
78      * @param username the username of the user to get a specific property value.
79      * @param propertyName the name of the property to return its value.
80      * @return the value of the specified property for the given username.
81      * @throws UserNotFoundException Depending on the installed user provider (some will return null instead).
82      */
getPropertyValue(String username, String propertyName)83     public static String getPropertyValue(String username, String propertyName) throws UserNotFoundException {
84         return UserManager.getUserPropertyProvider().loadProperty( username, propertyName );
85     }
86 
87     /**
88      * Constructor added for Externalizable. Do not use this constructor.
89      */
User()90     public User() {
91     }
92 
93     /**
94      * Constructs a new user. Normally, all arguments can be {@code null} except the username.
95      * However, a UserProvider -may- require a name or email address.  In those cases, the
96      * isNameRequired or isEmailRequired UserProvider tests indicate whether {@code null} is allowed.
97      * Typically, User objects should not be constructed by end-users of the API.
98      * Instead, user objects should be retrieved using {@link UserManager#getUser(String)}.
99      *
100      * @param username the username.
101      * @param name the name.
102      * @param email the email address.
103      * @param creationDate the date the user was created.
104      * @param modificationDate the date the user was last modified.
105      */
User(String username, String name, String email, Date creationDate, Date modificationDate)106     public User(String username, String name, String email, Date creationDate,
107             Date modificationDate)
108     {
109         if (username == null) {
110             throw new NullPointerException("Username cannot be null");
111         }
112         this.username = username;
113         if (UserManager.getUserProvider().isNameRequired() && (name == null || "".equals(name.trim()))) {
114             throw new IllegalArgumentException("Invalid or empty name specified with provider that requires name");
115         }
116         this.name = name;
117         if (UserManager.getUserProvider().isEmailRequired() && (email == null || "".equals(email.trim()))) {
118             throw new IllegalArgumentException("Empty email address specified with provider that requires email address. User: "
119                                                 + username + " Email: " + email);
120         }
121         this.email = email;
122         this.creationDate = creationDate;
123         this.modificationDate = modificationDate;
124     }
125 
126     /**
127      * Returns this user's username.
128      *
129      * @return the username..
130      */
getUsername()131     public String getUsername() {
132         return username;
133     }
134 
135     /**
136      * Sets a new password for this user.
137      *
138      * @param password the new password for the user.
139      * @throws UnsupportedOperationException exception
140      */
setPassword(String password)141     public void setPassword(String password) throws UnsupportedOperationException {
142         if (UserManager.getUserProvider().isReadOnly()) {
143             throw new UnsupportedOperationException("User provider is read-only.");
144         }
145 
146         try {
147             AuthFactory.setPassword(username, password);
148 
149             // Fire event.
150             Map<String,Object> params = new HashMap<>();
151             params.put("type", "passwordModified");
152             UserEventDispatcher.dispatchEvent(this, UserEventDispatcher.EventType.user_modified,
153                     params);
154         }
155         catch (UserNotFoundException | ConnectionException | InternalUnauthenticatedException e) {
156             Log.error(e.getMessage(), e);
157         }
158     }
159 
getStoredKey()160     public String getStoredKey() {
161         return storedKey;
162     }
163 
setStoredKey(String storedKey)164     public void setStoredKey(String storedKey) {
165         this.storedKey = storedKey;
166     }
167 
getServerKey()168     public String getServerKey() {
169         return serverKey;
170     }
171 
setServerKey(String serverKey)172     public void setServerKey(String serverKey) {
173         this.serverKey = serverKey;
174     }
175 
getSalt()176     public String getSalt() {
177         return salt;
178     }
179 
setSalt(String salt)180     public void setSalt(String salt) {
181         this.salt = salt;
182     }
183 
getIterations()184     public int getIterations() {
185         return iterations;
186     }
187 
setIterations(int iterations)188     public void setIterations(int iterations) {
189         this.iterations = iterations;
190     }
191 
getName()192     public String getName() {
193         return name == null ? "" : name;
194     }
195 
setName(String name)196     public void setName(String name) {
197         if (UserManager.getUserProvider().isReadOnly()) {
198             throw new UnsupportedOperationException("User provider is read-only.");
199         }
200 
201         if (name != null && name.matches("\\s*")) {
202             name = null;
203         }
204 
205         if (name == null && UserManager.getUserProvider().isNameRequired()) {
206             throw new IllegalArgumentException("User provider requires name.");
207         }
208 
209         try {
210             String originalName = this.name;
211             UserManager.getUserProvider().setName(username, name);
212             this.name = name;
213 
214             // Fire event.
215             Map<String,Object> params = new HashMap<>();
216             params.put("type", "nameModified");
217             params.put("originalValue", originalName);
218             UserEventDispatcher.dispatchEvent(this, UserEventDispatcher.EventType.user_modified,
219                     params);
220         }
221         catch (UserNotFoundException unfe) {
222             Log.error(unfe.getMessage(), unfe);
223         }
224     }
225 
226     /**
227      * Returns true if name is visible to everyone or not.
228      *
229      * @return true if name is visible to everyone, false if not.
230      */
isNameVisible()231     public boolean isNameVisible() {
232         return !getProperties().containsKey(NAME_VISIBLE_PROPERTY) || Boolean.valueOf(getProperties().get(NAME_VISIBLE_PROPERTY));
233     }
234 
235     /**
236      * Sets if name is visible to everyone or not.
237      *
238      * @param visible true if name is visible, false if not.
239      */
setNameVisible(boolean visible)240     public void setNameVisible(boolean visible) {
241         getProperties().put(NAME_VISIBLE_PROPERTY, String.valueOf(visible));
242     }
243 
244     /**
245      * Returns the email address of the user or {@code null} if none is defined.
246      *
247      * @return the email address of the user or null if none is defined.
248      */
getEmail()249     public String getEmail() {
250         return email;
251     }
252 
setEmail(String email)253     public void setEmail(String email) {
254         if (UserManager.getUserProvider().isReadOnly()) {
255             throw new UnsupportedOperationException("User provider is read-only.");
256         }
257 
258         if (email != null && email.matches("\\s*")) {
259             email = null;
260         }
261 
262         if (UserManager.getUserProvider().isEmailRequired() && !StringUtils.isValidEmailAddress(email)) {
263             throw new IllegalArgumentException("User provider requires email address.");
264         }
265 
266         try {
267             String originalEmail= this.email;
268             UserManager.getUserProvider().setEmail(username, email);
269             this.email = email;
270             // Fire event.
271             Map<String,Object> params = new HashMap<>();
272             params.put("type", "emailModified");
273             params.put("originalValue", originalEmail);
274             UserEventDispatcher.dispatchEvent(this, UserEventDispatcher.EventType.user_modified,
275                     params);
276         }
277         catch (UserNotFoundException unfe) {
278             Log.error(unfe.getMessage(), unfe);
279         }
280     }
281 
282     /**
283      * Returns true if email is visible to everyone or not.
284      *
285      * @return true if email is visible to everyone, false if not.
286      */
isEmailVisible()287     public boolean isEmailVisible() {
288         return !getProperties().containsKey(EMAIL_VISIBLE_PROPERTY) || Boolean.valueOf(getProperties().get(EMAIL_VISIBLE_PROPERTY));
289     }
290 
291     /**
292      * Sets if the email is visible to everyone or not.
293      *
294      * @param visible true if the email is visible, false if not.
295      */
setEmailVisible(boolean visible)296     public void setEmailVisible(boolean visible) {
297         getProperties().put(EMAIL_VISIBLE_PROPERTY, String.valueOf(visible));
298     }
299 
getCreationDate()300     public Date getCreationDate() {
301         return creationDate;
302     }
303 
setCreationDate(Date creationDate)304     public void setCreationDate(Date creationDate) {
305         if (UserManager.getUserProvider().isReadOnly()) {
306             throw new UnsupportedOperationException("User provider is read-only.");
307         }
308 
309         try {
310             Date originalCreationDate = this.creationDate;
311             UserManager.getUserProvider().setCreationDate(username, creationDate);
312             this.creationDate = creationDate;
313 
314             // Fire event.
315             Map<String,Object> params = new HashMap<>();
316             params.put("type", "creationDateModified");
317             params.put("originalValue", originalCreationDate);
318             UserEventDispatcher.dispatchEvent(this, UserEventDispatcher.EventType.user_modified,
319                     params);
320         }
321         catch (UserNotFoundException unfe) {
322             Log.error(unfe.getMessage(), unfe);
323         }
324     }
325 
getModificationDate()326     public Date getModificationDate() {
327         return modificationDate;
328     }
329 
setModificationDate(Date modificationDate)330     public void setModificationDate(Date modificationDate) {
331         if (UserManager.getUserProvider().isReadOnly()) {
332             throw new UnsupportedOperationException("User provider is read-only.");
333         }
334 
335         try {
336             Date originalModificationDate = this.modificationDate;
337             UserManager.getUserProvider().setCreationDate(username, modificationDate);
338             this.modificationDate = modificationDate;
339 
340             // Fire event.
341             Map<String,Object> params = new HashMap<>();
342             params.put("type", "nameModified");
343             params.put("originalValue", originalModificationDate);
344             UserEventDispatcher.dispatchEvent(this, UserEventDispatcher.EventType.user_modified,
345                     params);
346         }
347         catch (UserNotFoundException unfe) {
348             Log.error(unfe.getMessage(), unfe);
349         }
350     }
351 
352     /**
353      * Returns all extended properties of the user. Users have an arbitrary
354      * number of extended properties. The returned collection can be modified
355      * to add new properties or remove existing ones.
356      *
357      * @return the extended properties.
358      */
getProperties()359     public Map<String,String> getProperties() {
360         synchronized (this) {
361             if (properties == null) {
362                 try {
363                     properties = UserManager.getUserPropertyProvider().loadProperties( username );
364                 } catch (UserNotFoundException e ) {
365                     Log.error( "Unable to retrieve properties for user " + username, e );
366                 }
367             }
368         }
369         // Return a wrapper that will intercept add and remove commands.
370         return new PropertiesMap();
371     }
372 
373     /**
374      * Returns the user's roster. A roster is a list of users that the user wishes to know
375      * if they are online. Rosters are similar to buddy groups in popular IM clients.
376      *
377      * @return the user's roster.
378      */
getRoster()379     public Roster getRoster() {
380         try {
381             return XMPPServer.getInstance().getRosterManager().getRoster(username);
382         }
383         catch (UserNotFoundException unfe) {
384             Log.error(unfe.getMessage(), unfe);
385             return null;
386         }
387     }
388 
389     @Override
getCachedSize()390     public int getCachedSize()
391             throws CannotCalculateSizeException {
392         // Approximate the size of the object in bytes by calculating the size
393         // of each field.
394         int size = 0;
395         size += CacheSizes.sizeOfObject();              // overhead of object
396         size += CacheSizes.sizeOfLong();                // id
397         size += CacheSizes.sizeOfString(username);      // username
398         size += CacheSizes.sizeOfString(name);          // name
399         size += CacheSizes.sizeOfString(email);         // email
400         size += CacheSizes.sizeOfDate() * 2;            // creationDate and modificationDate
401         size += CacheSizes.sizeOfMap(properties);       // properties
402         return size;
403     }
404 
405     @Override
toString()406     public String toString() {
407         return username;
408     }
409 
410     @Override
hashCode()411     public int hashCode() {
412         return username.hashCode();
413     }
414 
415     @Override
equals(Object object)416     public boolean equals(Object object) {
417         if (this == object) {
418             return true;
419         }
420         if (object != null && object instanceof User) {
421             return username.equals(((User)object).getUsername());
422         }
423         else {
424             return false;
425         }
426     }
427 
428     /**
429      * Map implementation that updates the database when properties are modified.
430      */
431     private class PropertiesMap extends AbstractMap<String, String> {
432 
433         @Override
put(String key, String value)434         public String put(String key, String value) {
435             Map<String,Object> eventParams = new HashMap<>();
436             String answer;
437             String keyString = key;
438 
439             try {
440                 synchronized ((getName() + keyString).intern()) {
441                     if (properties.containsKey(keyString)) {
442                         String originalValue = properties.get(keyString);
443                         answer = properties.put(keyString, value);
444                         UserManager.getUserPropertyProvider().updateProperty(username, keyString, value);
445                         // Configure event.
446                         eventParams.put("type", "propertyModified");
447                         eventParams.put("propertyKey", key);
448                         eventParams.put("originalValue", originalValue);
449                     }
450                     else {
451                         answer = properties.put(keyString, value);
452                         UserManager.getUserPropertyProvider().insertProperty(username, keyString, value);
453                         // Configure event.
454                         eventParams.put("type", "propertyAdded");
455                         eventParams.put("propertyKey", key);
456                     }
457                 }
458 
459                 // Fire event.
460                 UserEventDispatcher.dispatchEvent(User.this,
461                                                   UserEventDispatcher.EventType.user_modified, eventParams);
462                 return answer;
463             } catch (UserNotFoundException e ) {
464                 Log.error( "Unable to put property for user " + username, e );
465             }
466             return null;
467         }
468 
469         @Override
entrySet()470         public Set<Entry<String, String>> entrySet() {
471             return new PropertiesEntrySet();
472         }
473     }
474 
475     /**
476      * Set implementation that updates the database when properties are deleted.
477      */
478     private class PropertiesEntrySet extends AbstractSet<Map.Entry<String, String>> {
479 
480         @Override
size()481         public int size() {
482             return properties.entrySet().size();
483         }
484 
485         @Override
iterator()486         public Iterator<Map.Entry<String, String>> iterator() {
487             return new Iterator<Map.Entry<String, String>>() {
488 
489                 Iterator<Map.Entry<String, String>> iter = properties.entrySet().iterator();
490                 Map.Entry<String,String> current = null;
491 
492                 @Override
493                 public boolean hasNext() {
494                     return iter.hasNext();
495                 }
496 
497                 @Override
498                 public Map.Entry<String, String> next() {
499                     current = iter.next();
500                     return current;
501                 }
502 
503                 @Override
504                 public void remove() {
505                     if (current == null) {
506                         throw new IllegalStateException();
507                     }
508                     String key = current.getKey();
509                     try {
510                         UserManager.getUserPropertyProvider().deleteProperty(username, key);
511                         iter.remove();
512                         // Fire event.
513                         Map<String,Object> params = new HashMap<>();
514                         params.put("type", "propertyDeleted");
515                         params.put("propertyKey", key);
516                         UserEventDispatcher.dispatchEvent(User.this,
517                                                           UserEventDispatcher.EventType.user_modified, params);
518                     } catch (UserNotFoundException e ) {
519                         Log.error( "Unable to delete property for user " + username, e );
520                     }
521                 }
522             };
523         }
524     }
525 
526     @Override
writeExternal(ObjectOutput out)527     public void writeExternal(ObjectOutput out) throws IOException {
528         ExternalizableUtil.getInstance().writeSafeUTF(out, username);
529         ExternalizableUtil.getInstance().writeSafeUTF(out, getName());
530         ExternalizableUtil.getInstance().writeBoolean(out, email != null);
531         if (email != null) {
532             ExternalizableUtil.getInstance().writeSafeUTF(out, email);
533         }
534         ExternalizableUtil.getInstance().writeLong(out, creationDate.getTime());
535         ExternalizableUtil.getInstance().writeLong(out, modificationDate.getTime());
536     }
537 
538     @Override
readExternal(ObjectInput in)539     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
540         username = ExternalizableUtil.getInstance().readSafeUTF(in);
541         name = ExternalizableUtil.getInstance().readSafeUTF(in);
542         if (ExternalizableUtil.getInstance().readBoolean(in)) {
543             email = ExternalizableUtil.getInstance().readSafeUTF(in);
544         }
545         creationDate = new Date(ExternalizableUtil.getInstance().readLong(in));
546         modificationDate = new Date(ExternalizableUtil.getInstance().readLong(in));
547     }
548 
549     /*
550      * (non-Javadoc)
551      * @see org.jivesoftware.util.resultsetmanager.Result#getUID()
552      */
553     @Override
getUID()554     public String getUID()
555     {
556         return username;
557     }
558 }
559