/*!
* Copyright 2010 - 2015 Pentaho Corporation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.pentaho.di.ui.repository.pur.repositoryexplorer.controller;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.repository.KettleRepositoryLostException;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.repository.Repository;
import org.pentaho.di.repository.RepositorySecurityManager;
import org.pentaho.di.repository.RepositorySecurityProvider;
import org.pentaho.di.ui.repository.pur.repositoryexplorer.IAclObject;
import org.pentaho.di.ui.repository.pur.repositoryexplorer.IUIEEUser;
import org.pentaho.di.ui.repository.pur.repositoryexplorer.model.UIRepositoryObjectAcl;
import org.pentaho.di.ui.repository.pur.repositoryexplorer.model.UIRepositoryObjectAclModel;
import org.pentaho.di.ui.repository.pur.repositoryexplorer.model.UIRepositoryObjectAcls;
import org.pentaho.di.ui.repository.repositoryexplorer.ContextChangeVetoer.TYPE;
import org.pentaho.di.ui.repository.repositoryexplorer.ControllerInitializationException;
import org.pentaho.di.ui.repository.repositoryexplorer.controllers.MainController;
import org.pentaho.platform.api.repository2.unified.RepositoryFilePermission;
import org.pentaho.ui.xul.XulComponent;
import org.pentaho.ui.xul.binding.Binding;
import org.pentaho.ui.xul.binding.BindingConvertor;
import org.pentaho.ui.xul.binding.BindingFactory;
import org.pentaho.ui.xul.binding.DefaultBindingFactory;
import org.pentaho.ui.xul.components.XulButton;
import org.pentaho.ui.xul.components.XulCheckbox;
import org.pentaho.ui.xul.components.XulConfirmBox;
import org.pentaho.ui.xul.components.XulMessageBox;
import org.pentaho.ui.xul.containers.XulDialog;
import org.pentaho.ui.xul.containers.XulListbox;
import org.pentaho.ui.xul.impl.AbstractXulEventHandler;
import org.pentaho.ui.xul.util.XulDialogCallback;
/**
* This is shared code between the folder/file permissions controller and the database permissions controller.
*
* @author Will Gorman (wgorman@pentaho.com)
*
*/
public abstract class AbstractPermissionsController extends AbstractXulEventHandler {
private static final Class<?> PKG = IUIEEUser.class;
protected BindingFactory bf;
protected XulMessageBox messageBox;
protected XulConfirmBox confirmBox;
protected XulListbox userRoleList;
protected XulListbox availableUserList;
protected XulListbox availableRoleList;
protected XulListbox selectedUserList;
protected XulListbox selectedRoleList;
protected XulCheckbox writeCheckbox;
protected XulCheckbox readCheckbox;
protected XulCheckbox manageAclCheckbox;
protected XulCheckbox deleteCheckbox;
protected XulButton addAclButton;
protected XulButton removeAclButton;
protected XulDialog manageAclsDialog;
protected XulButton assignUserButton;
protected XulButton unassignUserButton;
protected XulButton assignRoleButton;
protected XulButton unassignRoleButton;
protected XulButton applyAclButton;
protected Binding securityBinding;
protected UIRepositoryObjectAcls viewAclsModel;
protected UIRepositoryObjectAclModel manageAclsModel;
protected RepositorySecurityProvider service;
protected MainController mainController;
protected abstract List<? extends Object> getSelectedObjects();
protected PermissionsCheckboxHandler permissionsCheckboxHandler;
protected void init( Repository rep ) throws Exception {
if ( rep != null && rep.hasService( RepositorySecurityProvider.class ) ) {
service = (RepositorySecurityProvider) rep.getService( RepositorySecurityProvider.class );
} else {
throw new ControllerInitializationException( BaseMessages.getString( PKG,
"PermissionsController.ERROR_0001_UNABLE_TO_INITIAL_REPOSITORY_SERVICE", RepositorySecurityManager.class ) ); //$NON-NLS-1$
}
messageBox = (XulMessageBox) document.createElement( "messagebox" );//$NON-NLS-1$
viewAclsModel = new UIRepositoryObjectAcls();
manageAclsModel = new UIRepositoryObjectAclModel( viewAclsModel );
bf = new DefaultBindingFactory();
bf.setDocument( this.getXulDomContainer().getDocumentRoot() );
mainController = (MainController) this.getXulDomContainer().getEventHandler( "mainController" );
confirmBox = (XulConfirmBox) document.createElement( "confirmbox" );//$NON-NLS-1$
confirmBox.setTitle( BaseMessages.getString( PKG, "PermissionsController.RemoveAclWarning" ) ); //$NON-NLS-1$
confirmBox.setMessage( BaseMessages.getString( PKG, "PermissionsController.RemoveAclWarningText" ) ); //$NON-NLS-1$
confirmBox.setAcceptLabel( BaseMessages.getString( PKG, "Dialog.Ok" ) ); //$NON-NLS-1$
confirmBox.setCancelLabel( BaseMessages.getString( PKG, "Dialog.Cancel" ) ); //$NON-NLS-1$
confirmBox.addDialogCallback( new XulDialogCallback<Object>() {
public void onClose( XulComponent sender, Status returnCode, Object retVal ) {
if ( returnCode == Status.ACCEPT ) {
viewAclsModel.removeSelectedAcls();
}
}
public void onError( XulComponent sender, Throwable t ) {
}
} );
}
protected String getXulPrefix() {
return "";
}
protected void createBindings() {
userRoleList = (XulListbox) document.getElementById( getXulPrefix() + "user-role-list" );//$NON-NLS-1$
writeCheckbox = (XulCheckbox) document.getElementById( getXulPrefix() + "write-checkbox" );//$NON-NLS-1$
readCheckbox = (XulCheckbox) document.getElementById( getXulPrefix() + "read-checkbox" );//$NON-NLS-1$
manageAclCheckbox = (XulCheckbox) document.getElementById( getXulPrefix() + "manage-checkbox" );//$NON-NLS-1$
deleteCheckbox = (XulCheckbox) document.getElementById( getXulPrefix() + "delete-checkbox" );//$NON-NLS-1$
addAclButton = (XulButton) document.getElementById( getXulPrefix() + "add-acl-button" );//$NON-NLS-1$
removeAclButton = (XulButton) document.getElementById( getXulPrefix() + "remove-acl-button" );//$NON-NLS-1$
manageAclsDialog = (XulDialog) document.getElementById( getXulPrefix() + "manage-acls-dialog" );//$NON-NLS-1$
permissionsCheckboxHandler =
new PermissionsCheckboxHandler( readCheckbox, writeCheckbox, deleteCheckbox, manageAclCheckbox );
// Add/Remove Acl Binding
availableUserList = (XulListbox) document.getElementById( getXulPrefix() + "available-user-list" );//$NON-NLS-1$
selectedUserList = (XulListbox) document.getElementById( getXulPrefix() + "selected-user-list" );//$NON-NLS-1$
availableRoleList = (XulListbox) document.getElementById( getXulPrefix() + "available-role-list" );//$NON-NLS-1$
selectedRoleList = (XulListbox) document.getElementById( getXulPrefix() + "selected-role-list" );//$NON-NLS-1$
assignRoleButton = (XulButton) document.getElementById( getXulPrefix() + "assign-role" );//$NON-NLS-1$
unassignRoleButton = (XulButton) document.getElementById( getXulPrefix() + "unassign-role" );//$NON-NLS-1$
assignUserButton = (XulButton) document.getElementById( getXulPrefix() + "assign-user" );//$NON-NLS-1$
unassignUserButton = (XulButton) document.getElementById( getXulPrefix() + "unassign-user" );//$NON-NLS-1$
applyAclButton = (XulButton) document.getElementById( getXulPrefix() + "apply-acl" );//$NON-NLS-1$
// Binding the model user or role list to the ui user or role list
bf.setBindingType( Binding.Type.ONE_WAY );
bf.createBinding( manageAclsModel, "availableUserList", availableUserList, "elements" );//$NON-NLS-1$ //$NON-NLS-2$
bf.createBinding( manageAclsModel, "selectedUserList", selectedUserList, "elements" );//$NON-NLS-1$ //$NON-NLS-2$
bf.createBinding( manageAclsModel, "availableRoleList", availableRoleList, "elements" ); //$NON-NLS-1$ //$NON-NLS-2$
bf.createBinding( manageAclsModel, "selectedRoleList", selectedRoleList, "elements" ); //$NON-NLS-1$ //$NON-NLS-2$
// indicesToObjectsConverter convert the selected indices to the list of objects and vice versa
BindingConvertor<int[], List<UIRepositoryObjectAcl>> indicesToObjectsConverter =
new BindingConvertor<int[], List<UIRepositoryObjectAcl>>() {
@Override
public int[] targetToSource( List<UIRepositoryObjectAcl> acls ) {
if ( acls != null ) {
int i = 0;
int[] retVal = new int[acls.size()];
for ( UIRepositoryObjectAcl acl : acls ) {
retVal[i++] = viewAclsModel.getAceIndex( acl.getAce() );
}
return retVal;
}
return null;
}
@Override
public List<UIRepositoryObjectAcl> sourceToTarget( int[] indices ) {
if ( indices != null && indices.length > 0 ) {
List<UIRepositoryObjectAcl> retVal = new ArrayList<UIRepositoryObjectAcl>();
for ( int i = 0; i < indices.length; i++ ) {
retVal.add( new UIRepositoryObjectAcl( viewAclsModel.getAceAtIndex( indices[i] ) ) );
}
return retVal;
}
return null;
}
};
// indexToAvalableUserConverter convert the selected indices to the list of objects and vice versa
BindingConvertor<int[], List<String>> indexToAvailableUserConverter = new BindingConvertor<int[], List<String>>() {
@Override
public List<String> sourceToTarget( int[] indices ) {
List<String> userList = new ArrayList<String>();
for ( int i = 0; i < indices.length; i++ ) {
userList.add( manageAclsModel.getAvailableUser( indices[i] ) );
}
return userList;
}
@Override
public int[] targetToSource( List<String> userList ) {
int[] indices = new int[userList.size()];
int i = 0;
for ( String user : userList ) {
indices[i++] = manageAclsModel.getAvailableUserIndex( user );
}
return indices;
}
};
BindingConvertor<int[], List<String>> indexToAvailableRoleConverter = new BindingConvertor<int[], List<String>>() {
@Override
public List<String> sourceToTarget( int[] indices ) {
List<String> roleList = new ArrayList<String>();
for ( int i = 0; i < indices.length; i++ ) {
roleList.add( manageAclsModel.getAvailableRole( indices[i] ) );
}
return roleList;
}
@Override
public int[] targetToSource( List<String> roleList ) {
int[] indices = new int[roleList.size()];
int i = 0;
for ( String role : roleList ) {
indices[i++] = manageAclsModel.getAvailableRoleIndex( role );
}
return indices;
}
};
BindingConvertor<int[], List<UIRepositoryObjectAcl>> indexToSelectedUserConverter =
new BindingConvertor<int[], List<UIRepositoryObjectAcl>>() {
@Override
public List<UIRepositoryObjectAcl> sourceToTarget( int[] indices ) {
List<UIRepositoryObjectAcl> userList = new ArrayList<UIRepositoryObjectAcl>();
for ( int i = 0; i < indices.length; i++ ) {
userList.add( manageAclsModel.getSelectedUser( indices[i] ) );
}
return userList;
}
@Override
public int[] targetToSource( List<UIRepositoryObjectAcl> userList ) {
int[] indices = new int[userList.size()];
int i = 0;
for ( UIRepositoryObjectAcl user : userList ) {
indices[i++] = manageAclsModel.getSelectedUserIndex( user );
}
return indices;
}
};
BindingConvertor<int[], List<UIRepositoryObjectAcl>> indexToSelectedRoleConverter =
new BindingConvertor<int[], List<UIRepositoryObjectAcl>>() {
@Override
public List<UIRepositoryObjectAcl> sourceToTarget( int[] indices ) {
List<UIRepositoryObjectAcl> roleList = new ArrayList<UIRepositoryObjectAcl>();
for ( int i = 0; i < indices.length; i++ ) {
roleList.add( manageAclsModel.getSelectedRole( indices[i] ) );
}
return roleList;
}
@Override
public int[] targetToSource( List<UIRepositoryObjectAcl> roleList ) {
int[] indices = new int[roleList.size()];
int i = 0;
for ( UIRepositoryObjectAcl role : roleList ) {
indices[i++] = manageAclsModel.getSelectedRoleIndex( role );
}
return indices;
}
};
// Binding between the selected incides of the lists to the mode list objects
bf.setBindingType( Binding.Type.BI_DIRECTIONAL );
bf.createBinding( availableUserList, "selectedIndices", manageAclsModel, "selectedAvailableUsers",//$NON-NLS-1$ //$NON-NLS-2$
indexToAvailableUserConverter );
bf.createBinding( selectedUserList, "selectedIndices", manageAclsModel, "selectedAssignedUsers",//$NON-NLS-1$ //$NON-NLS-2$
indexToSelectedUserConverter );
bf.createBinding( availableRoleList, "selectedIndices", manageAclsModel, "selectedAvailableRoles",//$NON-NLS-1$ //$NON-NLS-2$
indexToAvailableRoleConverter );
bf.createBinding( selectedRoleList, "selectedIndices", manageAclsModel, "selectedAssignedRoles",//$NON-NLS-1$ //$NON-NLS-2$
indexToSelectedRoleConverter );
// Binding the selected indices of acl list to the list of acl objects in the mode
bf.createBinding( userRoleList, "selectedIndices", viewAclsModel, "selectedAclList", indicesToObjectsConverter ); //$NON-NLS-1$ //$NON-NLS-2$
// accumulatorButtonConverter determine whether to enable of disable the accumulator buttons
BindingConvertor<Integer, Boolean> accumulatorButtonConverter = new BindingConvertor<Integer, Boolean>() {
@Override
public Boolean sourceToTarget( Integer value ) {
if ( value != null && value >= 0 ) {
return true;
}
return false;
}
@Override
public Integer targetToSource( Boolean value ) {
// One way binding, nothing to do here
return null;
}
};
bf.setBindingType( Binding.Type.ONE_WAY );
bf.createBinding( selectedUserList, "selectedIndex", manageAclsModel, "userUnassignmentPossible",//$NON-NLS-1$ //$NON-NLS-2$
accumulatorButtonConverter );
bf.createBinding( availableUserList, "selectedIndex", manageAclsModel, "userAssignmentPossible",//$NON-NLS-1$ //$NON-NLS-2$
accumulatorButtonConverter );
bf.createBinding( manageAclsModel, "userUnassignmentPossible", unassignUserButton, "!disabled" );//$NON-NLS-1$ //$NON-NLS-2$
bf.createBinding( manageAclsModel, "userAssignmentPossible", assignUserButton, "!disabled" );//$NON-NLS-1$ //$NON-NLS-2$
bf.createBinding( selectedRoleList, "selectedIndex", manageAclsModel, "roleUnassignmentPossible",//$NON-NLS-1$ //$NON-NLS-2$
accumulatorButtonConverter );
bf.createBinding( availableRoleList, "selectedIndex", manageAclsModel, "roleAssignmentPossible",//$NON-NLS-1$ //$NON-NLS-2$
accumulatorButtonConverter );
bf.createBinding( manageAclsModel, "roleUnassignmentPossible", unassignRoleButton, "!disabled" );//$NON-NLS-1$ //$NON-NLS-2$
bf.createBinding( manageAclsModel, "roleAssignmentPossible", assignRoleButton, "!disabled" );//$NON-NLS-1$ //$NON-NLS-2$
// Only enable remove Acl button if the entries checkbox is unchecked and acl is selected from the list
bf.createBinding( viewAclsModel, "removeEnabled", removeAclButton, "!disabled" ); //$NON-NLS-1$ //$NON-NLS-2$
// Binding when the user select from the list
bf.createBinding( viewAclsModel, "selectedAclList", this, "aclState", //$NON-NLS-1$ //$NON-NLS-2$
new BindingConvertor<List<UIRepositoryObjectAcl>, UIRepositoryObjectAcl>() {
@Override
public UIRepositoryObjectAcl sourceToTarget( List<UIRepositoryObjectAcl> value ) {
if ( value != null && value.size() > 0 ) {
return value.get( 0 );
}
return null;
}
@Override
public List<UIRepositoryObjectAcl> targetToSource( UIRepositoryObjectAcl value ) {
return null;
}
} );
bf.createBinding( userRoleList, "selectedItem", this, "recipientChanged" ); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
*
* assignUsers method is call to add selected user(s) to the assign users list
*/
public void assignUsers() {
manageAclsModel.assignUsers( Arrays.asList( availableUserList.getSelectedItems() ) );
}
/**
* unassignUsers method is call to add unselected user(s) from the assign users list
*/
public void unassignUsers() {
manageAclsModel.unassign( Arrays.asList( selectedUserList.getSelectedItems() ) );
}
/**
* assignRoles method is call to add selected role(s) to the assign roles list
*/
public void assignRoles() {
manageAclsModel.assignRoles( Arrays.asList( availableRoleList.getSelectedItems() ) );
}
/**
* unassignRoles method is call to add unselected role(s) from the assign roles list
*/
public void unassignRoles() {
manageAclsModel.unassign( Arrays.asList( selectedRoleList.getSelectedItems() ) );
}
public void showManageAclsDialog() throws Exception {
try {
manageAclsModel.clear();
manageAclsModel.setAclsList( service.getAllUsers(), service.getAllRoles() );
} catch ( KettleException ke ) {
messageBox.setTitle( BaseMessages.getString( PKG, "Dialog.Error" ) ); //$NON-NLS-1$
messageBox.setAcceptLabel( BaseMessages.getString( PKG, "Dialog.Ok" ) ); //$NON-NLS-1$
messageBox.setMessage( BaseMessages.getString( PKG,
"PermissionsController.UnableToGetUserOrRole", ke.getLocalizedMessage() ) );//$NON-NLS-1$
messageBox.open();
}
manageAclsDialog.show();
}
public void closeManageAclsDialog() {
manageAclsDialog.hide();
}
/**
* updateAcls method is called when the user click ok on the manage acl dialog. It updates the selection to the model
*
* @throws Exception
*/
public void updateAcls() {
manageAclsModel.updateSelectedAcls();
viewAclsModel.setSelectedAclList( null );
setAclState( null );
closeManageAclsDialog();
}
/**
* removeAcl method is called when the user select a or a list of acls to remove from the list. It first display a
* confirmation box to the user asking to confirm the removal. If the user selected ok, it deletes selected acls from
* the list
*
* @throws Exception
*/
public void removeAcl() throws Exception {
confirmBox.open();
}
/*
* The method is called when a user select an acl from the acl list. This method reads the current selected acl and
* populates the UI with the details
*/
public void setRecipientChanged( UIRepositoryObjectAcl acl ) throws Exception {
List<UIRepositoryObjectAcl> acls = new ArrayList<UIRepositoryObjectAcl>();
// acl == null when user deselects recipient (CTRL-click)
if ( acl != null ) {
acls.add( acl );
}
viewAclsModel.setSelectedAclList( acls );
}
protected void updateCheckboxes( UIRepositoryObjectAcl acl ) {
permissionsCheckboxHandler.updateCheckboxes( hasManageAclAccess(), acl.getPermissionSet() );
}
public void setAclState( UIRepositoryObjectAcl acl ) {
if ( acl != null && acl.getPermissionSet() != null ) {
updateCheckboxes( acl );
} else {
permissionsCheckboxHandler.setAllChecked( false );
permissionsCheckboxHandler.setAllDisabled( true );
}
}
protected boolean hasManageAclAccess() {
try {
Object ro = getSelectedObjects().get( 0 );
if ( ro instanceof IAclObject ) {
return ( (IAclObject) ro ).hasAccess( RepositoryFilePermission.ACL_MANAGEMENT );
}
} catch ( Exception e ) {
if ( KettleRepositoryLostException.lookupStackStrace( e ) == null ) {
throw new RuntimeException( e );
}
}
return false;
}
private void clearSelectedObjAcl() {
Object ro = getSelectedObjects().get( 0 );
if ( ro instanceof IAclObject ) {
( (IAclObject) ro ).clearAcl();
}
}
/*
* updatePermission method is called when the user checks or uncheck any permission checkbox. This method updates the
* current model with the update value from the UI
*/
public void updatePermission() {
UIRepositoryObjectAcl acl = (UIRepositoryObjectAcl) userRoleList.getSelectedItem();
if ( acl == null ) {
throw new IllegalStateException( BaseMessages.getString( PKG, "PermissionsController.NoSelectedRecipient" ) );
}
acl.setPermissionSet( permissionsCheckboxHandler.processCheckboxes() );
clearSelectedObjAcl();
viewAclsModel.updateAcl( acl );
}
/*
* (non-Javadoc)
*
* @see org.pentaho.di.ui.repository.repositoryexplorer.ContextChangeListener#onContextChange() This method is called
* whenever user change the folder or file selection
*/
protected TYPE returnType;
public TYPE onContextChange() {
try {
if ( viewAclsModel.isModelDirty() ) {
if ( !hasManageAclAccess() ) {
// if the user does not have permission to modify the acls,
// ignore any changes, although this code shouldn't be executed
// because all buttons should be disabled.
viewAclsModel.clear();
// Clear the ACL from the backing repo object
clearSelectedObjAcl();
viewAclsModel.setModelDirty( false );
return TYPE.OK;
}
XulConfirmBox confirmBox = null;
try {
confirmBox = (XulConfirmBox) document.createElement( "confirmbox" );//$NON-NLS-1$
} catch ( Exception e ) {
// convert to runtime exception so it bubbles up through the UI
throw new RuntimeException( e );
}
confirmBox.setTitle( BaseMessages.getString( PKG, "PermissionsController.ContextChangeWarning" ) ); //$NON-NLS-1$
confirmBox.setMessage( BaseMessages.getString( PKG, "PermissionsController.ContextChangeWarningText" ) ); //$NON-NLS-1$
confirmBox.setAcceptLabel( BaseMessages.getString( PKG, "Dialog.Yes" ) ); //$NON-NLS-1$
confirmBox.setCancelLabel( BaseMessages.getString( PKG, "Dialog.No" ) ); //$NON-NLS-1$
confirmBox.addDialogCallback( new XulDialogCallback<Object>() {
public void onClose( XulComponent sender, Status returnCode, Object retVal ) {
if ( returnCode == Status.ACCEPT ) {
returnType = TYPE.OK;
viewAclsModel.clear();
// Clear the ACL from the backing repo object
clearSelectedObjAcl();
viewAclsModel.setModelDirty( false );
} else {
returnType = TYPE.CANCEL;
}
}
public void onError( XulComponent sender, Throwable t ) {
returnType = TYPE.NO_OP;
}
} );
confirmBox.open();
} else {
returnType = TYPE.NO_OP;
}
return returnType;
} catch ( Exception e ) {
if ( KettleRepositoryLostException.lookupStackStrace( e ) != null ) {
return TYPE.NO_OP;
} else {
throw new RuntimeException( e );
}
}
}
}