001    /*
002     * 
003     * $Revision: 14994 $ $Date: 2009-03-24 13:01:57 +0100 (Tue, 24 Mar 2009) $
004     *
005     * This file is part of ***  M y C o R e  ***
006     * See http://www.mycore.de/ for details.
007     *
008     * This program is free software; you can use it, redistribute it
009     * and / or modify it under the terms of the GNU General Public License
010     * (GPL) as published by the Free Software Foundation; either version 2
011     * of the License or (at your option) any later version.
012     *
013     * This program is distributed in the hope that it will be useful, but
014     * WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program, in a file called gpl.txt or license.txt.
020     * If not, write to the Free Software Foundation Inc.,
021     * 59 Temple Place - Suite 330, Boston, MA  02111-1307 USA
022     */
023    
024    package org.mycore.user;
025    
026    import static org.mycore.common.MCRConstants.XLINK_NAMESPACE;
027    import static org.mycore.common.MCRConstants.XSI_NAMESPACE;
028    
029    import java.security.Principal;
030    import java.sql.Timestamp;
031    import java.util.ArrayList;
032    import java.util.GregorianCalendar;
033    import java.util.List;
034    
035    import org.jdom.Document;
036    import org.jdom.Element;
037    import org.mycore.access.MCRAccessManager;
038    import org.mycore.common.MCRConfiguration;
039    import org.mycore.common.MCRException;
040    import org.mycore.common.MCRSession;
041    import org.mycore.common.MCRSessionMgr;
042    
043    /**
044     * Instances of this class represent MyCoRe users.
045     * 
046     * @see org.mycore.user.MCRUserMgr
047     * @see org.mycore.user.MCRUserObject
048     * @see org.mycore.user.MCRUserContact
049     * @see org.mycore.user.MCRUserMgr
050     * 
051     * @author Detlev Degenhardt
052     * @author Jens Kupferschmidt
053     * @author Thomas Scheffler (yagee)
054     * @author Heiko Helmbrecht
055     * @version $Revision: 14994 $ $Date: 2008-10-09 11:30:08 +0200 (Do, 09. Okt
056     *          2008) $
057     */
058    public class MCRUser extends MCRUserObject implements MCRPrincipal, Principal {
059        /** The numerical ID of the MyCoRe user unit (either user ID or group ID) */
060        protected int numID = -1;
061    
062        /** Specify whether the user ID is enabled or disabled */
063        protected boolean idEnabled = false;
064    
065        /** Specify whether the user is allowed to update the user object */
066        protected boolean updateAllowed = false;
067    
068        /** The password of the MyCoRe user */
069        protected String passwd = "";
070    
071        /** The primary group ID of the user */
072        protected String primaryGroupID = "";
073    
074        /** Object representing user address information */
075        protected MCRUserContact userContact;
076    
077        /** A list of groups (IDs) where this user is a member of */
078        protected List<String> groupIDs = null;
079    
080        /**
081         * Default constructor. It is used to create a user object with empty
082         * fields. This is useful for constructing an XML representation of a user
083         * without specialized data which is used e.g. by MCRUserServlet just to get
084         * an XML-representation. The XML representation is used by the
085         * XSLT-Stylesheet to create HTML output for the presentation. This empty
086         * user object will not be created in the persistent data store.
087         */
088        public MCRUser() {
089            super();
090            numID = -1;
091            idEnabled = false;
092            updateAllowed = false;
093            passwd = "";
094            primaryGroupID = "";
095            groupIDs = new ArrayList<String>();
096            userContact = new MCRUserContact();
097        }
098    
099        /**
100         * This minimal constructor only takes the user ID as a parameter. For all
101         * other attributes the default constructor is invoked. This constructor is
102         * used by the access control system.
103         * 
104         * @param id
105         *            the named user ID
106         */
107        public MCRUser(String id) {
108            super.ID = id.trim();
109        }
110    
111        /**
112         * This constructor takes all attributes of this class as single variables.
113         * 
114         * @param numID
115         *            (int) the numerical user ID
116         * @param ID
117         *            the named user ID
118         * @param idEnabled
119         *            (boolean) specifies whether the account is disabled or enabled
120         * @param updateAllowed
121         *            (boolean) specifies whether the user may update his or her
122         *            data
123         * @param description
124         *            description of the user
125         * @param passwd
126         *            password of the user (encrypted or not encrypted, depending on
127         *            property)
128         * @param primaryGroupID
129         *            the ID of the primary group of the user
130         * @param groupIDs
131         *            a ArrayList of groups (IDs) the user belongs to
132         * @param salutation
133         *            contact information
134         * @param firstname
135         *            contact information
136         * @param lastname
137         *            contact information
138         * @param street
139         *            contact information
140         * @param city
141         *            contact information
142         * @param postalcode
143         *            contact information
144         * @param country
145         *            contact information
146         * @param state
147         *            contact information
148         * @param institution
149         *            contact information
150         * @param faculty
151         *            contact information
152         * @param department
153         *            contact information
154         * @param institute
155         *            contact information
156         * @param telephone
157         *            telephone number
158         * @param fax
159         *            fax number
160         * @param email
161         *            email address
162         * @param cellphone
163         *            number of cellular phone, if available
164         */
165        public MCRUser(int numID, String ID, String creator, Timestamp creationDate, Timestamp modifiedDate, boolean idEnabled, boolean updateAllowed, String description, String passwd, String primaryGroupID, List<String> groupIDs, String salutation, String firstname, String lastname, String street, String city, String postalcode, String country, String state, String institution, String faculty, String department, String institute, String telephone, String fax, String email, String cellphone) throws MCRException, Exception {
166            // The following data will never be changed by update
167            super.ID = trim(ID, id_len);
168            this.numID = numID;
169            super.creator = trim(creator, id_len);
170    
171            // check if the creation and modified timestamp is provided. If not, use
172            // current timestamp
173            if (creationDate == null) {
174                super.creationDate = new Timestamp(new GregorianCalendar().getTime().getTime());
175            } else {
176                super.creationDate = creationDate;
177            }
178    
179            if (modifiedDate == null) {
180                super.modifiedDate = new Timestamp(new GregorianCalendar().getTime().getTime());
181            } else {
182                super.modifiedDate = modifiedDate;
183            }
184    
185            this.idEnabled = idEnabled;
186            this.updateAllowed = updateAllowed;
187            super.description = trim(description, description_len);
188            this.passwd = trim(passwd, password_len);
189            this.primaryGroupID = trim(primaryGroupID, id_len);
190            this.groupIDs = groupIDs;
191    
192            this.userContact = new MCRUserContact(salutation, firstname, lastname, street, city, postalcode, country, state, institution, faculty, department, institute, telephone, fax, email, cellphone);
193        }
194        
195        public MCRUser(String userid, String passwd) throws MCRException, Exception {
196            this(MCRUserMgr.instance().getMaxUserNumID() + 1, userid, MCRSessionMgr.getCurrentSession().getCurrentUserID(), null, null, true,
197                    true, null, null, MCRConfiguration.instance().getString("MCR.Users.Guestuser.GroupName", "guestgroup"), null, null, null,
198                    null, null, null, null, null, null, null, null, null, null, null, null, null, null);
199        }
200    
201        /**
202         * It the passes the given JDOM element to a different constructor and after
203         * that encrypts the password if the flag useEncryption is true. This
204         * constructor must only be used if a cleartext password is provided in the
205         * JDOM element user data.
206         * 
207         * @param elm
208         *            JDOM Element defining a user
209         * @param useEncryption
210         *            flag to determine if the password has to be encrypted
211         */
212        public MCRUser(Element elm, boolean useEncryption) {
213            this(elm);
214    
215            if (useEncryption) {
216                String cryptPwd = MCRCrypt.crypt(this.passwd);
217                this.passwd = cryptPwd;
218            }
219        }
220    
221        /**
222         * This constructor creates the data of this object from a given JDOM
223         * element.
224         * 
225         * @param elm
226         *            JDOM Element defining a user
227         */
228        @SuppressWarnings("unchecked")
229        public MCRUser(Element elm) {
230            this();
231    
232            if (!elm.getName().equals("user")) {
233                return;
234            } // Detlev asks: Jens, what is this good for??
235    
236            super.ID = trim(elm.getAttributeValue("ID"), id_len);
237    
238            String numIDtmp = trim(elm.getAttributeValue("numID"));
239    
240            try {
241                this.numID = Integer.parseInt(numIDtmp);
242            } catch (Exception e) {
243                this.numID = -1;
244            }
245    
246            this.idEnabled = (elm.getAttributeValue("id_enabled").equals("true")) ? true : false;
247            this.updateAllowed = (elm.getAttributeValue("update_allowed").equals("true")) ? true : false;
248            this.creator = trim(elm.getChildTextTrim("user.creator"), id_len);
249            this.passwd = trim(elm.getChildTextTrim("user.password"), password_len);
250    
251            String tmp = elm.getChildTextTrim("user.creation_date");
252    
253            if (tmp != null) {
254                try {
255                    super.creationDate = Timestamp.valueOf(tmp);
256                } catch (Exception e) {
257                }
258            }
259    
260            tmp = elm.getChildTextTrim("user.last_modified");
261    
262            if (tmp != null) {
263                try {
264                    super.modifiedDate = Timestamp.valueOf(tmp);
265                } catch (Exception e) {
266                }
267            }
268    
269            this.description = trim(elm.getChildTextTrim("user.description"), description_len);
270            this.primaryGroupID = trim(elm.getChildTextTrim("user.primary_group"), id_len);
271    
272            Element contactElement = elm.getChild("user.contact");
273    
274            if (contactElement != null) {
275                userContact = new MCRUserContact(contactElement);
276            }
277    
278            Element userGroupElement = elm.getChild("user.groups");
279    
280            if (userGroupElement != null) {
281                List<Element> groupIDList = userGroupElement.getChildren();
282    
283                for (int j = 0; j < groupIDList.size(); j++) {
284                    Element groupID = (Element) groupIDList.get(j);
285    
286                    if (!(groupID.getTextTrim()).equals("")) {
287                        groupIDs.add(groupID.getTextTrim());
288                    }
289                }
290            }
291        }
292    
293        /**
294         * @return This method returns the contact object of the user
295         */
296        public MCRUserContact getUserContact() {
297            return (MCRUserContact) userContact.clone();
298        }
299    
300        /**
301         * @return This method returns the ID (user ID or group ID) of the user
302         *         object.
303         */
304        public final String getID() {
305            return ID;
306        }
307    
308        /**
309         * This method removes a group from the groups list of the user object. This
310         * list is the list of group IDs where this user or group itself is a member
311         * of, not the list of groups this user or group has as members.
312         * 
313         * @param groupID
314         *            ID of the group removed from the user object
315         */
316        public void removeGroupID(String groupID) throws MCRException {
317            // Since this operation is a modification of the group with ID groupID
318            // and not of
319            // the current group we do not need to check if the modification is
320            // allowed.
321            if (groupIDs.contains(groupID)) {
322                groupIDs.remove(groupID);
323            }
324        }
325    
326        /**
327         * This method adds a group to the groups list of the user object. This is
328         * the list of group IDs where this user or group itself is a member of, not
329         * the list of groups this user or group has as members.
330         * 
331         * @param groupID
332         *            ID of the group added to the user object
333         */
334        public void addGroupID(String groupID) throws MCRException {
335            // Since this operation is a modification of the group with ID groupID
336            // and not of
337            // the current group we do not need to check if the modification is
338            // allowed.
339            if (!groupIDs.contains(groupID)) {
340                groupIDs.add(groupID);
341            }
342        }
343    
344        /**
345         * This method determines the list of all groups the user is a member of,
346         * including the implicit ones. That means: if user u is a member of group
347         * G1 and G1 is member of group G2 and G2 itself is member of G3, then user
348         * u is considered to be an implicit member of groups G2 and G3.
349         * 
350         * @return list of all groups the user is a member of
351         * @deprecated use getGroupIDs instead
352         */
353        public final List<String> getAllGroupIDs() {
354            return groupIDs;
355        }
356    
357        /**
358         * @return This method returns the numerical ID of the user object.
359         */
360        public int getNumID() {
361            return numID;
362        }
363    
364        /**
365         * @return This method returns the password of the user.
366         */
367        public String getPassword() {
368            return passwd;
369        }
370    
371        /**
372         * @return This method returns the ID of the primary group of the user.
373         */
374        public final String getPrimaryGroupID() {
375            return primaryGroupID;
376        }
377    
378        /**
379         * This method checks if the user is authenticated. It does so by querying
380         * the user manager. This information is needed to assign categories in the
381         * access control subsystem.
382         * 
383         * @return returns true if the user is authenticated
384         */
385        public final boolean isAuthenticated() {
386            return MCRUserMgr.isAuthenticated(this);
387        }
388    
389        /**
390         * @return This method returns true if the user is enabled and may login.
391         */
392        public boolean isEnabled() {
393            return idEnabled;
394        }
395    
396        /**
397         * This method checks if the user is member of a given group.
398         * 
399         * @param group
400         *            Is the user a member of this group?
401         * @return Returns true if the user is a member of the given group.
402         */
403        public boolean isMemberOf(MCRGroup group) {
404            if (this.getPrimaryGroupID().equals(group.getID()) || groupIDs.contains(group.getID())) {
405                return true;
406            }
407    
408            return false;
409        }
410    
411        /**
412         * @return This method returns true if the user may update his or her data.
413         */
414        public boolean isUpdateAllowed() {
415            return updateAllowed;
416        }
417    
418        /**
419         * This method checks if all required fields have been provided. In a later
420         * stage of the software development a User Policy object will be asked,
421         * which fields exactly are the required fields. This will be configurable.
422         * 
423         * @return returns true if all required fields have been provided
424         */
425        public boolean isValid() {
426            ArrayList<String> requiredUserAttributes = MCRUserPolicy.instance().getRequiredUserAttributes();
427            boolean test = true;
428    
429            if (requiredUserAttributes.contains("userID")) {
430                test = test && (super.ID.length() > 0);
431            }
432    
433            if (requiredUserAttributes.contains("numID")) {
434                test = test && (this.numID >= 0);
435            }
436    
437            if (requiredUserAttributes.contains("creator")) {
438                test = test && (super.ID.length() > 0);
439            }
440    
441            if (requiredUserAttributes.contains("password")) {
442                test = test && (this.passwd.length() > 0);
443            }
444    
445            if (requiredUserAttributes.contains("primary_group")) {
446                test = test && (this.primaryGroupID.length() > 0);
447            }
448    
449            return test;
450        }
451    
452        /**
453         * This method sets the password of the user.
454         * 
455         * @param newPassword
456         *            The new password of the user
457         */
458        public boolean setPassword(String newPassword) {
459            // We do not allow empty passwords. Later we might check if the password
460            // is
461            // conform with a password policy.
462            if (newPassword == null) {
463                return false;
464            }
465    
466            if (modificationIsAllowed()) {
467                if (newPassword.length() != 0) {
468                    passwd = trim(newPassword, password_len);
469                    super.modifiedDate = new Timestamp(new GregorianCalendar().getTime().getTime());
470    
471                    return true;
472                }
473            }
474    
475            return false;
476        }
477    
478        /**
479         * This method sets the "enabled" attribute to a boolean value.
480         * 
481         * @param flag
482         *            the boolean data
483         */
484        public final void setEnabled(boolean flag) {
485            if (modificationIsAllowed()) {
486                idEnabled = flag;
487            }
488        }
489    
490        /**
491         * This method updates this instance with the data of the given MCRUser.
492         * 
493         * @param newuser
494         *            the data for the update.
495         */
496        public final void update(MCRUser newuser) {
497            // updateAllowed is an attribute of the user object which determines,
498            // whether
499            // the user himself may modify his or her data at all.
500            if (!updateAllowed) {
501                return;
502            }
503    
504            if (modificationIsAllowed()) { // check if the current user/session may
505    
506                // modify the object
507                idEnabled = newuser.isEnabled();
508                passwd = newuser.getPassword();
509                primaryGroupID = newuser.getPrimaryGroupID();
510                description = newuser.getDescription();
511                groupIDs = newuser.getGroupIDs();
512                userContact = newuser.getUserContact();
513            }
514        }
515    
516        /**
517         * @return This method returns the user or group object as a JDOM document.
518         */
519        public Document toJDOMDocument() throws MCRException {
520            Element root = new Element("mycoreuser");
521            root.addNamespaceDeclaration(XSI_NAMESPACE);
522            root.addNamespaceDeclaration(XLINK_NAMESPACE);
523            root.setAttribute("noNamespaceSchemaLocation", "MCRUser.xsd", XSI_NAMESPACE);
524            root.addContent(this.toJDOMElement());
525    
526            Document jdomDoc = new Document(root);
527    
528            return jdomDoc;
529        }
530    
531        /**
532         * This method returns the user object as a JDOM element. This is needed if
533         * one wants to get a representation of several user objects in one xml
534         * document.
535         * 
536         * @return this user data as JDOM element
537         */
538        public Element toJDOMElement() throws MCRException {
539            Element user = new Element("user").setAttribute("numID", Integer.toString(numID)).setAttribute("ID", ID).setAttribute("id_enabled", (idEnabled) ? "true" : "false").setAttribute("update_allowed", (updateAllowed) ? "true" : "false");
540            Element Creator = new Element("user.creator").setText(super.creator);
541            Element CreationDate = new Element("user.creation_date").setText(super.creationDate.toString());
542            Element ModifiedDate = new Element("user.last_modified").setText(super.modifiedDate.toString());
543            Element Passwd = new Element("user.password").setText(passwd);
544            Element Description = new Element("user.description").setText(super.description);
545            Element Primarygroup = new Element("user.primary_group").setText(primaryGroupID);
546    
547            // Aggregate user element
548            user.addContent(Creator).addContent(CreationDate).addContent(ModifiedDate).addContent(Passwd).addContent(Description).addContent(Primarygroup).addContent(userContact.toJDOMElement());
549    
550            // Loop over all group IDs
551            if (groupIDs.size() != 0) {
552                Element Groups = new Element("user.groups");
553                for (int i = 0; i < groupIDs.size(); i++) {
554                    Element groupID = new Element("groups.groupID").setText((String) groupIDs.get(i));
555                    Groups.addContent(groupID);
556                }
557                user.addContent(Groups);
558            }
559            return user;
560        }
561    
562        /**
563         * This method writes debug data to the logger (for the debug mode).
564         */
565        public final void debug() {
566            debugDefault();
567            logger.debug("primaryGroupID     = " + primaryGroupID);
568            logger.debug("groupIDs #         = " + groupIDs.size());
569            for (int i = 0; i < groupIDs.size(); i++) {
570                logger.debug("groupIDs           = " + ((String) groupIDs.get(i)));
571            }
572            userContact.debug();
573        }
574    
575        /**
576         * This private helper method checks if the modification of the user object
577         * is allowed for the current user/session.
578         */
579        public final boolean modificationIsAllowed() throws MCRException {
580            // Get the MCRSession object for the current thread from the session
581            // manager.
582            MCRSession mcrSession = MCRSessionMgr.getCurrentSession();
583            String currentUserID = mcrSession.getCurrentUserID();
584    
585            MCRGroup primaryGroup = MCRUserMgr.instance().retrieveGroup(primaryGroupID, false);
586    
587            if (MCRAccessManager.checkPermission("modify-user")) {
588                return true;
589            } else if (this.ID.equals(currentUserID) || this.creator.equals(currentUserID)) {
590                return true;
591            } else if (primaryGroup.getAdminUserIDs().contains(currentUserID)) {
592                return true;
593            } else { // check if the current user is (direct, not implicit)
594                // member
595    
596                // of one of the admGroups
597                ArrayList<String> admGroupIDs = primaryGroup.getAdminGroupIDs();
598    
599                for (int i = 0; i < admGroupIDs.size(); i++) {
600                    MCRGroup currentGroup = MCRUserMgr.instance().retrieveGroup((String) admGroupIDs.get(i), false);
601    
602                    if (currentGroup.getMemberUserIDs().contains(mcrSession.getCurrentUserID())) {
603                        return true;
604                    }
605                }
606            }
607    
608            throw new MCRException("The current user " + currentUserID + " has no right to modify the user " + this.ID + ".");
609        }
610    
611        /**
612         * @return This method returns the list of groups as a ArrayList of strings.
613         *         These are the groups where the object itself is a member of.
614         */
615        public final List<String> getGroupIDs() {
616            return groupIDs;
617        }
618    
619        public final int getGroupCount() {
620            try {
621                return groupIDs.size();
622            } catch (Exception e) {
623                return 0;
624            }
625        }
626    
627        /**
628         * @see #getID()
629         */
630        public String getName() {
631            return getID();
632        }
633    
634        /**
635         * @see #getID()
636         */
637        public String toString() {
638            return getID();
639        }
640    
641        public boolean equals(Object obj) {
642            if (!(obj instanceof MCRUser)) {
643                return false;
644            }
645            MCRUser u = (MCRUser) obj;
646            if (this == u) {
647                return true;
648            }
649            if (this.hashCode() != this.hashCode()) {
650                // acording to the hashCode() contract
651                return false;
652            }
653            return fastEquals(u);
654        }
655    
656        private boolean fastEquals(MCRUser u) {
657            return (((this.getID() == u.getID()) || (this.getID().equals(u.getID()))) && ((this.getUserContact() == u.getUserContact()) || (this.getUserContact().equals(u.getUserContact()))));
658        }
659    
660        public int hashCode() {
661            int result = 17;
662            result = 37 * result + getID().hashCode();
663            result = 37 * result + getUserContact().hashCode();
664            return result;
665        }
666    }