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    }