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    }