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