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