org.pentaho.platform.engine.security.SecurityHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.platform.engine.security.SecurityHelper.java

Source

/*
 * This program is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License, version 2 as published by the Free Software
 * Foundation.
 *
 * You should have received a copy of the GNU General Public License along with this
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/gpl-2.0.html
 * or from the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * This program 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.
 *
 *
 * Copyright 2006 - 2013 Pentaho Corporation.  All rights reserved.
 */

package org.pentaho.platform.engine.security;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.platform.api.engine.IAclHolder;
import org.pentaho.platform.api.engine.IAclVoter;
import org.pentaho.platform.api.engine.IParameterProvider;
import org.pentaho.platform.api.engine.IPentahoAclEntry;
import org.pentaho.platform.api.engine.IPentahoSession;
import org.pentaho.platform.api.engine.ISecurityHelper;
import org.pentaho.platform.api.engine.ISolutionFile;
import org.pentaho.platform.api.engine.IUserRoleListService;
import org.pentaho.platform.api.mt.ITenant;
import org.pentaho.platform.api.mt.ITenantedPrincipleNameResolver;
import org.pentaho.platform.engine.core.system.PentahoSessionHolder;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.engine.core.system.StandaloneSession;
import org.pentaho.platform.engine.core.system.UserSession;
import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.context.SecurityContext;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
import org.springframework.security.userdetails.User;
import org.springframework.security.userdetails.UserDetailsService;

import java.util.concurrent.Callable;

/**
 * A utility class with several methods that are used to either bind the <tt>Authentication</tt> to the
 * <tt>IPentahoSession</tt>, retrieve the <tt>Authentication</tt> from the <tt>IPentahoSession</tt>, and other various
 * helper functions.
 *
 * @author mbatchel
 */

public class SecurityHelper implements ISecurityHelper {

    private static final Log logger = LogFactory.getLog(SecurityHelper.class);

    /**
     * The default instance of this singleton
     */
    private static ISecurityHelper instance = new SecurityHelper();
    private static ISecurityHelper mockInstance;

    private ITenantedPrincipleNameResolver tenantedUserNameUtils;
    private IAclVoter aclVoter;
    private UserDetailsService userDetailsService;
    private IUserRoleListService userRoleListService;

    /**
     * Returns the default instance, if the test instance is not null return the test instance
     */
    public static ISecurityHelper getInstance() {
        if (mockInstance != null) {
            return mockInstance;
        }

        return instance;
    }

    /**
     * Set the mockInstance, this should only be used for testing
     *
     * @param mockInstanceValue the test implementation of SecurityHelper
     */
    public static void setMockInstance(ISecurityHelper mockInstanceValue) {
        mockInstance = mockInstanceValue;
    }

    /**
     * Default constructor - protected so that it may be only constructed by a sub-class since this is a singleton
     */
    protected SecurityHelper() {

    }

    /**
     * Hi-jacks the system for the named user. <p/> <p> This will essentially create a session for this user, make that
     * session the current session, and add the Authentication objects to the session and Spring context holder. WARNING:
     * this method is irreversible!!! If you want execute a block of code as a surrogate user and have the orignal user
     * resume after it is complete, you want {@link #runAsUser(String, Callable)}. </p> <p/> <p> This is for unit tests
     * only. </p>
     *
     * @param principalName the user to become in the system
     */
    @Override
    public void becomeUser(final String principalName) {
        becomeUser(principalName, null);
    }

    /**
     * Hi-jacks the system for the named user. <p/> <p> This is for unit tests only. </p>
     */
    @Override
    public void becomeUser(final String principalName, final IParameterProvider paramProvider) {
        UserSession session = null;
        tenantedUserNameUtils = getTenantedUserNameUtils();
        if (tenantedUserNameUtils != null) {
            session = new UserSession(principalName, null, false, paramProvider);
            ITenant tenant = tenantedUserNameUtils.getTenant(principalName);
            session.setAttribute(IPentahoSession.TENANT_ID_KEY, tenant.getId());
            session.setAuthenticated(tenant.getId(), principalName);
        } else {
            session = new UserSession(principalName, null, false, paramProvider);
            session.setAuthenticated(principalName);
        }

        PentahoSessionHolder.setSession(session);

        Authentication auth = createAuthentication(principalName);
        // TODO We need to figure out how to inject this
        // Get the tenant id from the principle name and set it as an attribute of the pentaho session

        // Clearing the SecurityContext to force the subsequent call to getContext() to generate a new SecurityContext.
        // This prevents us from modifying the Authentication on a SecurityContext isntance which may be shared between
        // threads.
        SecurityContextHolder.clearContext();
        SecurityContextHolder.getContext().setAuthentication(auth);
        PentahoSystem.sessionStartup(PentahoSessionHolder.getSession(), paramProvider);
    }

    /**
     * Utility method that allows you to run a block of code as the given user. Regardless of success or exception
     * situation, the original session and authentication will be restored once your block of code is finished executing,
     * i.e. the given user will apply only to your {@link Callable}, then the system environment will return to the user
     * present prior to you calling this method.
     *
     * @param <T>           the return type of your operation, specify this type as <code>T</code>
     * @param principalName the user under whom you wish to run a section of code
     * @param callable      {@link Callable#call()} contains the code you wish to run as the given user
     * @return the value returned by your implementation of {@link Callable#call()}
     * @throws Exception
     * @see {@link Callable}
     */
    @Override
    public <T> T runAsUser(final String principalName, final Callable<T> callable) throws Exception {
        return runAsUser(principalName, null, callable);
    }

    @Override
    public <T> T runAsUser(final String principalName, final IParameterProvider paramProvider,
            final Callable<T> callable) throws Exception {
        IPentahoSession origSession = PentahoSessionHolder.getSession();
        SecurityContext originalContext = SecurityContextHolder.getContext();
        try {
            becomeUser(principalName);
            return callable.call();
        } finally {
            IPentahoSession sessionToDestroy = PentahoSessionHolder.getSession();
            if (sessionToDestroy != null && sessionToDestroy != origSession) {
                try {
                    sessionToDestroy.destroy();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            PentahoSessionHolder.setSession(origSession);
            SecurityContextHolder.setContext(originalContext);
        }
    }

    /**
     * Utility method that allows you to run a block of code as the given user. Regardless of success or exception
     * situation, the original session and authentication will be restored once your block of code is finished executing,
     * i.e. the given user will apply only to your {@link Callable}, then the system environment will return to the user
     * present prior to you calling this method.
     *
     * @param <T>      the return type of your operation, specify this type as <code>T</code>
     * @param callable {@link Callable#call()} contains the code you wish to run as the given user
     * @return the value returned by your implementation of {@link Callable#call()}
     * @throws Exception
     * @see {@link Callable}
     */
    @Override
    public <T> T runAsAnonymous(final Callable<T> callable) throws Exception {
        IPentahoSession origSession = PentahoSessionHolder.getSession();
        SecurityContext originalContext = SecurityContextHolder.getContext();
        try {
            PentahoSessionHolder.setSession(new StandaloneSession());

            // get anonymous username/role defined in pentaho.xml
            String user = PentahoSystem.getSystemSetting("anonymous-authentication/anonymous-user", //$NON-NLS-1$
                    "anonymousUser"); //$NON-NLS-1$
            String role = PentahoSystem.getSystemSetting("anonymous-authentication/anonymous-role", "Anonymous"); //$NON-NLS-1$//$NON-NLS-2$
            GrantedAuthority[] authorities = new GrantedAuthority[] { new GrantedAuthorityImpl(role) };

            Authentication auth = new AnonymousAuthenticationToken("anonymousUser",
                    new User(user, "ignored", true, true, true, true, authorities), authorities);

            // Clearing the SecurityContext to force the subsequent call to getContext() to generate a new SecurityContext.
            // This prevents us from modifying the Authentication on a SecurityContext isntance which may be shared between
            // threads.
            SecurityContextHolder.clearContext();
            SecurityContextHolder.getContext().setAuthentication(auth);
            return callable.call();
        } finally {
            PentahoSessionHolder.setSession(origSession);
            SecurityContextHolder.setContext(originalContext);
        }
    }

    /**
     * Utility method that communicates with the installed ACLVoter to determine administrator status
     *
     * @param session The users IPentahoSession object
     * @return true if the user is considered a Pentaho administrator
     */
    @Override
    public boolean isPentahoAdministrator(final IPentahoSession session) {
        return getAclVoter().isPentahoAdministrator(session);
    }

    /**
     * Utility method that communicates with the installed ACLVoter to determine whether a particular role is granted to
     * the specified user.
     *
     * @param session The users' IPentahoSession
     * @param role    The role to look for
     * @return true if the user is granted the specified role.
     */
    @Override
    public boolean isGranted(final IPentahoSession session, final GrantedAuthority role) {
        return getAclVoter().isGranted(session, role);
    }

    /**
     * @param aFile
     * @return a boolean that indicates if this file can have ACLS placed on it.
     */
    @Override
    public boolean canHaveACLS(final ISolutionFile aFile) {
        if (aFile.isDirectory()) { // All Directories can have ACLS
            return true;
        }

        // Otherwise anything in the PentahoSystem extension list.
        return PentahoSystem.getACLFileExtensionList().contains(aFile.getExtension());
    }

    @Override
    public boolean hasAccess(final IAclHolder aHolder, final int actionOperation, final IPentahoSession session) {
        int aclMask = -1;

        switch (actionOperation) {
        case (IAclHolder.ACCESS_TYPE_READ): {
            aclMask = IPentahoAclEntry.PERM_EXECUTE;
            break;
        }
        case IAclHolder.ACCESS_TYPE_WRITE:
        case IAclHolder.ACCESS_TYPE_UPDATE: {
            aclMask = IPentahoAclEntry.PERM_UPDATE;
            break;
        }
        case IAclHolder.ACCESS_TYPE_DELETE: {
            aclMask = IPentahoAclEntry.PERM_DELETE;
            break;
        }
        case IAclHolder.ACCESS_TYPE_ADMIN: {
            aclMask = IPentahoAclEntry.PERM_ADMINISTRATION;
            break;
        }
        default: {
            aclMask = IPentahoAclEntry.PERM_EXECUTE;
            break;
        }

        }
        return getAclVoter().hasAccess(session, aHolder, aclMask);
    }

    /**
     * Utility method for hydrating a Spring Authentication object (Principal) given just a user name. Note: The {@link
     * IUserRoleListService} will be consulted for the roles associated with this user.
     *
     * @param principalName the subject of this Authentication object
     * @return a Spring Authentication for the given user
     */
    @Override
    public Authentication createAuthentication(String principalName) {

        //get 'anonymousUser' defined name from pentaho.xml's <anonymous-authentication> block
        String anonymousUser = PentahoSystem.getSystemSetting("anonymous-authentication/anonymous-user", //$NON-NLS-1$
                "anonymousUser"); //$NON-NLS-1$

        userDetailsService = getUserDetailsService();
        userRoleListService = getUserRoleListService();

        List<String> roles = new ArrayList<String>();

        // anonymousUser gets its roles from session
        if (anonymousUser.equals(principalName)) {

            //get 'anonymous' defined role from pentaho.xml's <anonymous-authentication> block
            String anonymousRole = PentahoSystem.getSystemSetting("anonymous-authentication/anonymous-role", //$NON-NLS-1$
                    "Anonymous"); //$NON-NLS-1$
            roles.add(anonymousRole);

        } else {

            // default (standard) role fetching via IUserRoleListService
            roles = userRoleListService.getRolesForUser(null, principalName);

        }

        if (SecurityHelper.logger.isDebugEnabled()) {
            SecurityHelper.logger.debug("rolesForUser:" + roles); //$NON-NLS-1$
        }

        GrantedAuthority[] grantedAuthorities = new GrantedAuthority[roles.size()];
        if (roles != null) {
            for (int i = 0; i < roles.size(); i++) {
                grantedAuthorities[i] = new GrantedAuthorityImpl(roles.get(i));
            }
        }

        User user = new User(principalName, "", true, true, true, true, grantedAuthorities);
        Authentication auth = new UsernamePasswordAuthenticationToken(user, null, grantedAuthorities);
        return auth;

    }

    @Override
    public Authentication getAuthentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    /**
     * Remove this method when data-access is JCR-branched
     *
     * @param ignoredSession
     * @param ignoredAllowAnonymous
     * @return
     */
    @Override
    public Authentication getAuthentication(IPentahoSession ignoredSession, boolean ignoredAllowAnonymous) {
        return getAuthentication();
    }

    /**
     * Runs code as system with full privileges.
     */
    public <T> T runAsSystem(final Callable<T> callable) throws Exception {
        String singleTenantAdmin = PentahoSystem.get(String.class, "singleTenantAdminUserName", null);
        IPentahoSession origSession = PentahoSessionHolder.getSession();

        SecurityContext originalContext = SecurityContextHolder.getContext();

        StandaloneSession session = null;
        try {
            session = new StandaloneSession(singleTenantAdmin);
            session.setAuthenticated(singleTenantAdmin);

            // Set the session first or else the call to
            // createAuthentication will fail
            PentahoSessionHolder.setSession(session);

            // Clearing the SecurityContext to force the subsequent call to getContext() to generate a new SecurityContext.
            // This prevents us from modifying the Authentication on a SecurityContext isntance which may be shared between
            // threads.
            SecurityContextHolder.clearContext();

            // Now create the authentication
            Authentication auth = createAuthentication(singleTenantAdmin); //$NON-NLS-1$
            SecurityContextHolder.getContext().setAuthentication(auth);

            // Invoke the delta.
            return callable.call();
        } finally {
            // Make sure to destroy the system session so we don't leak anything.
            if (session != null) {
                try {
                    session.destroy();
                } catch (Exception e) {
                    // We can safely ignore this.
                    e.printStackTrace();
                }
            }
            // Reset the original session.
            PentahoSessionHolder.setSession(origSession);
            SecurityContextHolder.setContext(originalContext);
        }
    }

    public ITenantedPrincipleNameResolver getTenantedUserNameUtils() {
        if (tenantedUserNameUtils == null) {
            tenantedUserNameUtils = PentahoSystem.get(ITenantedPrincipleNameResolver.class, "tenantedUserNameUtils",
                    null);
        }
        return tenantedUserNameUtils;
    }

    public IAclVoter getAclVoter() {
        if (aclVoter == null) {
            aclVoter = PentahoSystem.get(IAclVoter.class);
        }
        return aclVoter;
    }

    public UserDetailsService getUserDetailsService() {
        if (userDetailsService == null) {
            userDetailsService = PentahoSystem.get(UserDetailsService.class);
        }
        return userDetailsService;
    }

    public IUserRoleListService getUserRoleListService() {
        if (userRoleListService == null) {
            userRoleListService = PentahoSystem.get(IUserRoleListService.class);
        }
        return userRoleListService;
    }
}