001/*
002 $Id: SessionControl.java 3677 2010-04-20 12:02:33Z gregory $
003
004 Copyright (C) 2006 Gregory Vincic, Olle Mansson
005 Copyright (C) 2007 Gregory Vincic
006
007 This file is part of Proteios.
008 Available at http://www.proteios.org/
009
010 Proteios is free software; you can redistribute it and/or modify it
011 under the terms of the GNU General Public License as published by
012 the Free Software Foundation; either version 2 of the License, or
013 (at your option) any later version.
014
015 Proteios is distributed in the hope that it will be useful, but
016 WITHOUT ANY WARRANTY; without even the implied warranty of
017 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
018 General Public License for more details.
019
020 You should have received a copy of the GNU General Public License
021 along with this program; if not, write to the Free Software
022 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
023 02111-1307, USA.
024 */
025package org.proteios.core;
026
027import org.proteios.core.authentication.AuthenticationInformation;
028import org.proteios.core.authentication.Authenticator;
029import org.proteios.core.data.ClientData;
030import org.proteios.core.data.ClientDefaultSettingData;
031import org.proteios.core.data.ItemKeyData;
032import org.proteios.core.data.PasswordData;
033import org.proteios.core.data.ProjectData;
034import org.proteios.core.data.ProjectKeyData;
035import org.proteios.core.data.QuotaData;
036import org.proteios.core.data.RoleData;
037import org.proteios.core.data.SessionData;
038import org.proteios.core.data.SettingData;
039import org.proteios.core.data.ShareableData;
040import org.proteios.core.data.SharedData;
041import org.proteios.core.data.UserClientSettingData;
042import org.proteios.core.data.UserData;
043import org.proteios.core.data.UserDefaultSettingData;
044import org.proteios.util.Enumeration;
045import org.proteios.util.MD5;
046import java.util.Collections;
047import java.util.Date;
048import java.util.EnumSet;
049import java.util.HashMap;
050import java.util.List;
051import java.util.Map;
052import java.util.Set;
053import java.util.WeakHashMap;
054
055/**
056 * This class handles user sessions. Each instance is thread-safe and can handle
057 * a single user at a time. This class is used to create {@link DbControl}
058 * objects which are needed to access the database.
059 * 
060 * @author Nicklas
061 * @version 2.0
062 * @see Application#newSessionControl(String, String, String)
063 * @see Application#getSessionControl(String, String)
064 * @base.modified $Date: 2010-04-20 14:02:33 +0200 (Tue, 20 Apr 2010) $
065 */
066public final class SessionControl
067{
068        /**
069         * Log core events.
070         */
071        private static final org.apache.log4j.Logger log = org.apache.log4j.LogManager
072                .getLogger("org.proteios.core");
073        /**
074         * The id of the object. Used by
075         * {@link Application#getSessionControl(String,String)}
076         */
077        private final String id;
078        /**
079         * The id of the {@link ClientData}. 0 = no client
080         */
081        private final int clientId;
082        /**
083         * The external id of the {@link ClientData}. null = no client
084         */
085        private final String externalClientId;
086        /**
087         * The remote IP.
088         */
089        private final String remoteId;
090        /**
091         * Holds all created <code>DbControl</code> object, so we can close them
092         * if the user logs out. We use only the keys of the map since there is no
093         * <code>WeakHashSet</code> class.
094         */
095        private final Map<DbControl, String> dbControlCache = Collections
096                .synchronizedMap(new WeakHashMap<DbControl, String>());
097        /**
098         * The time of the last access to this object.
099         */
100        private long lastAccess;
101        /**
102         * Stores name and value from {@link ClientDefaultSettingData}.
103         */
104        private Map<String, SettingInfo> clientDefaultSettings;
105        /**
106         * Login information for the logged in user.
107         */
108        private LoginInfo loginInfo;
109        /**
110         * The last generated challenge for password encryption.
111         */
112        private String lastChallenge;
113
114
115        /**
116         * Creates a new <code>SessionControl</code> object.
117         * 
118         * @param id The id used in
119         *        {@link Application#newSessionControl(String,String,String)}
120         * @param clientId The id of the client application
121         * @param remoteId For example, the IP-address
122         */
123        SessionControl(String id, int clientId, String externalClientId,
124                        String remoteId) throws BaseException
125        {
126                this.id = id;
127                this.clientId = clientId;
128                this.externalClientId = externalClientId;
129                this.remoteId = remoteId;
130                if (clientId != 0)
131                {
132                        // load default settings for client application
133                        org.hibernate.Session session = null;
134                        org.hibernate.Transaction tx = null;
135                        try
136                        {
137                                session = HibernateUtil.newSession();
138                                tx = HibernateUtil.newTransaction(session);
139                                clientDefaultSettings = loadClientDefaultSettings(session,
140                                        clientId);
141                        }
142                        finally
143                        {
144                                if (tx != null)
145                                        HibernateUtil.commit(tx);
146                                if (session != null)
147                                        HibernateUtil.close(session);
148                        }
149                }
150                updateLastAccess();
151        }
152
153
154        /**
155         * Get the time this object last was accessed. Used by the cleanup timer in
156         * Application
157         */
158        long getLastAccess()
159        {
160                return lastAccess;
161        }
162
163
164        /**
165         * Update the time this object last was accessed. Used by the cleanup timer
166         * in Application.
167         */
168        void updateLastAccess()
169        {
170                lastAccess = System.currentTimeMillis();
171        }
172
173
174        /**
175         * Get the id of this <code>SessionControl</code>. Use this value for
176         * retrieving the object from the {@link Application} cache.
177         * 
178         * @return The id
179         * @see Application#getSessionControl(String,String)
180         */
181        public String getId()
182        {
183                return id;
184        }
185
186
187        /**
188         * Get the id of the <code>Client</code> application in use.
189         */
190        public int getClientId()
191        {
192                updateLastAccess();
193                return clientId;
194        }
195
196
197        /**
198         * Get the external id of the <code>Client</code> application in use.
199         * 
200         * @see #getClientId()
201         */
202        public String getExternalClientId()
203        {
204                updateLastAccess();
205                return externalClientId;
206        }
207
208
209        /**
210         * Get the remote id that was passed to the {@link 
211         * Application#newSessionControl(String,String,String)} method when creating
212         * this object. This value is checked against the value passed to the {@link
213         * Application#getSessionControl(String,String)} method.
214         */
215        String getRemoteId()
216        {
217                return remoteId;
218        }
219
220
221        /**
222         * Create a new <code>DbControl</code> object for this session.
223         * 
224         * @return A <code>DbControl</code> object
225         * @throws BaseException If there is an error
226         */
227        public DbControl newDbControl()
228                        throws BaseException
229        {
230                updateLastAccess();
231                DbControl dbControl = new DbControl(this);
232                dbControlCache.put(dbControl, null);
233                return dbControl;
234        }
235
236
237        void login(Keyring keyring)
238        {
239                updateLastAccess();
240                LoginInfo li = new LoginInfo();
241                li.keyring = keyring;
242                li.sessionId = 0;
243                li.userId = 0;
244                li.userLogin = null;
245                loginInfo = li;
246        }
247
248
249        /**
250         * Generate a new random string to be used for password encryption in the
251         * login method. Using encryption prevents that user passwords are sent in
252         * clear text between client and server (ie. web browser and web server).
253         * The client application should use the challenge as follows:
254         * <ol>
255         * <li>Calculate the MD5 of the real password
256         * <li>Concatenate this with the challenge with a colon inbetween:
257         * <code>MD5:challenge</code>
258         * <li>Calculate the MD5 of the concatenated string. This is the encrypted
259         * password, which should be sent to the login method.
260         * </ol>
261         * Note! This is not intended as a replacement for SSL encrypted
262         * communication. Note! Each call to this method generates a new random
263         * challenge.
264         * 
265         * @return A challenge string used to encrypt the password
266         * @see #login(String, String, String, boolean)
267         */
268        public String getChallenge()
269        {
270                lastChallenge = Application.generateRandomId(16);
271                return lastChallenge;
272        }
273
274
275        /**
276         * Get the last challenge generated.
277         */
278        private String getLastChallenge()
279        {
280                return lastChallenge;
281        }
282
283
284        // Used by the ActionFactory. This was implemented to solve the
285        // authentication problem using
286        // tomcats Realm authenticator together with the existing SessionControl.
287        
288        /**
289           @param login
290           @param comment
291           @param encryptedPassword
292           @throws ItemNotFoundException
293           @throws PermissionDeniedException
294           @throws BaseException
295           @deprecated
296         */
297        synchronized void login(String login, String comment,
298                        boolean encryptedPassword)
299                        throws ItemNotFoundException, PermissionDeniedException,
300                        BaseException
301        {
302                updateLastAccess();
303                // Are we already logged in?
304                if (isLoggedIn())
305                {
306                        throw new AlreadyLoggedInException(loginInfo.userLogin);
307                }
308                if (login == null)
309                        throw new InvalidUseOfNullException("login");
310                org.hibernate.Session session = null;
311                org.hibernate.Transaction tx = null;
312                try
313                {
314                        session = HibernateUtil.newSession();
315                        tx = HibernateUtil.newTransaction(session);
316                        // Verify login and password
317                        UserData userData = loadUserData(session, login);
318                        if (userData == null)
319                                throw new ItemNotFoundException("'" + login + "' doesn't exist");
320                        LoginInfo li = createLoginInfo(session, userData, comment, false);
321                        HibernateUtil.commit(tx);
322                        loginInfo = li;
323                }
324                catch (BaseException ex)
325                {
326                        if (tx != null)
327                                HibernateUtil.rollback(tx);
328                        throw ex;
329                }
330                finally
331                {
332                        if (session != null)
333                                HibernateUtil.close(session);
334                }
335        }
336
337
338        /**
339         * Log in to Proteios. The method checks that the given login is valid, the
340         * password is correct and that the user has USE permission for the client
341         * application if one has been specified.
342         * <p>
343         * The <code>encryptedPassword</code> parameter is used if the client
344         * application sends the password encrypted. See {@link #getChallenge()} for
345         * information about how to encrypt the password.
346         * 
347         * @param login The login of the user
348         * @param password The password for the user
349         * @param comment A comment for the login, which will show in the
350         *        {@link Session}
351         * @param encryptedPassword A flag indicating if the password has been
352         *        encrypted or not
353         * @throws ItemNotFoundException If a user with the specified username is
354         *         not found
355         * @throws InvalidPasswordException If the specified password is incorrect
356         * @throws PermissionDeniedException If the user doesn't have
357         *         {@link Permission#USE} permission for the current client
358         *         application or if a user is already logged in
359         * @throws BaseException If there is any other error
360         * @see #logout()
361         * @see #isLoggedIn()
362         * @see #getLoggedInUserId()
363         */
364        public synchronized void login(String login, String password,
365                        String comment, boolean encryptedPassword)
366                        throws ItemNotFoundException, PermissionDeniedException,
367                        InvalidPasswordException, BaseException
368        {
369                updateLastAccess();
370                // Are we already logged in?
371                if (isLoggedIn())
372                {
373                        throw new AlreadyLoggedInException(loginInfo.userLogin);
374                }
375                if (login == null)
376                        throw new InvalidUseOfNullException("login");
377                if (password == null)
378                        throw new InvalidUseOfNullException("password");
379                org.hibernate.Session session = null;
380                org.hibernate.Transaction tx = null;
381                try
382                {
383                        session = HibernateUtil.newSession();
384                        tx = HibernateUtil.newTransaction(session);
385                        // Verify login and password
386                        UserData userData = null;
387                        // The root user should always use internal verification
388                        UserData root = HibernateUtil.loadData(session, UserData.class,
389                                SystemItems.getId(User.ROOT));
390                        assert root != null : "root == null";
391                        if (Application.isUsingInternalAuthentication() || login
392                                .equals(root.getLogin()))
393                        {
394                                userData = verifyUserInternal(session, login, password,
395                                        encryptedPassword);
396                        }
397                        else
398                        {
399                                if (encryptedPassword)
400                                {
401                                        throw new BaseException(
402                                                "Encrypted passwords are not supported when using external authentication");
403                                }
404                                userData = verifyUserExternal(session, login, password);
405                        }
406                        LoginInfo li = createLoginInfo(session, userData, comment, false);
407                        HibernateUtil.commit(tx);
408                        loginInfo = li;
409                        // Add user id to user login time map
410                        Application.addUserLoginTime(loginInfo.userId);
411                }
412                catch (BaseException ex)
413                {
414                        if (tx != null)
415                                HibernateUtil.rollback(tx);
416                        throw ex;
417                }
418                finally
419                {
420                        if (session != null)
421                                HibernateUtil.close(session);
422                }
423        }
424
425
426        /**
427         * Check that the user exists and verify the password using internal
428         * authentication.
429         */
430        private UserData verifyUserInternal(org.hibernate.Session session,
431                        String login, String password, boolean encryptedPassword)
432                        throws ItemNotFoundException, InvalidPasswordException,
433                        AccountExpiredException, BaseException
434        {
435                UserData userData = loadUserData(session, login);
436                if (userData == null)
437                {
438                        throw new ItemNotFoundException("User[login=" + login + "]");
439                }
440                if (userData.isRemoved())
441                {
442                        throw new PermissionDeniedException(
443                                "The account has been flagged for deletion: User[login=" + login + "]");
444                }
445                if (userData.isDisabled())
446                {
447                        throw new PermissionDeniedException(
448                                "The account has been disabled: User[login=" + login + "]");
449                }
450                Date expirationDate = userData.getExpirationDate();
451                if ((expirationDate != null) && expirationDate.before(new Date()))
452                {
453                        throw new AccountExpiredException(login, expirationDate);
454                }
455                PasswordData passwordData = userData.getPassword();
456                String md5Password = passwordData.getMd5Password();
457                if (encryptedPassword)
458                {
459                        md5Password = MD5
460                                .getHashString(md5Password + ":" + getLastChallenge());
461                }
462                else
463                {
464                        password = MD5.getHashString(password);
465                }
466                if (!md5Password.equals(password))
467                {
468                        throw new InvalidPasswordException("User[login=" + login + "]");
469                }
470                return userData;
471        }
472
473
474        private UserData loadUserData(org.hibernate.Session session, String login)
475        {
476                org.hibernate.Query query = HibernateUtil.getPredefinedQuery(session,
477                        "GET_USER_FOR_LOGIN");
478                /*
479                 * SELECT usr FROM UserData usr WHERE usr.login = :login
480                 */
481                query.setString("login", login);
482                UserData userData = HibernateUtil.loadData(UserData.class, query);
483                return userData;
484        }
485
486
487        /**
488         * Verify the user with external authentication.
489         */
490        private UserData verifyUserExternal(org.hibernate.Session session,
491                        String login, String password)
492                        throws ItemNotFoundException, InvalidPasswordException,
493                        BaseException
494        {
495                AuthenticationInformation info = null;
496                Authenticator authenticator = null;
497                try
498                {
499                        authenticator = Application.getAuthenticator();
500                        info = authenticator.authenticate(login, password);
501                }
502                catch (org.proteios.core.authentication.UnknownLoginException ex)
503                {
504                        throw new ItemNotFoundException("User[login=" + login + "]");
505                }
506                catch (org.proteios.core.authentication.InvalidPasswordException ex)
507                {
508                        throw new InvalidPasswordException("User[login=" + login + "]");
509                }
510                catch (org.proteios.core.authentication.AuthenticationException ex)
511                {
512                        if (Config.getBoolean("auth.cachepasswords"))
513                        {
514                                return verifyUserInternal(session, login, password, false);
515                        }
516                        throw new BaseException(ex);
517                }
518                org.hibernate.Query query = HibernateUtil.getPredefinedQuery(session,
519                        "GET_USER_FOR_EXTERNAL_ID");
520                /*
521                 * SELECT usr FROM UserData usr WHERE usr.externalId = :externalId
522                 */
523                query.setParameter("externalId", info.id);
524                UserData userData = HibernateUtil.loadData(UserData.class, query);
525                if (userData == null)
526                {
527                        userData = new UserData();
528                        userData.setExternalId(info.id);
529                        userData.setLogin(info.login);
530                        userData.setName(info.name == null ? info.login : info.name);
531                        userData.getPassword().setMd5Password("");
532                        userData.setQuota(HibernateUtil.loadData(session, QuotaData.class,
533                                SystemItems.getId(Quota.DEFAULT)));
534                        // TODO - should be configurable
535                        RoleData users = HibernateUtil.loadData(session, RoleData.class,
536                                SystemItems.getId(Role.USER));
537                        users.getUsers().add(userData);
538                        // TODO - make it possible to add default groups as well
539                }
540                if (Config.getBoolean("auth.cachepasswords"))
541                {
542                        userData.getPassword().setMd5Password(MD5.getHashString(password));
543                        int daysToCache = Config.getInt("auth.daystocache", 0);
544                        userData
545                                .setExpirationDate(daysToCache > 0 ? new Date(System
546                                        .currentTimeMillis() + daysToCache * 24 * 60 * 60 * 1000) : null);
547                }
548                if (authenticator.supportsExtraInformation() && (Config
549                        .getBoolean("auth.synchronize") || userData.getId() == 0))
550                {
551                        userData.setName(info.name == null ? info.login : info.name);
552                        userData.setOrganisation(info.organisation);
553                        userData.setAddress(info.address);
554                        userData.setPhone(info.phone);
555                        userData.setFax(info.fax);
556                        userData.setEmail(info.email);
557                        userData.setUrl(info.url);
558                        userData.setDescription(info.description);
559                }
560                if (userData.getId() == 0)
561                {
562                        HibernateUtil.saveData(session, userData);
563                }
564                HibernateUtil.flush(session);
565                return userData;
566        }
567
568
569        /**
570         * Log in as another user. If this call is successful, you will get a new
571         * <code>SessionControl</code> object which is equivalent to a
572         * <code>SessionControl</code> where that user logged in by normal means.
573         * This method requires that the logged in user has
574         * {@link Permission#ACT_AS_ANOTHER_USER} permission.
575         * 
576         * @param userId The id of the user to login as
577         * @param comment A comment that will be placed in the {@link Session}
578         *        information
579         * @return A new <code>SessionControl</code> object
580         * @throws ItemNotFoundException If no user with the specified id exists
581         * @throws PermissionDeniedException If the logged in user doesn't have
582         *         {@link Permission#ACT_AS_ANOTHER_USER} permission
583         * @throws BaseException If there is another error
584         */
585        public SessionControl impersonateLogin(int userId, String comment)
586                        throws ItemNotFoundException, PermissionDeniedException,
587                        BaseException
588        {
589                if (!hasSystemPermission(Permission.ACT_AS_ANOTHER_USER))
590                {
591                        throw new PermissionDeniedException(
592                                "No permission to act as another user.");
593                }
594                org.hibernate.Session session = null;
595                org.hibernate.Transaction tx = null;
596                try
597                {
598                        session = HibernateUtil.newSession();
599                        tx = HibernateUtil.newTransaction(session);
600                        // Load user data
601                        UserData userData = HibernateUtil.loadData(session, UserData.class,
602                                userId);
603                        LoginInfo li = createLoginInfo(session, userData, comment, true);
604                        HibernateUtil.commit(tx);
605                        SessionControl impersonated = Application.newSessionControl(
606                                getExternalClientId(), getRemoteId(), null);
607                        impersonated.loginInfo = li;
608                        return impersonated;
609                }
610                catch (BaseException ex)
611                {
612                        if (tx != null)
613                                HibernateUtil.rollback(tx);
614                        throw ex;
615                }
616                finally
617                {
618                        if (session != null)
619                                HibernateUtil.close(session);
620                }
621        }
622
623
624        /**
625         * Create a LoginInfo object and load all information that it needs.
626         */
627        private LoginInfo createLoginInfo(org.hibernate.Session session,
628                        UserData userData, String comment, boolean impersonated)
629                        throws BaseException
630        {
631                // Load the Keyring
632                LoginInfo li = new LoginInfo();
633                li.impersonated = impersonated;
634                li.keyring = new Keyring(userData.getId());
635                // If a client has been specified - check for USE permission
636                ClientData clientData = null;
637                if (getClientId() != 0)
638                {
639                        clientData = HibernateUtil.loadData(session, ClientData.class,
640                                getClientId());
641                        int permissions = li.keyring.getAllPermissions(Item.CLIENT,
642                                clientData.getOwner(), clientData.getItemKey(), clientData
643                                        .getProjectKey());
644                        if (!Permission.hasPermission(permissions, Permission.USE))
645                        {
646                                throw new PermissionDeniedException(Permission.USE,
647                                        "Client[externalId=" + clientData.getExternalId() + "]");
648                        }
649                }
650                // Load settings
651                if (clientId != 0)
652                {
653                        li.userClientSettings = loadUserClientSettings(session, userData
654                                .getId(), clientId);
655                }
656                li.userDefaultSettings = loadUserDefaultSettings(session, userData
657                        .getId());
658                // Create a new session information entry
659                SessionData sessionData = new SessionData();
660                sessionData.setUser(userData);
661                sessionData.setClient(clientData);
662                sessionData.setLoginTime(new Date());
663                sessionData.setLoginComment(comment);
664                sessionData.setRemoteId(getRemoteId());
665                sessionData.setImpersonated(impersonated);
666                HibernateUtil.saveData(session, sessionData);
667                li.sessionId = sessionData.getId();
668                li.userId = userData.getId();
669                li.userLogin = userData.getLogin();
670                li.sessionSettings = Collections
671                        .synchronizedMap(new HashMap<String, Object>());
672                return li;
673        }
674
675
676        /**
677         * Log out of Proteios. If the logged in user was impersonated the system
678         * will revert to the original user. If no user is logged in this method
679         * does nothing. If there are any {@link DbControl} objects still open, they
680         * will be closed. Changes made to items managed by those
681         * <code>DbControl</code> objects will be lost unless the items are
682         * connected to a new <code>DbControl</code> object.
683         * 
684         * @throws BaseException If there is an error
685         * @see #login(String, String, String, boolean)
686         * @see #isLoggedIn()
687         * @see #impersonateLogin(int, String)
688         * @see #isImpersonated()
689         */
690        public synchronized void logout()
691                        throws BaseException
692        {
693                updateLastAccess();
694                if (!isLoggedIn())
695                        return;
696                // Close any open DbControl objects obtained during this login
697                synchronized (dbControlCache)
698                {
699                        for (DbControl dc : dbControlCache.keySet())
700                        {
701                                dc.close();
702                        }
703                        dbControlCache.clear();
704                }
705                if (loginInfo.userId != 0)
706                {
707                        org.hibernate.Session session = null;
708                        org.hibernate.Transaction tx = null;
709                        try
710                        {
711                                session = HibernateUtil.newSession();
712                                tx = HibernateUtil.newTransaction(session);
713                                // Load and save settings
714                                UserData userData = HibernateUtil.loadData(session,
715                                        UserData.class, loginInfo.userId);
716                                if (!userData.isMultiuserAccount())
717                                {
718                                        if (clientId != 0)
719                                        {
720                                                ClientData clientData = HibernateUtil.loadData(session,
721                                                        ClientData.class, clientId);
722                                                org.hibernate.Query query = HibernateUtil
723                                                        .getPredefinedQuery(session,
724                                                                "LOAD_USER_CLIENT_SETTINGS");
725                                                query.setEntity("user", userData);
726                                                query.setEntity("client", clientData);
727                                                saveSettings(session, query,
728                                                        loginInfo.userClientSettings,
729                                                        UserClientSettingData.class, userData, clientData);
730                                        }
731                                        org.hibernate.Query query = HibernateUtil
732                                                .getPredefinedQuery(session,
733                                                        "LOAD_USER_DEFAULT_SETTINGS");
734                                        query.setEntity("user", userData);
735                                        saveSettings(session, query, loginInfo.userDefaultSettings,
736                                                UserDefaultSettingData.class, userData, null);
737                                }
738                                // Load session information and set logout time
739                                SessionData sessionData = HibernateUtil.loadData(session,
740                                        SessionData.class, loginInfo.sessionId);
741                                sessionData.setLogoutTime(new Date());
742                                HibernateUtil.commit(tx);
743                                // Remove user id from user login time map
744                                Application.removeUserLoginTime(loginInfo.userId);
745                        }
746                        catch (BaseException ex)
747                        {
748                                if (tx != null)
749                                        HibernateUtil.rollback(tx);
750                                throw ex;
751                        }
752                        finally
753                        {
754                                if (session != null)
755                                        HibernateUtil.close(session);
756                        }
757                }
758                loginInfo = null;
759        }
760
761
762        /**
763         * Checks if a user is logged in or not.
764         * 
765         * @return TRUE if a user is logged in, FALSE otherwise.
766         * @see #login(String, String, String, boolean)
767         * @see #logout()
768         * @see #getLoggedInUserId()
769         */
770        public boolean isLoggedIn()
771        {
772                updateLastAccess();
773                return loginInfo != null;
774        }
775
776
777        /**
778         * Checks if a user is logged in or not.
779         * Intended for administrative info only,
780         * therefore does not update last access
781         * time.
782         * 
783         * @return TRUE if a user is logged in, FALSE otherwise.
784         * @see #login(String, String, String, boolean)
785         * @see #logout()
786         * @see #getLoggedInUserId()
787         */
788        public boolean isLoggedInAdminInfo()
789        {
790                return loginInfo != null;
791        }
792
793
794        /**
795         * Get the id of the logged in user, or 0 if no user is logged in. Use
796         * {@link User#getById(DbControl, int)} to get the {@link User} object.
797         * 
798         * @see #login(String, String, String, boolean)
799         * @see #logout()
800         * @see #isLoggedIn()
801         */
802        public int getLoggedInUserId()
803        {
804                updateLastAccess();
805                return loginInfo == null ? 0 : loginInfo.userId;
806        }
807
808
809        /**
810         * Get the id of the logged in user, or 0 if no user is logged in. Use
811         * {@link User#getById(DbControl, int)} to get the {@link User} object.
812         * Intended for administrative info only,
813         * therefore does not update last access
814         * time.
815         * 
816         * @see #login(String, String, String, boolean)
817         * @see #logout()
818         * @see #isLoggedIn()
819         */
820        public int getLoggedInUserIdAdminInfo()
821        {
822                return loginInfo == null ? 0 : loginInfo.userId;
823        }
824
825
826        /**
827         * Check if the logged in user was impersonated by another user.
828         * 
829         * @return TRUE or FALSE
830         * @see #impersonateLogin(int, String)
831         */
832        public boolean isImpersonated()
833        {
834                updateLastAccess();
835                return loginInfo != null && loginInfo.impersonated;
836        }
837
838
839        /**
840         * Tell the core to reload the logged in users permission the next time the
841         * client application needs to check the permissions.
842         */
843        public void reloadPermissions()
844        {
845                updateLastAccess();
846                if (loginInfo != null)
847                        loginInfo.keyring.setReload(true);
848        }
849
850
851        /**
852         * Reloads user- and client-specific settings.
853         * 
854         * @param saveCurrentUserSettings Setting this to TRUE will save the current
855         *        user-specific settings before reloading them. This parameter is
856         *        ignored if <code>onlyClientDefaultSettings</code> is true (This
857         *        feature is current not implemented)
858         * @throws BaseException If the settings cannot be reloaded
859         */
860        public void reloadSettings(boolean onlyClientDefaultSettings,
861                        boolean saveCurrentUserSettings)
862                        throws BaseException
863        {
864                updateLastAccess();
865                org.hibernate.Session session = null;
866                org.hibernate.Transaction tx = null;
867                try
868                {
869                        session = HibernateUtil.newSession();
870                        tx = HibernateUtil.newTransaction(session);
871                        if (getClientId() != 0)
872                        {
873                                clientDefaultSettings = loadClientDefaultSettings(session,
874                                        getClientId());
875                        }
876                        if (!onlyClientDefaultSettings && isLoggedIn())
877                        {
878                                if (saveCurrentUserSettings)
879                                {
880                                        // TODO - implement the feature of saving current user
881                                        // settings
882                                }
883                                if (getClientId() != 0)
884                                {
885                                        loginInfo.userClientSettings = loadUserClientSettings(
886                                                session, getLoggedInUserId(), getClientId());
887                                }
888                                loginInfo.userDefaultSettings = loadUserDefaultSettings(
889                                        session, getLoggedInUserId());
890                        }
891                }
892                finally
893                {
894                        if (tx != null)
895                                HibernateUtil.commit(tx);
896                        if (session != null)
897                                HibernateUtil.close(session);
898                }
899        }
900
901
902        /**
903         * Get the id of the active project, or 0 if no project is active. Use
904         * {@link Project#getById(DbControl, int)} to get the {@link Project}
905         * object.
906         * 
907         * @see #setActiveProject(Project)
908         */
909        public int getActiveProjectId()
910        {
911                updateLastAccess();
912                return loginInfo == null ? 0 : loginInfo.activeProjectId;
913        }
914
915
916        /**
917         * Set the active project. When a project is active queries will by default
918         * only load items shared to the that project, and new {@link Shareable}
919         * items will automatically be shared to the active project with delete
920         * permission. The active project doesn't affect already existing items.
921         * 
922         * @param project The project to make active
923         * @throws PermissionDeniedException If the logged in user doesn't have
924         *         {@link Permission#USE} permission for the project
925         * @throws BaseException If there is another error
926         * @see #getActiveProjectId()
927         */
928        public synchronized void setActiveProject(Project project)
929                        throws PermissionDeniedException, BaseException
930        {
931                updateLastAccess();
932                if (loginInfo != null)
933                {
934                        if (project == null)
935                        {
936                                loginInfo.keyring.setActiveProject(null);
937                                loginInfo.activeProjectId = 0;
938                                loginInfo.projectKeyId = 0;
939                        }
940                        else
941                        {
942                                project.checkPermission(Permission.READ);
943                                loginInfo.keyring.setActiveProject(project.getData());
944                                loginInfo.activeProjectId = project.getId();
945                                if (project.hasPermission(Permission.USE))
946                                {
947                                        ProjectPermissions pp = new ProjectPermissions();
948                                        pp.setPermissions(project, EnumSet.range(Permission.READ,
949                                                Permission.DELETE));
950                                        loginInfo.projectKeyId = ProjectKey.getNewOrExistingId(
951                                                null, pp.getProjectPermissions());
952                                }
953                                else
954                                {
955                                        loginInfo.projectKeyId = 0;
956                                }
957                        }
958                }
959        }
960
961
962        /**
963         * Get the id of the {@link ProjectKey} that can be used to share an item to
964         * the active project with full permission, or 0 if no project is active or
965         * the logged in user only has read permission to the active project. Use
966         * {@link ProjectKey#getById(DbControl, int)} to get the {@link ProjectKey}
967         * object.
968         * 
969         * @see #getActiveProjectId()
970         * @see #setActiveProject(Project)
971         */
972        public int getProjectKeyId()
973        {
974                return loginInfo == null ? 0 : loginInfo.projectKeyId;
975        }
976
977
978        /**
979         * Check if the logged in user has the specified system permission.
980         * 
981         * @param permission One of the system permission constants defined by the
982         *        {@link Permission} enumeration
983         * @return TRUE if the logged in user has the permission, FALSE otherwise
984         */
985        public boolean hasSystemPermission(Permission permission)
986        {
987                return hasPermission(permission, Item.SYSTEM);
988        }
989
990
991        /**
992         * Check if the logged in user has the specfied permission for all item of
993         * the specified type.
994         * 
995         * @param permission One of the permissions constants defined by the
996         *        {@link Permission} enumeration
997         * @param itemType One of the item constants defined by the {@link Item}
998         *        enumeration
999         * @return TRUE if the logged in user has the permission, FALSE otherwise
1000         */
1001        public boolean hasPermission(Permission permission, Item itemType)
1002        {
1003                return Permission.hasPermission(getRolePermissions(itemType),
1004                        permission);
1005        }
1006
1007
1008        /**
1009         * Get all role-based permissions for the specified type of items.
1010         */
1011        int getRolePermissions(Item itemType)
1012        {
1013                return loginInfo == null ? 0 : loginInfo.keyring
1014                        .getRolePermissions(itemType);
1015        }
1016
1017
1018        /**
1019         * Get the shared permissions for a {@link ShareableData} object.
1020         */
1021        int getSharedPermissions(ShareableData shareableData)
1022        {
1023                return loginInfo == null ? 0 : loginInfo.keyring
1024                        .getSharedPermissions(shareableData);
1025        }
1026
1027
1028        /**
1029         * Get the combined permissions for an item of the specified type, owned by
1030         * the specified user and shared with the specified item and project keys.
1031         */
1032        int getAllPermissions(Item itemType, UserData owner, ItemKeyData itemKey,
1033                        ProjectKeyData projectKey)
1034        {
1035                return loginInfo == null ? 0 : loginInfo.keyring.getAllPermissions(
1036                        itemType, owner, itemKey, projectKey);
1037        }
1038
1039
1040        /**
1041         * Same as
1042         * {@link #getAllPermissions(Item, UserData, ItemKeyData, ProjectKeyData)}
1043         */
1044        int getAllPermissions(SharedData sharedData)
1045        {
1046                return getAllPermissions(Item.fromDataObject(sharedData), sharedData
1047                        .getOwner(), sharedData.getItemKey(), sharedData.getProjectKey());
1048        }
1049
1050
1051        /**
1052         * Check if the logged in user is a member, directly or indirectly, of the
1053         * specified group.
1054         * 
1055         * @param group The group to check
1056         * @return TRUE if the user is a member, FALSE otherwise
1057         */
1058        public boolean isMemberOf(Group group)
1059        {
1060                return loginInfo == null ? false : loginInfo.keyring.getGroups()
1061                        .contains(group.getId());
1062        }
1063
1064
1065        /**
1066         * Check if the logged in user is a member of the specified role.
1067         * 
1068         * @param role The role to check
1069         * @return TRUE if the user is a member, FALSE otherwise
1070         */
1071        public boolean isMemberOf(Role role)
1072        {
1073                return loginInfo == null ? false : loginInfo.keyring.getRoles()
1074                        .contains(role.getId());
1075        }
1076
1077
1078        /**
1079         * Check if the logged in user is a member, directly or indirectly, of the
1080         * specified project.
1081         * 
1082         * @param project The project to check
1083         * @return TRUE if the user is a member, FALSE otherwise
1084         */
1085        public boolean isMemberOf(Project project)
1086        {
1087                return loginInfo == null ? false : loginInfo.keyring.getProjects()
1088                        .keySet().contains(project.getId());
1089        }
1090
1091
1092        int getProjectPermission(Project project)
1093        {
1094                return loginInfo == null ? 0 : IntegerUtil.getInt(loginInfo.keyring
1095                        .getProjects().get(project.getId()));
1096        }
1097
1098
1099        /**
1100         * Check if the logged in user is a friend of of the specified user. A users
1101         * is a friend if the logged in user is a member, directly or indirectly, of
1102         * a group where the other user is direct member.
1103         * 
1104         * @param user The user to check
1105         * @return TRUE if the user is a friend, FALSE otherwise
1106         */
1107        public boolean isFriendOf(User user)
1108        {
1109                return loginInfo == null ? false : loginInfo.keyring.getUsers()
1110                        .contains(user.getId());
1111        }
1112
1113
1114        /**
1115         * Get the id of all roles where the logged in user is a member.
1116         * 
1117         * @return A <code>Set</code> containing role id:s
1118         */
1119        public Set<Integer> getRoles()
1120        {
1121                return loginInfo == null ? null : loginInfo.keyring.getRoles();
1122        }
1123
1124
1125        /**
1126         * Get the id of all groups where the logged in user is a member, directly
1127         * or indirectly.
1128         * 
1129         * @return A <code>Set</code> containing group id:s
1130         */
1131        public Set<Integer> getGroups()
1132        {
1133                return loginInfo == null ? null : loginInfo.keyring.getGroups();
1134        }
1135
1136
1137        /**
1138         * Get the id of all friends to logged in user. A users is a friend if the
1139         * logged in user is a member, directly or indirectly, of a group where the
1140         * other user is direct member.
1141         * 
1142         * @return A <code>Set</code> containing user id:s
1143         */
1144        public Set<Integer> getFriends()
1145        {
1146                return loginInfo == null ? null : loginInfo.keyring.getUsers();
1147        }
1148
1149
1150        /**
1151         * Get the id of all projects where the logged in user is a member, directly
1152         * or indirectly.
1153         * 
1154         * @return A <code>Set</code> containing project id:s
1155         */
1156        public Set<Integer> getProjects()
1157        {
1158                return loginInfo == null ? null : loginInfo.keyring.getProjects()
1159                        .keySet();
1160        }
1161
1162
1163        /**
1164         * Get the id of all item keys where the logged in user has a permission.
1165         * 
1166         * @return A <code>Set</code> containing item key id:s
1167         */
1168        public Set<Integer> getItemKeys()
1169        {
1170                return loginInfo == null ? null : loginInfo.keyring.getItemKeys();
1171        }
1172
1173
1174        /**
1175         * Get the id of all project keys where the logged in user has a permission.
1176         * 
1177         * @return A <code>Set</code> containing project key id:s
1178         */
1179        public Set<Integer> getProjectKeys()
1180        {
1181                return loginInfo == null ? null : loginInfo.keyring.getProjectKeys();
1182        }
1183
1184
1185        /**
1186         * Get the value of a session setting with the specified name.
1187         * 
1188         * @param name The name of the setting
1189         * @return A object with the value of the setting, or null if no setting is
1190         *         found
1191         */
1192        public Object getSessionSetting(String name)
1193        {
1194                return loginInfo == null ? null : loginInfo.sessionSettings.get(name);
1195        }
1196
1197
1198        /**
1199         * Set the value of a session setting.
1200         * 
1201         * @param name The name of the setting
1202         * @param value The new value of the setting, or null to remove the setting
1203         * @return The old value of the setting, or null if it did not exist
1204         */
1205        public Object setSessionSetting(String name, Object value)
1206        {
1207                if (loginInfo == null || loginInfo.sessionSettings == null)
1208                        return null;
1209                if (value == null)
1210                {
1211                        return loginInfo.sessionSettings.remove(name);
1212                }
1213                return loginInfo.sessionSettings.put(name, value);
1214        }
1215
1216
1217        /**
1218         * Get the value of the UserClientSetting with the specified name. If no
1219         * user is logged in null is returned.
1220         * 
1221         * @param name The name of the setting
1222         * @return A string with the value of the setting, or null if no setting is
1223         *         found
1224         */
1225        public String getUserClientSetting(String name)
1226        {
1227                if (loginInfo == null || loginInfo.userClientSettings == null)
1228                        return null;
1229                SettingInfo si = loginInfo.userClientSettings.get(name);
1230                return si == null ? null : si.value;
1231        }
1232
1233
1234        /**
1235         * Set the value of a UserClientSetting.
1236         * 
1237         * @param name The name of the setting
1238         * @param value The new value of the setting, or null to remove the setting
1239         * @return The old value of the setting, or null if it did not exist or no
1240         *         user is logged in
1241         */
1242        public String setUserClientSetting(String name, String value)
1243        {
1244                if (loginInfo == null || loginInfo.userClientSettings == null)
1245                        return null;
1246                SettingInfo si = loginInfo.userClientSettings.put(name,
1247                        new SettingInfo(value, true));
1248                return si == null ? null : si.value;
1249        }
1250
1251
1252        /**
1253         * Get the value of the UserDefaultSetting with the specified name.
1254         * 
1255         * @param name The name of the setting
1256         * @return A string with the value of the setting, or null if no setting is
1257         *         found
1258         */
1259        public String getUserDefaultSetting(String name)
1260        {
1261                if (loginInfo == null || loginInfo.userDefaultSettings == null)
1262                        return null;
1263                SettingInfo si = loginInfo.userDefaultSettings.get(name);
1264                return si == null ? null : si.value;
1265        }
1266
1267
1268        /**
1269         * Set the value of a UserDefaultSetting.
1270         * 
1271         * @param name The name of the setting
1272         * @param value The new value of the setting, or null to remove the setting
1273         * @return The old value of the setting, or null if it did not exist or no
1274         *         user is logged in
1275         */
1276        public String setUserDefaultSetting(String name, String value)
1277        {
1278                if (loginInfo == null || loginInfo.userDefaultSettings == null)
1279                        return null;
1280                SettingInfo si = loginInfo.userDefaultSettings.put(name,
1281                        new SettingInfo(value, true));
1282                return si == null ? null : si.value;
1283        }
1284
1285
1286        /**
1287         * Get the value of the ClientDefaultSetting with the specified name.
1288         * 
1289         * @param name The name of the setting
1290         * @return A string with the value of the setting, or null if no setting is
1291         *         found
1292         */
1293        public String getClientDefaultSetting(String name)
1294        {
1295                if (clientDefaultSettings == null)
1296                        return null;
1297                SettingInfo si = clientDefaultSettings.get(name);
1298                return si == null ? null : si.value;
1299        }
1300
1301
1302        /**
1303         * Copy settings from a List to a Map. The key is the name of the setting ({@link SettingData#getName()})
1304         * and the value is a {@link SettingInfo} object.
1305         */
1306        private Map<String, SettingInfo> listToMap(
1307                        List<? extends SettingData> settings)
1308        {
1309                Map<String, SettingInfo> map = Collections
1310                        .synchronizedMap(new HashMap<String, SettingInfo>(settings.size()));
1311                for (SettingData s : settings)
1312                {
1313                        map.put(s.getName(), new SettingInfo(s.getValue(), false));
1314                }
1315                return map;
1316        }
1317
1318
1319        /**
1320         * Load client applications default settings.
1321         */
1322        private Map<String, SettingInfo> loadClientDefaultSettings(
1323                        org.hibernate.Session session, int clientId)
1324        {
1325                org.hibernate.Query query = HibernateUtil.getPredefinedQuery(session,
1326                        "LOAD_CLIENT_DEFAULT_SETTINGS");
1327                /*
1328                 * SELECT s FROM ClientDefaultSettingData s WHERE s.client = :client
1329                 */
1330                query.setInteger("client", clientId);
1331                return listToMap(HibernateUtil.loadList(ClientDefaultSettingData.class,
1332                        query));
1333        }
1334
1335
1336        /**
1337         * Load users default settings.
1338         */
1339        private Map<String, SettingInfo> loadUserDefaultSettings(
1340                        org.hibernate.Session session, int userId)
1341        {
1342                org.hibernate.Query query = HibernateUtil.getPredefinedQuery(session,
1343                        "LOAD_USER_DEFAULT_SETTINGS");
1344                /*
1345                 * SELECT s FROM UserDefaultSettingData s WHERE s.user = :user
1346                 */
1347                query.setInteger("user", userId);
1348                return listToMap(HibernateUtil.loadList(UserDefaultSettingData.class,
1349                        query));
1350        }
1351
1352
1353        /**
1354         * Load users client-specific settings.
1355         */
1356        private Map<String, SettingInfo> loadUserClientSettings(
1357                        org.hibernate.Session session, int userId, int clientId)
1358        {
1359                org.hibernate.Query query = HibernateUtil.getPredefinedQuery(session,
1360                        "LOAD_USER_CLIENT_SETTINGS");
1361                /*
1362                 * SELECT s FROM UserClientSettingData s WHERE s.user = :user AND
1363                 * s.client = client
1364                 */
1365                query.setInteger("user", userId);
1366                query.setInteger("client", clientId);
1367                return listToMap(HibernateUtil.loadList(UserClientSettingData.class,
1368                        query));
1369        }
1370
1371
1372        /**
1373         * Save settings when a user logs out. This method will delete settings that
1374         * have been deleted, update changed settings and create new settings.
1375         * 
1376         * @param session The Hibernate session
1377         * @param query A Hibernate query to load the settings already in the
1378         *        database
1379         * @param settings The settings to save
1380         * @param dataClass The <code>Class</code> of the settings, should be one
1381         *        of {@link UserDefaultSettingData} or {@link UserClientSettingData}
1382         * @param userData The {@link UserData} object for the logged in user
1383         * @param clientData The {@link ClientData} object for the client
1384         *        application
1385         */
1386        private <I extends SettingData> void saveSettings(
1387                        org.hibernate.Session session, org.hibernate.Query query,
1388                        Map<String, SettingInfo> settings, Class<I> dataClass,
1389                        UserData userData, ClientData clientData)
1390                        throws BaseException
1391        {
1392                // Load the existing settings from the database
1393                List<I> dbSettings = HibernateUtil.loadList(dataClass, query);
1394                // Iterator the existing settings and check what has happened to them
1395                for (SettingData s : dbSettings)
1396                {
1397                        // Get the info, use remove so we know which ones have been
1398                        // processed
1399                        SettingInfo si = settings.remove(s.getName());
1400                        // Check if the setting has been modified
1401                        if (si != null && si.modified)
1402                        {
1403                                // A null value --> delete the setting
1404                                if (si.value == null)
1405                                {
1406                                        HibernateUtil.deleteData(session, s);
1407                                }
1408                                else
1409                                {
1410                                        s.setValue(si.value);
1411                                }
1412                        }
1413                }
1414                // The settings map now contain only those setting not found in the
1415                // database
1416                try
1417                {
1418                        for (Map.Entry<String, SettingInfo> entry : settings.entrySet())
1419                        {
1420                                SettingInfo si = entry.getValue();
1421                                // If the setting has been modified, it should be created
1422                                if (si != null && si.modified)
1423                                {
1424                                        I s = dataClass.newInstance();
1425                                        s.setName(entry.getKey());
1426                                        s.setValue(si.value);
1427                                        if (s instanceof UserDefaultSettingData)
1428                                        {
1429                                                ((UserDefaultSettingData) s).setUser(userData);
1430                                        }
1431                                        if (s instanceof UserClientSettingData)
1432                                        {
1433                                                ((UserClientSettingData) s).setUser(userData);
1434                                                ((UserClientSettingData) s).setClient(clientData);
1435                                        }
1436                                        HibernateUtil.saveData(session, s);
1437                                }
1438                        }
1439                }
1440                catch (InstantiationException ex)
1441                {
1442                        throw new BaseException(ex);
1443                }
1444                catch (IllegalAccessException ex)
1445                {
1446                        throw new BaseException(ex);
1447                }
1448        }
1449
1450
1451        /**
1452         * Clean up if a bad client application forgets to logout.
1453         */
1454        @Override
1455        protected void finalize()
1456                        throws Throwable
1457        {
1458                try
1459                {
1460                        if (isLoggedIn())
1461                                logout();
1462                }
1463                catch (BaseException ex)
1464                {
1465                        log.warn("Exception during SessionControl.finalize()", ex);
1466                }
1467                super.finalize();
1468        }
1469
1470        /**
1471         * Internal class to hold information about the logged in user.
1472         */
1473        private static class LoginInfo
1474        {
1475                /**
1476                 * If the logged in user was logged in with the
1477                 * {@link SessionControl#impersonateLogin(int, String)}
1478                 */
1479                private boolean impersonated;
1480                /**
1481                 * The id of the {@link SessionData}.
1482                 */
1483                private int sessionId;
1484                /**
1485                 * The id of the {@link UserData} object for the logged in user.
1486                 */
1487                private int userId;
1488                /**
1489                 * The login property of the logged in user.
1490                 */
1491                private String userLogin;
1492                /**
1493                 * The id of the {@link ProjectData} object of the active project.
1494                 */
1495                private int activeProjectId;
1496                /**
1497                 * The id of the {@link ProjectKeyData} object that shares an item with
1498                 * full permission to the active project.
1499                 */
1500                private int projectKeyId;
1501                /**
1502                 * The {@link Keyring} of the logged in user.
1503                 */
1504                private Keyring keyring;
1505                /**
1506                 * Stores name and value from {@link UserClientSettingData}.
1507                 */
1508                private Map<String, SettingInfo> userClientSettings;
1509                /**
1510                 * Stores name and value from {@link UserDefaultSettingData}.
1511                 */
1512                private Map<String, SettingInfo> userDefaultSettings;
1513                /**
1514                 * Stores name and value of session settings.
1515                 */
1516                private Map<String, Object> sessionSettings;
1517
1518
1519                private LoginInfo()
1520                {}
1521        }
1522
1523        /**
1524         * Internal class to hold information about a setting
1525         */
1526        private static class SettingInfo
1527        {
1528                /**
1529                 * The value of the setting, null indicates removal
1530                 */
1531                private final String value;
1532                /**
1533                 * If the setting has been modified while the user was logged in or not
1534                 */
1535                private final boolean modified;
1536
1537
1538                private SettingInfo(String value, boolean modified)
1539                {
1540                        this.value = value;
1541                        this.modified = modified;
1542                }
1543        }
1544}