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 }