/* * 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 - 2016 Pentaho Corporation. All rights reserved. */ package org.pentaho.platform.security.policy.rolebased; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.jcr.NamespaceException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import org.pentaho.platform.api.engine.IAuthorizationAction; import org.pentaho.platform.api.engine.ICacheManager; import org.pentaho.platform.api.engine.IPluginManager; import org.pentaho.platform.api.engine.IPluginManagerListener; import org.pentaho.platform.api.engine.security.userroledao.NotFoundException; import org.pentaho.platform.api.mt.ITenant; import org.pentaho.platform.api.mt.ITenantedPrincipleNameResolver; import org.pentaho.platform.engine.core.system.PentahoSystem; import org.pentaho.platform.engine.core.system.TenantUtils; import org.pentaho.platform.repository2.unified.ServerRepositoryPaths; import org.pentaho.platform.repository2.unified.jcr.JcrStringHelper; import org.pentaho.platform.repository2.unified.jcr.JcrTenantUtils; import org.pentaho.platform.repository2.unified.jcr.NodeHelper; import org.pentaho.platform.repository2.unified.jcr.PentahoJcrConstants; import org.pentaho.platform.security.policy.rolebased.messages.Messages; import org.springframework.util.Assert; import com.google.common.collect.HashMultimap; public abstract class AbstractJcrBackedRoleBindingDao implements IRoleAuthorizationPolicyRoleBindingDao { private final ICacheManager cacheManager; protected ITenantedPrincipleNameResolver tenantedRoleNameUtils; protected Map<String, List<IAuthorizationAction>> immutableRoleBindings; protected Map<String, List<String>> immutableRoleBindingNames; protected Map<String, List<String>> bootstrapRoleBindings; protected String superAdminRoleName; private List<IAuthorizationAction> authorizationActions = Collections.emptyList(); public static final String FOLDER_NAME_AUTHZ = ".authz"; //$NON-NLS-1$ public static final String FOLDER_NAME_ROLEBASED = "roleBased"; //$NON-NLS-1$ public static final String FOLDER_NAME_RUNTIMEROLES = "runtimeRoles"; //$NON-NLS-1$ private static final String LOGICAL_ROLE_BINDINGS_REGION = "roleBindingCache"; public AbstractJcrBackedRoleBindingDao() { cacheManager = PentahoSystem.getCacheManager( null ); if ( !cacheManager.cacheEnabled( LOGICAL_ROLE_BINDINGS_REGION ) ) { cacheManager.addCacheRegion( LOGICAL_ROLE_BINDINGS_REGION ); } } public AbstractJcrBackedRoleBindingDao( final Map<String, List<IAuthorizationAction>> immutableRoleBindings, final Map<String, List<String>> bootstrapRoleBindings, final String superAdminRoleName, final ITenantedPrincipleNameResolver tenantedRoleNameUtils, final List<IAuthorizationAction> authorizationActions ) { this(); // TODO: replace with IllegalArgumentException Assert.notNull( immutableRoleBindings ); Assert.notNull( bootstrapRoleBindings ); Assert.notNull( superAdminRoleName ); Assert.notNull( authorizationActions ); setAuthorizationActions( authorizationActions ); this.immutableRoleBindings = immutableRoleBindings; this.bootstrapRoleBindings = bootstrapRoleBindings; this.superAdminRoleName = superAdminRoleName; this.tenantedRoleNameUtils = tenantedRoleNameUtils; immutableRoleBindingNames = new HashMap<String, List<String>>(); for ( final Entry<String, List<IAuthorizationAction>> entry : immutableRoleBindings.entrySet() ) { final List<String> roles = new ArrayList<String>(); for ( final IAuthorizationAction a : entry.getValue() ) { roles.add( a.getName() ); } immutableRoleBindingNames.put( entry.getKey(), roles ); } // TODO this code can be replaced for 7.0 version by commit // https://github.com/AliaksandrDrebenchuk/pentaho-platform/commit/3adf0df3a337b6dc1b864e74b62143510d0381ee IPluginManager pluginManager = PentahoSystem.get( IPluginManager.class ); if ( pluginManager != null ) { pluginManager.addPluginManagerListener( new IPluginManagerListener() { private boolean loaded = false; @Override public void onReload() { if ( !loaded ) { setAuthorizationActions( PentahoSystem.getAll( IAuthorizationAction.class ) ); updateImmutableRoleBindingNames(); // when immutableRoleBindingNames gets updated, we should ensure no stale logical roles remain cached if ( cacheManager.cacheEnabled( LOGICAL_ROLE_BINDINGS_REGION ) ) { cacheManager.removeRegionCache( LOGICAL_ROLE_BINDINGS_REGION ); } loaded = true; } } } ); } } public void updateImmutableRoleBindingNames() { for ( List<String> roles : immutableRoleBindingNames.values() ) { roles.clear(); for ( final IAuthorizationAction action : authorizationActions ) { roles.add( action.getName() ); } } } public void setAuthorizationActions( final List<IAuthorizationAction> authorizationActions ) { this.authorizationActions = authorizationActions; } @Override public List<String> getBoundLogicalRoleNames( Session session, List<String> runtimeRoleNames ) throws NamespaceException, RepositoryException { Set<String> boundRoleNames = new HashSet<String>(); HashMap<ITenant, List<String>> tenantMap = new HashMap<ITenant, List<String>>(); boolean includeSuperAdminLogicalRoles = false; for ( String runtimeRoleName : runtimeRoleNames ) { if ( !superAdminRoleName.equals( runtimeRoleName ) ) { ITenant tenant = JcrTenantUtils.getTenant( runtimeRoleName, false ); List<String> runtimeRoles = tenantMap.get( tenant ); if ( runtimeRoles == null ) { runtimeRoles = new ArrayList<String>(); tenantMap.put( tenant, runtimeRoles ); } runtimeRoles.add( tenantedRoleNameUtils.getPrincipleName( runtimeRoleName ) ); } else { includeSuperAdminLogicalRoles = true; } } for ( Map.Entry<ITenant, List<String>> mapEntry : tenantMap.entrySet() ) { boundRoleNames.addAll( getBoundLogicalRoleNames( session, mapEntry.getKey(), mapEntry.getValue() ) ); } if ( includeSuperAdminLogicalRoles ) { boundRoleNames.addAll( immutableRoleBindingNames.get( superAdminRoleName ) ); } return new ArrayList<String>( boundRoleNames ); } @Override public List<String> getBoundLogicalRoleNames( Session session, ITenant tenant, List<String> runtimeRoleNames ) throws NamespaceException, RepositoryException { if ( ( tenant == null ) || ( tenant.getId() == null ) ) { return getBoundLogicalRoleNames( session, runtimeRoleNames ); } if ( !TenantUtils.isAccessibleTenant( tenant ) ) { return new ArrayList<String>(); } final List<String> uncachedRuntimeRoleNames = new ArrayList<String>(); final Set<String> cachedBoundLogicalRoleNames = new HashSet<String>(); for ( String runtimeRoleName : runtimeRoleNames ) { String roleName = tenantedRoleNameUtils.getPrincipleName( runtimeRoleName ); String roleId = tenantedRoleNameUtils.getPrincipleId( tenant, runtimeRoleName ); Object fromRegionCache = cacheManager.getFromRegionCache( LOGICAL_ROLE_BINDINGS_REGION, roleId ); if ( fromRegionCache != null ) { cachedBoundLogicalRoleNames.addAll( (Collection<String>) fromRegionCache ); } else { uncachedRuntimeRoleNames.add( roleName ); } } if ( uncachedRuntimeRoleNames.isEmpty() ) { // no need to hit the repo return new ArrayList<String>( cachedBoundLogicalRoleNames ); } PentahoJcrConstants pentahoJcrConstants = new PentahoJcrConstants( session ); final String phoNsPrefix = session.getNamespacePrefix( PentahoJcrConstants.PHO_NS ) + ":"; //$NON-NLS-1$ final String onlyPentahoPattern = phoNsPrefix + "*"; //$NON-NLS-1$ HashMultimap<String, String> boundLogicalRoleNames = HashMultimap.create(); Node runtimeRolesFolderNode = getRuntimeRolesFolderNode( session, tenant ); NodeIterator runtimeRoleNodes = runtimeRolesFolderNode.getNodes( onlyPentahoPattern ); if ( !runtimeRoleNodes.hasNext() ) { // no bindings setup yet; fall back on bootstrap bindings for ( String runtimeRoleName : uncachedRuntimeRoleNames ) { String roleId = tenantedRoleNameUtils.getPrincipleId( tenant, runtimeRoleName ); if ( bootstrapRoleBindings.containsKey( runtimeRoleName ) ) { boundLogicalRoleNames.putAll( roleId, bootstrapRoleBindings.get( runtimeRoleName ) ); } } } else { for ( String runtimeRoleName : uncachedRuntimeRoleNames ) { if ( NodeHelper.hasNode( runtimeRolesFolderNode, phoNsPrefix, runtimeRoleName ) ) { Node runtimeRoleFolderNode = NodeHelper.getNode( runtimeRolesFolderNode, phoNsPrefix, runtimeRoleName ); if ( runtimeRoleFolderNode.hasProperty( pentahoJcrConstants.getPHO_BOUNDROLES() ) ) { Value[] values = runtimeRoleFolderNode.getProperty( pentahoJcrConstants.getPHO_BOUNDROLES() ).getValues(); String roleId = tenantedRoleNameUtils.getPrincipleId( tenant, runtimeRoleName ); for ( Value value : values ) { boundLogicalRoleNames.put( roleId, value.getString() ); } } } } } // now add in immutable bound logical role names for ( String runtimeRoleName : uncachedRuntimeRoleNames ) { if ( immutableRoleBindings.containsKey( runtimeRoleName ) ) { String roleId = tenantedRoleNameUtils.getPrincipleId( tenant, runtimeRoleName ); boundLogicalRoleNames.putAll( roleId, immutableRoleBindingNames.get( runtimeRoleName ) ); } } // update cache Map<String, Collection<String>> stringCollectionMap = boundLogicalRoleNames.asMap(); for ( Entry<String, Collection<String>> stringCollectionEntry : stringCollectionMap.entrySet() ) { cacheManager.putInRegionCache( LOGICAL_ROLE_BINDINGS_REGION, stringCollectionEntry.getKey(), stringCollectionEntry.getValue() ); } // now add in those runtime roles that have no bindings to the cache for ( String runtimeRoleName : uncachedRuntimeRoleNames ) { String roleId = tenantedRoleNameUtils.getPrincipleId( tenant, runtimeRoleName ); if ( cacheManager.getFromRegionCache( LOGICAL_ROLE_BINDINGS_REGION, roleId ) == null ) { cacheManager.putInRegionCache( LOGICAL_ROLE_BINDINGS_REGION, roleId, Collections.emptyList() ); } } // combine cached findings plus ones from repo Set<String> res = new HashSet<String>(); res.addAll( cachedBoundLogicalRoleNames ); res.addAll( boundLogicalRoleNames.values() ); return new ArrayList<String>( res ); } public void setRoleBindings( Session session, ITenant tenant, String runtimeRoleName, List<String> logicalRoleNames ) throws NamespaceException, RepositoryException { if ( tenant == null ) { tenant = JcrTenantUtils.getTenant( runtimeRoleName, false ); runtimeRoleName = getPrincipalName( runtimeRoleName ); } if ( !TenantUtils.isAccessibleTenant( tenant ) ) { throw new NotFoundException( "Tenant " + tenant.getId() + " not found" ); } PentahoJcrConstants pentahoJcrConstants = new PentahoJcrConstants( session ); final String phoNsPrefix = session.getNamespacePrefix( PentahoJcrConstants.PHO_NS ) + ":"; //$NON-NLS-1$ final String onlyPentahoPattern = phoNsPrefix + "*"; //$NON-NLS-1$ Node runtimeRolesFolderNode = getRuntimeRolesFolderNode( session, tenant ); NodeIterator runtimeRoleNodes = runtimeRolesFolderNode.getNodes( onlyPentahoPattern ); int i = 0; while ( runtimeRoleNodes.hasNext() ) { runtimeRoleNodes.nextNode(); i++; } if ( i == 0 ) { // no bindings setup yet; install bootstrap bindings; bootstrapRoleBindings will now no longer be // consulted for ( Map.Entry<String, List<String>> entry : bootstrapRoleBindings.entrySet() ) { JcrRoleAuthorizationPolicyUtils .internalSetBindings( pentahoJcrConstants, runtimeRolesFolderNode, entry.getKey(), entry.getValue(), phoNsPrefix ); } } if ( !isImmutable( runtimeRoleName ) ) { JcrRoleAuthorizationPolicyUtils.internalSetBindings( pentahoJcrConstants, runtimeRolesFolderNode, runtimeRoleName, logicalRoleNames, phoNsPrefix ); } else { throw new RuntimeException( Messages.getInstance().getString( "JcrRoleAuthorizationPolicyRoleBindingDao.ERROR_0001_ATTEMPT_MOD_IMMUTABLE", runtimeRoleName ) ); //$NON-NLS-1$ } session.save(); Assert.isTrue( NodeHelper.hasNode( runtimeRolesFolderNode, phoNsPrefix, runtimeRoleName ) ); // update cache String roleId = tenantedRoleNameUtils.getPrincipleId( tenant, runtimeRoleName ); cacheManager.putInRegionCache( LOGICAL_ROLE_BINDINGS_REGION, roleId, logicalRoleNames ); } private String getPrincipalName( String principalId ) { String principalName = null; if ( tenantedRoleNameUtils != null ) { principalName = tenantedRoleNameUtils.getPrincipleName( principalId ); } return principalName; } protected boolean isImmutable( final String runtimeRoleName ) { return immutableRoleBindings.containsKey( runtimeRoleName ); } protected Map<String, List<String>> getRoleBindings( Session session, ITenant tenant ) throws RepositoryException { Map<String, List<String>> map = new HashMap<String, List<String>>(); if ( tenant == null ) { tenant = JcrTenantUtils.getTenant(); } if ( !TenantUtils.isAccessibleTenant( tenant ) ) { return map; } PentahoJcrConstants pentahoJcrConstants = new PentahoJcrConstants( session ); final String phoNsPrefix = session.getNamespacePrefix( PentahoJcrConstants.PHO_NS ) + ":"; //$NON-NLS-1$ final String onlyPentahoPattern = phoNsPrefix + "*"; //$NON-NLS-1$ Node runtimeRolesFolderNode = getRuntimeRolesFolderNode( session, tenant ); NodeIterator runtimeRoleNodes = runtimeRolesFolderNode.getNodes( onlyPentahoPattern ); if ( !runtimeRoleNodes.hasNext() ) { // no bindings setup yet; fall back on bootstrap bindings map.putAll( bootstrapRoleBindings ); } else { while ( runtimeRoleNodes.hasNext() ) { Node runtimeRoleNode = runtimeRoleNodes.nextNode(); if ( runtimeRoleNode.hasProperty( pentahoJcrConstants.getPHO_BOUNDROLES() ) ) { // get clean runtime role name String runtimeRoleName = JcrStringHelper.fileNameDecode( runtimeRoleNode.getName().substring( phoNsPrefix.length() ) ); // get logical role names List<String> logicalRoleNames = new ArrayList<String>(); Value[] values = runtimeRoleNode.getProperty( pentahoJcrConstants.getPHO_BOUNDROLES() ).getValues(); for ( Value value : values ) { logicalRoleNames.add( value.getString() ); } map.put( runtimeRoleName, logicalRoleNames ); } } } // add all immutable bindings map.putAll( immutableRoleBindingNames ); return map; } public RoleBindingStruct getRoleBindingStruct( Session session, ITenant tenant, String locale ) throws RepositoryException { return new RoleBindingStruct( getMapForLocale( locale ), getRoleBindings( session, tenant ), new HashSet<String>( immutableRoleBindingNames.keySet() ) ); } protected Map<String, String> getMapForLocale( final String localeString ) { Map<String, String> map = new HashMap<String, String>(); for ( IAuthorizationAction authorizationAction : authorizationActions ) { map.put( authorizationAction.getName(), authorizationAction.getLocalizedDisplayName( localeString ) ); } return map; } public Node getRuntimeRolesFolderNode( final Session session, ITenant tenant ) throws RepositoryException { Node tenantRootFolderNode = null; try { tenantRootFolderNode = (Node) session.getItem( ServerRepositoryPaths.getTenantRootFolderPath( tenant ) ); } catch ( PathNotFoundException e ) { throw new RepositoryException( "Error retrieving RuntimeRoles for folder, folder not found", e ); // Assert.state(false, Messages.getInstance().getString( // "JcrRoleAuthorizationPolicyRoleBindingDao.ERROR_0002_REPO_NOT_INITIALIZED")); //$NON-NLS-1$ } Node authzFolderNode = tenantRootFolderNode.getNode( FOLDER_NAME_AUTHZ ); Node roleBasedFolderNode = authzFolderNode.getNode( FOLDER_NAME_ROLEBASED ); return roleBasedFolderNode.getNode( FOLDER_NAME_RUNTIMEROLES ); } }