001 package users; 002 003 import java.io.Serializable; 004 import java.util.Collections; 005 import java.util.HashMap; 006 import java.util.HashSet; 007 import java.util.Map; 008 import java.util.Set; 009 010 import javax.swing.JCheckBox; 011 import javax.swing.JToggleButton; 012 013 import users.events.CapabilityDataEvent; 014 import users.events.CapabilityDataListener; 015 import util.Debug; 016 import util.HelpableListener; 017 import util.ListenerHelper; 018 import util.SerializableListener; 019 020 /** 021 * A user, having a name, a password for log-in purposes, and a set of capabilities. 022 * 023 * <p><code>User</code> objects are used to store all information associated with a user. As a 024 * default users have a name, a password for log-in purposes, and a set of capabilities 025 * that can be used to restrict the users usage of the application. Additional information 026 * stored in subclasses of <code>User</code> could include statistics on application usage, bonus data 027 * etc.</p> 028 * 029 * @see UserManager 030 * @see Capability 031 * 032 * @author Steffen Zschaler 033 * @version 2.0 05/05/1999 034 * @since v2.0 035 */ 036 public class User extends Object implements Serializable, Comparable<Object> { 037 038 /** 039 * ID for serialization. 040 */ 041 private static final long serialVersionUID = 5388761953776948314L; 042 043 /** 044 * The user's name. This is an immutable value and cannot be changed once the user 045 * has been created. 046 * 047 * @serial 048 */ 049 private final String m_sName; 050 051 /** 052 * The user's log-in password. This should normally be stored in garbled form as it 053 * may be serialized and thus there is the potential risk of it being read by unauthorized 054 * persons. 055 * 056 * @serial 057 */ 058 private char[] m_sPassWd; 059 060 /** 061 * The user's capabilities. 062 * 063 * @see Capability 064 * 065 * @serial 066 */ 067 private Map<String, Capability> m_mpCapabilities = new HashMap<String, Capability>(); 068 069 /** 070 * The list of all listeners that showed an interest in this user. 071 * 072 * @serial See <a href="#util.ListenerHelper">ListenerHelper's serializable form</a> for more information on 073 * what listeners get a chance to be serialized. 074 */ 075 protected ListenerHelper m_lhListeners = new ListenerHelper(); 076 077 /** 078 * Create a new User with a given name. The password will initially be the empty 079 * string and there will be no capabilities. 080 * 081 * @param sName the new user's name. 082 */ 083 public User(String sName) { 084 085 super(); 086 087 m_sName = sName; 088 m_sPassWd = new char[0]; 089 } 090 091 /** 092 * Retrieve the name of this user. 093 * 094 * @return the name of the user. 095 * 096 * @override Never 097 */ 098 public final String getName() { 099 return m_sName; 100 } 101 102 /** 103 * Check whether a given string is identical to the password of this user. 104 * 105 * <p>For security reasons there is no <code>getPassWd()</code> method. The only way to 106 * check a user's password is this method. The string you pass as a parameter will be 107 * compared to the user's password as it is stored, i.e. if the password is stored in 108 * a garbled form (recommended) the string you pass as a parameter must also be in 109 * garbled form.</p> 110 * 111 * @param sPassWd the string to be compared to the user's password. Must be in the 112 * same form as the actual password, i.e. esp. it must be garbled if the actual password 113 * is. 114 * 115 * @return true if the password and the string passed as a parameter are equal. 116 * 117 * @see #garblePassWD 118 * 119 * @override Never 120 */ 121 public final boolean isPassWd(char[] sPassWd) { 122 if(m_sPassWd.length != sPassWd.length) return false; 123 for(int i=0;i<m_sPassWd.length;i++) { 124 if(m_sPassWd[i] != sPassWd[i]) return false; 125 } 126 return true; 127 } 128 129 /** 130 * Set the password of this user. 131 * 132 * <p>The password is stored exactly as given, i.e. no garbling of any kind is performed. 133 * It is strongly recommended, though, that you pass a garbled password, so that 134 * passwords are not stored as plain text.</p> 135 * 136 * @param sPassWd the new password 137 * 138 * @see #garblePassWD 139 * @see PassWDGarbler 140 * 141 * @override Never 142 */ 143 public final void setPassWd(char[] sPassWd) { 144 m_sPassWd = sPassWd; 145 } 146 147 /** 148 * Check whether the given object equals this user. 149 * 150 * <p>Two users are considered equal if their names are equal.</p> 151 * 152 * @param o the object to be compared to. 153 * 154 * @return true if the given object equals this user. 155 * 156 * @override Sometimes Override this method if you need to implement a different notion of equality between 157 * users. 158 */ 159 public boolean equals(Object o) { 160 if (o instanceof User) { 161 return ((User)o).getName().equals(this.getName()); 162 } else { 163 return false; 164 } 165 } 166 167 /** 168 * Compares two Users. 169 * 170 * @param o the User to be compared with <code>this</code>. 171 * @return the comparison result. 172 * @override Sometimes 173 */ 174 public int compareTo(Object o) { 175 return m_sName.compareTo(((User)o).getName()); 176 } 177 178 /** 179 * Return a String representation. 180 * 181 * @return the {@link #getName name} of the user. 182 * 183 * @override Sometimes 184 */ 185 public String toString() { 186 return getName(); 187 } 188 189 /** 190 * Set a range of the user's capabilities to new values. 191 * 192 * <p>Sets all capabilities from <code>mpCapabilities</code> to the new values. 193 * This will fire <code>capabilitiesAdded</code> events, and <code>capabilitiesReplaced</code> 194 * events if capabilities were changed.</p> 195 * 196 * <p><strong>Attention:</strong> A capability that has been set cannot be removed 197 * again. Capabilities have two states (Granted and Not Granted). If you want to 198 * remove a certain capability, set its state to Not Granted.</p> 199 * 200 * @param mpCapabilities the capabilities to be set. The keys of this map must be the 201 * names of the capabilities to be set, whereas the corresponding values must be the 202 * actual Capability objects. 203 * 204 * @see Capability 205 * @see #setCapability 206 * @see users.events.CapabilityDataListener 207 * 208 * @override Never 209 */ 210 public synchronized void setCapabilities(Map<String, Capability> mpCapabilities) { 211 212 // distinguish added and replaced capabilities to make sure we fire 213 // the correct events. 214 Set<String> stReplacements = new HashSet<String>(mpCapabilities.keySet()); 215 stReplacements.retainAll(m_mpCapabilities.keySet()); 216 217 Set<String> stAdded = new HashSet<String>(mpCapabilities.keySet()); 218 stAdded.removeAll(m_mpCapabilities.keySet()); 219 220 // store the capabilities 221 m_mpCapabilities.putAll(mpCapabilities); 222 223 // fire the events 224 fireCapabilitiesAdded(Collections.unmodifiableSet(stAdded)); 225 fireCapabilitiesReplaced(Collections.unmodifiableSet(stReplacements)); 226 } 227 228 /** 229 * Set one capability. 230 * 231 * <p><strong>Attention:</strong> A capability that has been set cannot be removed 232 * again. Capabilities have two states (Granted and Not Granted). If you want to 233 * remove a certain capability, set its state to Not Granted.</p> 234 * 235 * <p>This will fire a <code>capabilitiesAdded</code> or a <code>capabilitiesReplaced</code> 236 * event.</p> 237 * 238 * @param cap the capability to be set. 239 * 240 * @return the previous value of the capability or <code>null</code> if none. 241 * 242 * @override Never 243 */ 244 public synchronized Capability setCapability(Capability cap) { 245 if (cap != null) { 246 Capability capReturn = (Capability)m_mpCapabilities.remove(cap.getName()); 247 248 m_mpCapabilities.put(cap.getName(), cap); 249 250 if (capReturn != null) { 251 fireCapabilitiesReplaced(Collections.singleton(cap.getName())); 252 } else { 253 fireCapabilitiesAdded(Collections.singleton(cap.getName())); 254 } 255 256 return capReturn; 257 } 258 259 return null; 260 } 261 262 /** 263 * Retrieve one of this user's capabilities. 264 * 265 * <p>Retrieves the capability of this user that is identified by <code>sCapName</code>.</p> 266 * 267 * @param sCapName the name of the capability to be returned. 268 * 269 * @return the capability associated with the given name or <code>null</code> if none. 270 * 271 * @see Capability 272 * 273 * @override Never 274 */ 275 public synchronized Capability getCapability(String sCapName) { 276 return (Capability)m_mpCapabilities.get(sCapName); 277 } 278 279 /** 280 * Return a checkbox that can be used to visualize and change the value of a certain 281 * capability of this user. 282 * 283 * <p>The checkbox will be backed by the capability, i.e. changes of the capability 284 * will be directly reflected in the checkbox and vice-versa. There will be a 285 * <code>NullPointerException</code> if the specified capability does not exist.</p> 286 * 287 * @param sCapName the name of the capability to be visualized by the checkbox. 288 * 289 * @return a checkbox that can be used to visualize and change the capability. 290 * 291 * @exception NullPointerException if Capability does not exist. 292 * 293 * @see javax.swing.JCheckBox 294 * @see Capability 295 * @see Capability#getDisplayName 296 * 297 * @override Never 298 */ 299 public JCheckBox getCapabilityCheckBox(final String sCapName) { 300 301 class CapabilityButtonModel extends JToggleButton.ToggleButtonModel implements CapabilityDataListener, 302 HelpableListener, SerializableListener { 303 304 private static final long serialVersionUID = 1102671339244866894L; 305 306 { 307 // replace listener list for special support 308 listenerList = new ListenerHelper(this); 309 310 updateModel(); 311 } 312 313 private Capability capModelled; 314 315 // ButtonModel interface methods 316 public boolean isSelected() { 317 ((ListenerHelper)listenerList).needModelUpdate(); 318 319 return capModelled.isGranted(); 320 } 321 322 public void setSelected(boolean bSelect) { 323 if (bSelect != capModelled.isGranted()) { 324 setCapability(capModelled.getToggled()); 325 } 326 } 327 328 // CapabilityDataListener interface methods 329 public void capabilitiesAdded(CapabilityDataEvent e) {} 330 331 public void capabilitiesReplaced(CapabilityDataEvent e) { 332 if (e.affectsCapability(capModelled.getName())) { 333 capModelled = e.getCapability(capModelled.getName()); 334 335 fireStateChanged(); 336 } 337 } 338 339 // HelpableListener interface methods 340 public void updateModel() { 341 capModelled = getCapability(sCapName); 342 } 343 344 public void subscribe() { 345 User.this.addCapabilityDataListener(CapabilityButtonModel.this); 346 Debug.print("CapabilityButtonModel.subscribe", -1); 347 } 348 349 public void unsubscribe() { 350 User.this.removeCapabilityDataListener(CapabilityButtonModel.this); 351 Debug.print("CapabilityButtonModel.unsubscribe", -1); 352 } 353 } 354 355 Capability cap = getCapability(sCapName); 356 JCheckBox jcbReturn = new JCheckBox(cap.getDisplayName(), cap.isGranted()); 357 jcbReturn.setModel(new CapabilityButtonModel()); 358 359 return jcbReturn; 360 } 361 362 // Event handling 363 /** 364 * Add a CapabilityDataListener. CapabilityDataListeners receive events whenever a 365 * user's capability list changed. 366 * 367 * @param cdl the CapabilityDataListener to add. 368 * 369 * @override Never 370 */ 371 public void addCapabilityDataListener(CapabilityDataListener cdl) { 372 m_lhListeners.add(CapabilityDataListener.class, cdl); 373 } 374 375 /** 376 * Remove a CapabilityDataListener. CapabilityDataListeners receive events whenever a 377 * user's capability list changed. 378 * 379 * @param cdl the CapabilityDataListener to remove. 380 * 381 * @override Never 382 */ 383 public void removeCapabilityDataListener(CapabilityDataListener cdl) { 384 m_lhListeners.remove(CapabilityDataListener.class, cdl); 385 } 386 387 /** 388 * Fire a <code>capabilitiesAdded</code> event. 389 * 390 * @param stCapNames the set of capability names that where added. 391 * 392 * @see users.events.CapabilityDataListener#capabilitiesAdded 393 * 394 * @override Never 395 */ 396 protected void fireCapabilitiesAdded(Set<String> stCapNames) { 397 CapabilityDataEvent cde = null; 398 399 // Guaranteed to return a non-null array 400 Object[] listeners = m_lhListeners.getListenerList(); 401 402 // Process the listeners last to first, notifying 403 // those that are interested in this event 404 for (int i = listeners.length - 2; i >= 0; i -= 2) { 405 if (listeners[i] == CapabilityDataListener.class) { 406 // Lazily create the event: 407 if (cde == null) { 408 cde = new CapabilityDataEvent(this, stCapNames); 409 410 } 411 ((CapabilityDataListener)listeners[i + 1]).capabilitiesAdded(cde); 412 } 413 } 414 } 415 416 /** 417 * Fire a <code>capabilitiesReplaced</code> event. 418 * 419 * @param stCapNames the set of capability names that where replaced. 420 * 421 * @see users.events.CapabilityDataListener#capabilitiesReplaced 422 * 423 * @override Never 424 */ 425 protected void fireCapabilitiesReplaced(Set<String> stCapNames) { 426 CapabilityDataEvent cde = null; 427 428 // Guaranteed to return a non-null array 429 Object[] listeners = m_lhListeners.getListenerList(); 430 431 // Process the listeners last to first, notifying 432 // those that are interested in this event 433 for (int i = listeners.length - 2; i >= 0; i -= 2) { 434 if (listeners[i] == CapabilityDataListener.class) { 435 // Lazily create the event: 436 if (cde == null) { 437 cde = new CapabilityDataEvent(this, stCapNames); 438 439 } 440 ((CapabilityDataListener)listeners[i + 1]).capabilitiesReplaced(cde); 441 } 442 } 443 } 444 445 /** 446 * Method called by the UserManager when the user was associated with some object. 447 * 448 * @param oTo the object this user was associated with. 449 * 450 * @see UserManager 451 * 452 * @override Sometimes Override this method if you need to be informed when the user was logged on to some 453 * object. 454 */ 455 public void loggedOn(Object oTo) {} 456 457 /** 458 * Method called by the UserManager when the user was disassociated from some object. 459 * 460 * @param oFrom the object this user was disassociated from. 461 * 462 * @see UserManager 463 * 464 * @override Sometimes Override this method if you need to be informed when the user was logged off from 465 * some object. 466 */ 467 public void loggedOff(Object oFrom) {} 468 469 /////////////////////////////////////////////////////////////////////////////////////////////// 470 /// STATIC PART 471 /////////////////////////////////////////////////////////////////////////////////////////////// 472 473 /** 474 * Converts a char[]-Array to String 475 * @param chValue the char[]-Array to be converted 476 * @return the converted value as String 477 */ 478 public static String getStringFromChar(char[] chValue) { 479 int i; 480 String strReturn = ""; 481 for(i=0;i<chValue.length;i++) { 482 strReturn += chValue[i]; 483 } 484 return strReturn; 485 } 486 487 /** 488 * Converst a String to char[]-Array 489 * @param strValue the String to be converted 490 * @return the converted value as char[]-Array 491 */ 492 public static char[] getCharFromString(String strValue) { 493 char[] chReturn = new char[strValue.length()]; 494 int i; 495 for(i=0;i<strValue.length();i++) { 496 chReturn[i] = strValue.charAt(i); 497 } 498 return chReturn; 499 } 500 501 /** 502 * The default password garbler. 503 * 504 * <p>The default password garbling algorithm is very simple and should only be used if no real security 505 * concerns are present. It will take the input String and perform a one-complement and add 7 for each byte 506 * in the String.</p> 507 */ 508 public static final PassWDGarbler DEFAULT_PASSWORD_GARBLER = new PassWDGarbler() { 509 private static final long serialVersionUID = -1778238039097376383L; 510 511 public char[] garblePassWD(char[] sPassWD) { 512 return getCharFromString(MD5.encodeString(getStringFromChar(sPassWD))); 513 } 514 }; 515 516 /** 517 * The global password garbler. It defaults to {@link #DEFAULT_PASSWORD_GARBLER}. 518 */ 519 private static PassWDGarbler s_pwdgGlobal = DEFAULT_PASSWORD_GARBLER; 520 521 /** 522 * Set the global password garbler. 523 * 524 * <p>The global password garbler can be used as a central instance for garbling 525 * your users' passwords. It defaults to {@link #DEFAULT_PASSWORD_GARBLER}.</p> 526 * 527 * @param pwdgNew the new global password garbler. 528 * 529 * @return the previous global password garbler. 530 */ 531 public synchronized static PassWDGarbler setGlobalPassWDGarbler(PassWDGarbler pwdgNew) { 532 PassWDGarbler pwdg = s_pwdgGlobal; 533 534 s_pwdgGlobal = pwdgNew; 535 536 return pwdg; 537 } 538 539 /** 540 * Get the global password garbler. 541 * 542 * @return the global password garbler. 543 */ 544 public synchronized static PassWDGarbler getGlobalPassWDGarbler() { 545 return s_pwdgGlobal; 546 } 547 548 /** 549 * Garble a password using the global password garbler, if any. 550 * 551 * <p>If no global password garbler is installed, the password 552 * is returned unchanged. Otherwise the garbled password is returned.</p> 553 * 554 * @param sPassWD the password to garble 555 * 556 * @return the garbled password. 557 */ 558 public synchronized static char[] garblePassWD(char[] sPassWD) { 559 return ((s_pwdgGlobal == null) ? (sPassWD) : (s_pwdgGlobal.garblePassWD(sPassWD))); 560 } 561 }