com.flexive.core.security.UserTicketStore.java Source code

Java tutorial

Introduction

Here is the source code for com.flexive.core.security.UserTicketStore.java

Source

/***************************************************************
 *  This file is part of the [fleXive](R) framework.
 *
 *  Copyright (c) 1999-2014
 *  UCS - unique computing solutions gmbh (http://www.ucs.at)
 *  All rights reserved
 *
 *  The [fleXive](R) project is free software; you can redistribute
 *  it and/or modify it under the terms of the GNU Lesser General Public
 *  License version 2.1 or higher as published by the Free Software Foundation.
 *
 *  The GNU Lesser General Public License can be found at
 *  http://www.gnu.org/licenses/lgpl.html.
 *  A copy is found in the textfile LGPL.txt and important notices to the
 *  license from the author are found in LICENSE.txt distributed with
 *  these libraries.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  For further information about UCS - unique computing solutions gmbh,
 *  please see the company website: http://www.ucs.at
 *
 *  For further information about [fleXive](R), please see the
 *  project website: http://www.flexive.org
 *
 *
 *  This copyright notice MUST APPEAR in all copies of the file!
 ***************************************************************/
package com.flexive.core.security;

import com.flexive.core.structure.StructureLoader;
import com.flexive.shared.CacheAdmin;
import com.flexive.shared.EJBLookup;
import com.flexive.shared.FxContext;
import com.flexive.shared.FxSharedUtils;
import com.flexive.shared.cache.FxCacheException;
import com.flexive.shared.exceptions.FxApplicationException;
import com.flexive.shared.exceptions.FxLoadException;
import com.flexive.shared.exceptions.FxNoAccessException;
import com.flexive.shared.exceptions.FxNotFoundException;
import com.flexive.shared.interfaces.AccountEngine;
import com.flexive.shared.mbeans.FxCacheMBean;
import com.flexive.shared.security.*;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.security.auth.Subject;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * Store for all currently logged in user(ticket)s
 *
 * @author Gregor Schober (gregor.schober@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 */
public class UserTicketStore {

    private static final String KEY_TICKET = "Ticket";
    private static final Log LOG = LogFactory.getLog(UserTicketStore.class);

    /**
     * Helper class
     */
    static class SubjectWithPath {
        Subject subject;
        String path;
    }

    /**
     * Stores a subject (and its ticket) for the current session.
     *
     * @param sub the subject to store
     */
    public static void storeSubject(Subject sub) {
        FxCacheMBean cache = CacheAdmin.getInstance();
        FxContext si = FxContext.get();
        try {
            cache.put(getCacheRoot(si), KEY_TICKET, sub);
            if (LOG.isDebugEnabled())
                LOG.debug("Storing at [" + getCacheRoot(si) + "]: [" + sub + "]");
        } catch (FxCacheException exc) {
            LOG.error("Failed to store ticket in UserTickerStore: " + exc.getMessage(), exc);
            System.err.println("Failed to store ticket in UserTickerStore: " + exc.getMessage());
        }
    }

    /**
     * Stores a subject (and its ticket) for the given path
     *
     * @param sub the subject to store
     */
    private static void storeSubject(SubjectWithPath sub) {
        FxCacheMBean cache = CacheAdmin.getInstance();
        try {
            cache.put(CacheAdmin.ROOT_USERTICKETSTORE + "/" + sub.path, KEY_TICKET, sub.subject);
            if (LOG.isDebugEnabled())
                LOG.debug("Storing at [" + CacheAdmin.ROOT_USERTICKETSTORE + "/" + sub.path + "]: [" + sub.subject
                        + "]");
        } catch (FxCacheException exc) {
            LOG.error("Failed to store ticket in UserTickerStore: " + exc.getMessage(), exc);
        }
    }

    /**
     * Removes the subject for the current session
     */
    public static void removeSubject() {
        FxCacheMBean cache = CacheAdmin.getInstance();
        FxContext si = FxContext.get();
        try {
            cache.remove(getCacheRoot(si), KEY_TICKET);
            if (LOG.isDebugEnabled())
                LOG.debug("Removing all subjects at for [" + getCacheRoot(si) + "]");
        } catch (FxCacheException exc) {
            LOG.error("Failed to clear session in UserTickerStore: " + exc.getMessage(), exc);
        }
    }

    /**
     * Computes the ticket store path of a session.
     *
     * @param si the session info
     * @return the path of the ticket for the session
     */
    protected static String getCacheRoot(final FxContext si) {
        return CacheAdmin.ROOT_USERTICKETSTORE + "/" + si.getApplicationId() + (si.isWebDAV() ? "_WebDav" : "")
                + ":" + si.getSessionId();
    }

    /**
     * Gets the ticket of the current request.
     *
     * @param si the request information
     * @return the ticket of the current request.
     */
    protected static Subject getSubject(FxContext si) {
        FxCacheMBean cache = CacheAdmin.getInstance();
        Subject sub = null;
        try {
            sub = (Subject) cache.get(getCacheRoot(si), KEY_TICKET);
            if (LOG.isDebugEnabled())
                LOG.debug("getSubject returned for [" + getCacheRoot(si) + "]: " + sub);
        } catch (FxCacheException exc) {
            LOG.error("Failed to get ticket from UserTicketStore Cache: " + exc.getMessage(), exc);
        } catch (Exception exc) {
            LOG.error("Failed to get ticket from UserTicketStore: " + exc.getMessage(), exc);
        }
        return sub;
    }

    /**
     * Returns all subjects(tickets) in the store that match at least one of the given parameters.
     *
     * @param userId  the user id to look for
     * @param groupId the group ids to match, if the ticket is a member of at least
     *                one group it is added to the result
     * @param acls    the acls
     * @return the matching subjects
     */
    private static SubjectWithPath[] getSubjects(Long userId, long[] groupId, long[] acls) {
        FxCacheMBean cache = CacheAdmin.getInstance();
        try {
            List<SubjectWithPath> result = new ArrayList<SubjectWithPath>(50);
            Set aSet = cache.getChildrenNames(CacheAdmin.ROOT_USERTICKETSTORE);
            if (aSet == null)
                return new SubjectWithPath[0];
            UserTicketImpl aTicket;
            Subject sub;
            for (Object subPath : aSet) {
                sub = (Subject) cache.get(CacheAdmin.ROOT_USERTICKETSTORE + "/" + subPath, KEY_TICKET);
                if (sub == null)
                    continue;
                try {
                    aTicket = (UserTicketImpl) FxDefaultLogin.getUserTicket(sub);
                } catch (FxNotFoundException exc) {
                    LOG.error("No UserTicket found in Subject " + sub, exc);
                    continue;
                }
                boolean match = userId != null && aTicket.getUserId() == userId;
                match = match || aTicket.isInAtLeastOneGroup(groupId);
                match = match || aTicket.hasAtLeastOneACL(acls);
                if (!match)
                    continue;
                SubjectWithPath sp = new SubjectWithPath();
                sp.subject = sub;
                sp.path = String.valueOf(subPath);
                result.add(sp);
            }
            return result.toArray(new SubjectWithPath[result.size()]);
        } catch (FxCacheException exc) {
            LOG.error("Failed to examine tickets: " + exc.getMessage(), exc);
            return new SubjectWithPath[0];
        }
    }

    /**
     * Gets the user ticket for the current request.
     *
     * @return the user ticket for the current request.
     */
    public static UserTicket getTicket() {
        return getTicket(true);
    }

    /**
     * Gets the user ticket for the current request.
     *
     * @param refreshIfDirty true: syncs the ticket with the database if it is dirty
     * @return the user ticket for the current request.
     */
    public static UserTicket getTicket(boolean refreshIfDirty) {
        FxContext si = FxContext.get();
        try {
            Subject sub = getSubject(si);
            UserTicketImpl ticket;
            if (sub == null) {
                ticket = (UserTicketImpl) UserTicketImpl.getGuestTicket();
            } else {
                ticket = (UserTicketImpl) FxDefaultLogin.getUserTicket(sub);
                // Check dirty flag and sync with database if needed
                if (refreshIfDirty && ticket.isDirty()) {
                    ticket = (UserTicketImpl) getUserTicket(ticket.getLoginName());
                    FxDefaultLogin.updateUserTicket(sub, ticket);
                    storeSubject(sub);
                }
            }
            return ticket;
        } catch (Exception exc) {
            LOG.fatal(exc, exc);
            return UserTicketImpl.getGuestTicket();
        }
    }

    public static UserTicket getUserTicket(String loginName) throws FxApplicationException {
        FxContext ri = FxContext.get();
        ri.runAsSystem();
        try {
            AccountEngine ae = EJBLookup.getAccountEngine();
            Account acc = ae.load(loginName);
            final List<UserGroup> groups = ae.getGroups(acc.getId());
            final List<Role> roleList = ae.getRoles(acc.getId(), RoleLoadMode.ALL);
            final Role[] roles = roleList.toArray(new Role[roleList.size()]);
            final List<ACLAssignment> assignmentList = ae.loadAccountAssignments(acc.getId());
            ACLAssignment[] aad = assignmentList.toArray(new ACLAssignment[assignmentList.size()]);
            final List<Long> groupIds = FxSharedUtils.getSelectableObjectIdList(groups);
            return new UserTicketImpl(ri.getApplicationId(), ri.isWebDAV(), acc,
                    ArrayUtils.toPrimitive(groupIds.toArray(new Long[groupIds.size()])), roles, aad,
                    acc.getLanguage());
        } catch (FxNoAccessException exc) {
            // This should NEVER happen since we are running as system
            throw new FxLoadException(LOG, exc);
        } finally {
            ri.stopRunAsSystem();
        }
    }

    /**
     * Flags all active UserTickets with a given user id as dirty, which will
     * force them to sync with the database upon the next access.
     *
     * @param userId the user id, or null
     */
    public static void flagDirtyHavingUserId(Long userId) {
        flagDirtyHaving(userId, null, null);

    }

    /**
     * Flags all active UserTickets with a given ACL id as dirty, which will
     * force them to sync with the database upon the next access.
     *
     * @param aclId the acl
     */
    public static void flagDirtyHavingACL(long aclId) {
        flagDirtyHaving(null, null, aclId);
    }

    /**
     * Flags all active UserTickets with a given group id as dirty, which will
     * force them to sync with the database upon the next access.
     *
     * @param groupId the group id
     */
    public static void flagDirtyHavingGroupId(Long groupId) {
        flagDirtyHaving(null, groupId, null);
    }

    /**
     * Flags all active UserTickets with a given id or acl as dirty, which will
     * force them to sync with the database upon the next access.
     *
     * @param userId  the user id, or null
     * @param aclId   a acl, or null
     * @param groupId a group id, or null
     */
    private static void flagDirtyHaving(Long userId, Long groupId, Long aclId) {
        SubjectWithPath[] subs = getSubjects(userId, groupId == null ? null : new long[] { groupId },
                aclId == null ? null : new long[] { aclId });
        if (LOG.isDebugEnabled())
            LOG.debug("Flagging " + subs.length + " dirty subjects with userId=" + userId + ", groupId=" + groupId
                    + ", aclId=" + aclId);
        for (SubjectWithPath sub : subs) {
            if (LOG.isDebugEnabled())
                LOG.debug("Dirty subject: " + sub);
            try {
                final UserTicketImpl ticket = (UserTicketImpl) FxDefaultLogin.getUserTicket(sub.subject);
                if (!ticket.isDirty()) {
                    // Flag as dirty
                    ticket.setDirty(true);
                    FxDefaultLogin.updateUserTicket(sub.subject, ticket);
                    // Write back to the cluster cache
                    storeSubject(sub);
                }
            } catch (Exception exc) {
                LOG.fatal("Subject without UserTicket [" + sub + "], dirty flag update skipped", exc);
            }
        }
        if (LOG.isDebugEnabled())
            LOG.debug("Done flagging dirty.");
        final UserTicket currentUserTicket = FxContext.getUserTicket();
        if ((userId != null && userId == currentUserTicket.getUserId())
                || (groupId != null && currentUserTicket.isInGroup(groupId)) || aclId != null) {
            // update current user's ticket directly from the DB 
            try {
                final UserTicket ticket = getUserTicket(FxContext.getUserTicket().getLoginName());
                // remember current language (FX-329)
                ticket.setLanguage(FxContext.getUserTicket().getLanguage());
                // update request ticket
                FxContext.get().setTicket(ticket);
            } catch (FxApplicationException e) {
                throw e.asRuntimeException();
            }
        }
        // need to flag environment as dirty to force guest ticket updates (FX-349)
        StructureLoader.updateEnvironmentTimestamp();
    }

    /**
     * Removes all data matching a given user id.
     *
     * @param userId        the user id
     * @param applicationId the application id, may be null for all applications
     * @return amount of deleted entries
     */
    public static int removeUserId(final long userId, final String applicationId) {
        if (LOG.isDebugEnabled())
            LOG.debug("Removing userId " + userId + " with applicationId " + applicationId);
        FxCacheMBean cache = CacheAdmin.getInstance();
        List<String> remove = new ArrayList<String>(100);
        // Find all matching nodes
        try {
            Set aSet = cache.getChildrenNames(CacheAdmin.ROOT_USERTICKETSTORE);
            if (aSet == null)
                return 0;
            UserTicket aTicket;
            Subject sub;
            for (Object subPath : aSet) {
                String fullNodeName = CacheAdmin.ROOT_USERTICKETSTORE + "/" + subPath;
                sub = (Subject) cache.get(fullNodeName, KEY_TICKET);
                if (sub == null)
                    continue;
                try {
                    aTicket = FxDefaultLogin.getUserTicket(sub);
                } catch (FxNotFoundException exc) {
                    LOG.error("No UserTicket found in Subject " + sub);
                    continue;
                }
                if (aTicket.getUserId() != userId)
                    continue;
                if (applicationId != null && !aTicket.getApplicationId().equals(applicationId))
                    continue;
                remove.add(fullNodeName);
            }
        } catch (FxCacheException exc) {
            LOG.error("Failed to examine tickets: " + exc.getMessage(), exc);
            return 0;
        }
        // Clear matching nodes
        int count = 0;
        for (String node : remove) {
            try {
                cache.remove(node);
                count++;
            } catch (Exception exc) {
                LOG.error("Failed to remove node [" + node + "] for userid:" + userId, exc);
            }
        }
        // Return amount of deleted entries
        return count;
    }

    /**
     * Returns all UserTickets currently in the store.
     *
     * @return all UserTickets that are in the store
     */
    public static List<UserTicket> getTickets() {
        FxCacheMBean cache = CacheAdmin.getInstance();
        try {
            List<UserTicket> result = new ArrayList<UserTicket>(1000);
            Set aSet = cache.getChildrenNames(CacheAdmin.ROOT_USERTICKETSTORE);
            if (aSet == null)
                return new ArrayList<UserTicket>(0);
            UserTicketImpl aTicket;
            Subject sub;
            for (Object subPath : aSet) {
                sub = (Subject) cache.get(CacheAdmin.ROOT_USERTICKETSTORE + "/" + subPath, KEY_TICKET);
                if (sub == null)
                    continue;
                try {
                    aTicket = (UserTicketImpl) FxDefaultLogin.getUserTicket(sub);
                } catch (FxNotFoundException exc) {
                    LOG.error("No UserTicket found in Subject " + sub, exc);
                    continue;
                }
                result.add(aTicket);
            }
            return result;
        } catch (FxCacheException exc) {
            LOG.error("Failed to examine tickets: " + exc.getMessage(), exc);
            return new ArrayList<UserTicket>(0);
        }
    }

}