Skip to content

Commit

Permalink
Merge pull request pentaho#3030 from mchen-len-son/BISERVER-13264
Browse files Browse the repository at this point in the history
[BISERVER-13264] When using a custom AccessVoter, cut/paste does not …
  • Loading branch information
Marc Batchelor authored Nov 16, 2016
2 parents c4d4c57 + 445df91 commit 11220a5
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import org.codehaus.enunciate.jaxrs.StatusCodes;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.pentaho.platform.api.engine.IAuthorizationPolicy;
Expand Down Expand Up @@ -298,6 +297,9 @@ public Response doMove( @PathParam ( "pathId" ) String destPathId, String params
} catch ( FileNotFoundException e ) {
logger.error( Messages.getInstance().getErrorString( "FileResource.DESTINATION_PATH_UNKNOWN", destPathId ), e );
return buildStatusResponse( Response.Status.NOT_FOUND );
} catch ( UnifiedRepositoryAccessDeniedException e ) {
logger.error( Messages.getInstance().getErrorString( "FileResource.FILE_MOVE_ACCESS_DENIED", params ), e );
return buildStatusResponse( Response.Status.FORBIDDEN );
} catch ( Throwable t ) {
logger.error( Messages.getInstance().getString( "SystemResource.FILE_MOVE_FAILED" ), t );
return buildStatusResponse( Response.Status.INTERNAL_SERVER_ERROR );
Expand Down Expand Up @@ -424,6 +426,9 @@ public Response doCopyFiles( @PathParam ( "pathId" ) String pathId, @QueryParam
fileService.doCopyFiles( pathId, mode, params );
} catch ( IllegalArgumentException e ) {
return buildStatusResponse( Response.Status.FORBIDDEN );
} catch ( UnifiedRepositoryAccessDeniedException e ) {
logger.error( Messages.getInstance().getErrorString( "FileResource.FILE_COPY_ACCESS_DENIED", params ), e );
return buildStatusResponse( Response.Status.FORBIDDEN );
} catch ( Exception e ) {
logger.error( Messages.getInstance().getString( "SystemResource.GENERAL_ERROR" ), e );
return buildSafeHtmlServerErrorResponse( e );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.pentaho.platform.api.repository2.unified.RepositoryFile;
import org.pentaho.platform.api.repository2.unified.RepositoryFileAcl;
import org.pentaho.platform.api.repository2.unified.RepositoryRequest;
import org.pentaho.platform.api.repository2.unified.UnifiedRepositoryAccessDeniedException;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.repository2.unified.webservices.DefaultUnifiedRepositoryWebService;
import org.pentaho.platform.repository2.unified.webservices.RepositoryFileAdapter;
Expand Down Expand Up @@ -262,7 +263,10 @@ private void copyRenameMode( RepositoryFile repoFile ) {
repositoryFile =
getRepository().createFile( destDir.getId(), duplicateFile, data, acl, null );
}

if ( repositoryFile == null ) {
throw new UnifiedRepositoryAccessDeniedException( Messages.getInstance().getString(
"JcrRepositoryFileDao.ERROR_0006_ACCESS_DENIED_CREATE", destDir.getId() ) );
}

getRepository().setFileMetadata( repositoryFile.getId(), getRepository().getFileMetadata( repoFile.getId() ) );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ public void doMoveFiles( String destPathId, String params ) throws FileNotFoundE
for ( ; i < sourceFileIds.length; i++ ) {
getRepoWs().moveFile( sourceFileIds[ i ], repositoryFileDto.getPath(), null );
}
} catch ( IllegalArgumentException e ) {
} catch ( IllegalArgumentException | UnifiedRepositoryAccessDeniedException e ) {
throw e;
} catch ( Exception e ) {
throw new InternalError();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright 2006 - 2010 Pentaho Corporation. All rights reserved.
# Copyright 2006 - 2016 Pentaho Corporation. All rights reserved.
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
# Foundation.
Expand Down Expand Up @@ -119,6 +119,8 @@ FileResource.EXPORT_FAILED=Export failed: {0}
FileResource.DESTINATION_PATH_UNKNOWN=Destination path not found: {0}
FileResource.FILE_NOT_FOUND=File not found: {0}
FileResource.FILE_MOVE_FAILED=Move failed for path: {0}
FileResource.FILE_MOVE_ACCESS_DENIED=Access denied while moving files: {0}
FileResource.FILE_COPY_ACCESS_DENIED=Access denied while copying files: {0}
FileResource.FILE_RESTORE_FAILED=Restore failed for path: {0}
FileResource.FILE_SET_CONTENT_CREATOR=Set Content Creator failed for path: {0}
FileResource.FILE_GET_LOCALES=Get Locales failed for path: {0}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2008 - 2010 Pentaho Corporation. All rights reserved.
# Copyright 2008 - 2016 Pentaho Corporation. All rights reserved.
# 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.
Expand Down Expand Up @@ -99,6 +99,7 @@ XmlAdapter.ERROR_0002_UNMARSHAL=error unmarshalling {0} to {1}
DefaultDeleteHelper.ERROR_0001_PATH_NOT_FOUND=cannot determine original parent folder ID since original parent folder path does not exist
DefaultDeleteHelper.ERROR_0002_NOT_CLEAN=this should have been cleaned up on undelete or permanent delete
JcrRepositoryFileDao.ERROR_0006_ACCESS_DENIED_DELETE=Access denied while deleting file with id [ {0} ]
JcrRepositoryFileDao.ERROR_0006_ACCESS_DENIED_CREATE=Access denied while creating file in folder with id [ {0} ]
DefaultUnifiedRepository.ERROR_0001_ACCESS_DENIED_UPDATE_ACL=Access denied while updating permissions on file with id [ {0} ]
AclNodeHelper.ERROR_0001_ROOT_FOLDER_NOT_AVAILABLE=Root folder {0} not available. Using default {1} instead
AclNodeHelper.WARN_0001_REMOVE_ACL_NODE=Removing the ACL node:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the GNU General Public License for more details.
*
*
* Copyright 2006 - 2013 Pentaho Corporation. All rights reserved.
* Copyright 2006 - 2016 Pentaho Corporation. All rights reserved.
*/

package org.pentaho.platform.repository2.unified.jcr;
Expand Down Expand Up @@ -846,11 +846,27 @@ private void internalCopyOrMove( final Serializable fileId, final String destRel
jcrTemplate.execute( new JcrCallback() {
@Override
public Object doInJcr( final Session session ) throws RepositoryException, IOException {
// if we're moving the file,
// check that user has permissions to remove the file from it's current location
RepositoryFile file = getFileById( fileId );
RepositoryFileAcl acl = aclDao.getAcl( fileId );
if ( !accessVoterManager.hasAccess( file, RepositoryFilePermission.WRITE, acl, PentahoSessionHolder
.getSession() ) ) {
return null;
if ( !copy ) {
RepositoryFileAcl acl = aclDao.getAcl( fileId );
if ( !accessVoterManager.hasAccess( file, RepositoryFilePermission.WRITE, acl,
PentahoSessionHolder.getSession() ) ) {
throw new AccessDeniedException( Messages.getInstance().getString(
"JcrRepositoryFileDao.ERROR_0006_ACCESS_DENIED_DELETE", fileId ) );
}
}

// check that user has permissions to write to the destination folder
RepositoryFile destFolder = getFile( destRelPath );
if ( destFolder != null ) {
RepositoryFileAcl destFolderAcl = aclDao.getAcl( destFolder.getId() );
if ( !accessVoterManager.hasAccess( destFolder, RepositoryFilePermission.WRITE, destFolderAcl,
PentahoSessionHolder.getSession() ) ) {
throw new AccessDeniedException( Messages.getInstance().getString(
"JcrRepositoryFileDao.ERROR_0006_ACCESS_DENIED_CREATE", destFolder.getId() ) );
}
}

PentahoJcrConstants pentahoJcrConstants = new PentahoJcrConstants( session );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.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 Lesser General Public License for more details.
*
* Copyright (c) 2002-2016 Pentaho Corporation.. All rights reserved.
*/

package org.pentaho.platform.repository2.unified.jcr;

import org.apache.jackrabbit.core.VersionManagerImpl;
import org.junit.Before;
import org.junit.Test;
import org.pentaho.platform.api.engine.IPentahoSession;
import org.pentaho.platform.api.repository2.unified.IRepositoryAccessVoterManager;
import org.pentaho.platform.api.repository2.unified.IRepositoryFileData;
import org.pentaho.platform.api.repository2.unified.IRepositoryVersionManager;
import org.pentaho.platform.api.repository2.unified.RepositoryFile;
import org.pentaho.platform.api.repository2.unified.RepositoryFilePermission;
import org.pentaho.platform.engine.core.system.PentahoSessionHolder;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.repository2.unified.IRepositoryFileAclDao;
import org.springframework.dao.DataAccessException;
import org.springframework.extensions.jcr.JcrCallback;
import org.springframework.extensions.jcr.JcrTemplate;

import javax.jcr.AccessDeniedException;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Workspace;
import java.util.Collections;
import java.util.List;

import static org.junit.Assert.fail;
import static org.mockito.Mockito.*;

public class JcrRepositoryFileDaoTest {

private JcrRepositoryFileDao dao;

private IRepositoryAccessVoterManager accessVoterManager;

private IPentahoSession pentahoSession;

@Before
public void setUp() throws RepositoryException {
Node node = mock( Node.class );
Node nodeParent = mock( Node.class );
when( node.getIdentifier() ).thenReturn( "" );
when( nodeParent.getIdentifier() ).thenReturn( "" );
when( node.getParent() ).thenReturn( nodeParent );
when( node.isNodeType( "null:pentahoFile" ) ).thenReturn( true );
when( node.isNodeType( "null:pentahoVersionable" ) ).thenReturn( true );
VersionManagerImpl versionManager = mock( VersionManagerImpl.class );
Workspace workspace = mock( Workspace.class );
when( workspace.getVersionManager() ).thenReturn( versionManager );
Session session = mock( Session.class );
when( session.getWorkspace() ).thenReturn( workspace );
when( session.getNodeByIdentifier( anyString() ) ).thenReturn( node );
when( session.getItem( anyString() ) ).thenReturn( node );
pentahoSession = mock( IPentahoSession.class );
PentahoSessionHolder.setSession( pentahoSession );
IRepositoryVersionManager repositoryVersionManager = mock( IRepositoryVersionManager.class );
when( repositoryVersionManager.isVersioningEnabled( anyString() ) ).thenReturn( true );
PentahoSystem.registerObject( repositoryVersionManager );
JcrTemplate jcrTemplate = new JcrTemplate() {
@Override
public Object execute( JcrCallback callback ) throws DataAccessException {

try {
return callback.doInJcr( session );
} catch ( Exception e ) {
// wrapping exception to comply overriding rules
throw new RuntimeException( e );
}
}
};
List<ITransformer<IRepositoryFileData>> transformerList = Collections.emptyList();
IPathConversionHelper pathConversionHelper = new DefaultPathConversionHelper();
IRepositoryFileAclDao aclDao = mock( IRepositoryFileAclDao.class );
accessVoterManager = mock( IRepositoryAccessVoterManager.class );
JcrRepositoryFileDao jcrDao = new JcrRepositoryFileDao( jcrTemplate, transformerList, null, null,
pathConversionHelper, aclDao, null, accessVoterManager );
dao = spy( jcrDao );
}

@Test
public void shouldConsultAccessVoterWhenCopyingOrMovingFiles() {
RepositoryFile filePermitted = mock( RepositoryFile.class );
RepositoryFile fileNotPermitted = mock( RepositoryFile.class );
RepositoryFile destinationPermitted = mock( RepositoryFile.class );
RepositoryFile destinationNotPermitted = mock( RepositoryFile.class );
doReturn( filePermitted ).when( dao ).getFileById( "/filePermitted" );
doReturn( fileNotPermitted ).when( dao ).getFileById( "/fileNotPermitted" );
doReturn( destinationPermitted ).when( dao ).getFile( "/destinationPermitted" );
doReturn( destinationNotPermitted ).when( dao ).getFile( "/destinationNotPermitted" );
doReturn( true ).when( accessVoterManager )
.hasAccess( filePermitted, RepositoryFilePermission.WRITE, null, pentahoSession );
doReturn( false ).when( accessVoterManager )
.hasAccess( fileNotPermitted, RepositoryFilePermission.WRITE, null, pentahoSession );
doReturn( true ).when( accessVoterManager )
.hasAccess( destinationPermitted, RepositoryFilePermission.WRITE, null, pentahoSession );
doReturn( false ).when( accessVoterManager )
.hasAccess( destinationNotPermitted, RepositoryFilePermission.WRITE, null, pentahoSession );

// should move the file,
// if the user has write permissions to the source file,
// and write permissions to the destination folder.
try {
dao.moveFile( "/filePermitted", "/destinationPermitted", null );
} catch ( Throwable e ) {
fail( e.getMessage() );
}

// should NOT move the file and throw an exception,
// if the user has write permissions to the destination folder,
// but does not have write permissions to the source file.
try {
dao.moveFile( "/fileNotPermitted", "/destinationPermitted", null );
} catch ( Throwable e ) {
// unwrap original exception and check it
if ( !( e instanceof RuntimeException )
|| !( e.getCause() instanceof AccessDeniedException ) ) {
fail( e.getMessage() );
}
}

// should NOT move the file and throw an exception,
// if the user has write permissions to the source file,
// but does not have write permissions to the destination folder.
try {
dao.moveFile( "/filePermitted", "/destinationNotPermitted", null );
} catch ( Throwable e ) {
// unwrap original exception and check it
if ( !( e instanceof RuntimeException )
|| !( e.getCause() instanceof AccessDeniedException ) ) {
fail( e.getMessage() );
}
}

// should NOT move the file and throw an exception,
// if the user neither has write permissions to the source file,
// nor has write permissions to the destination folder.
try {
dao.moveFile( "/fileNotPermitted", "/destinationNotPermitted", null );
} catch ( Throwable e ) {
// unwrap original exception and check it
if ( !( e instanceof RuntimeException )
|| !( e.getCause() instanceof AccessDeniedException ) ) {
fail( e.getMessage() );
}
}
}
}

0 comments on commit 11220a5

Please sign in to comment.