ACLWidget.java
/*
** Module : ACLWidget.java
** Abstract : MVP view.
**
** Copyright (c) 2017-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------Description-----------------------------------
** 001 HC 20170612 Initial version.
** 002 HC 20230427 Added support for file system rights.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** 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 Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.admin.client.application.home.acl;
import com.goldencode.p2j.admin.*;
import com.goldencode.p2j.admin.client.*;
import com.goldencode.p2j.admin.client.application.*;
import com.goldencode.p2j.admin.client.application.home.*;
import com.goldencode.p2j.admin.client.application.home.acl.model.*;
import com.goldencode.p2j.admin.client.editors.*;
import com.goldencode.p2j.admin.client.widget.*;
import com.goldencode.p2j.admin.client.widget.dialog.*;
import com.goldencode.p2j.admin.client.widget.dialog.InputDialog.*;
import com.goldencode.p2j.admin.shared.*;
import com.goldencode.p2j.security.*;
import com.google.gwt.event.dom.client.*;
import com.google.gwt.regexp.shared.*;
import com.google.gwt.uibinder.client.*;
import com.google.gwt.user.client.*;
import com.google.gwt.user.client.ui.*;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.*;
import com.gwtplatform.mvp.client.proxy.*;
import com.gwtplatform.mvp.shared.proxy.*;
import org.gwtbootstrap3.client.ui.Button;
import org.gwtbootstrap3.client.ui.CheckBox;
import org.gwtbootstrap3.client.ui.*;
import org.gwtbootstrap3.client.ui.TextBox;
import org.gwtbootstrap3.client.ui.form.validator.*;
import org.gwtbootstrap3.client.ui.gwt.*;
import org.gwtbootstrap3.client.ui.html.*;
import org.gwtbootstrap3.extras.select.client.ui.*;
import java.util.*;
import java.util.function.*;
/**
* ACL view.
*/
public class ACLWidget
extends Composite
{
/**
* GWT binder.
*/
interface Binder
extends UiBinder<Container, ACLWidget>
{}
/** the string representation of shared instance */
private static final String SHARED_INSTANCE = "*shared*";
/** widget */
@UiField
Button bRefresh;
/** widget */
@UiField
Button bAdd;
/** widget */
@UiField
Button bDelete;
/** widget */
@UiField
Button bClone;
/** widget */
@UiField
Button bMoveUp;
/** widget */
@UiField
Button bMoveDown;
/** widget */
@UiField
Button bFilter;
/** widget */
@UiField
Button bResequence;
/** acl grid widget */
@UiField(provided = true)
DataGrid grid = GridHelper.create();
/** widget */
@UiField
InlineCheckBox cpcEv;
/** widget */
@UiField
SimplePanel modalPanel;
/** widget */
@UiField
SimplePanel rightsModalPanel;
/** widget */
@UiField
Strong aclTitle;
/** state definition - initial empty */
private final boolean[] STATE_INITIAL_EMPTY =
{
/* cpbR, */ true,
/* cpbA, */ true,
/* cpbD, */ false,
/* cpbC, */ false,
/* cpbMu, */ false,
/* cpbMd, */ false,
/* cpbF */ true,
/* cpbS */ false
};
/** state definition - single selection */
private final boolean[] STATE_ACL_SINGLE =
{
/* cpbR, */ true,
/* cpbA, */ true,
/* cpbD, */ true,
/* cpbC, */ true,
/* cpbMu, */ true,
/* cpbMd, */ true,
/* cpbF */ true,
/* cpbS */ true
};
/** state definition - multiple selection */
private final boolean[] STATE_ACL_MULTI =
{
/* cpbR, */ true,
/* cpbA, */ true,
/* cpbD, */ true,
/* cpbC, */ false,
/* cpbMu, */ false,
/* cpbMd, */ false,
/* cpbF */ true,
/* cpbS */ false
};
/** Access Control filtered by account. */
private String account;
/** Access Control filtered by resource type. */
private String resourceType;
/** Access Control filtered by resource instance name. */
private String resourceName;
/** The last applied filter */
private String lastFilter;
/** The last applied filter */
private RegExp lastFilterRegexp;
/** All the ACLs, with no menu filters applied. */
private List<AclRow> allAcls = null;
/** The ui state machine */
private ViewStateMachineImpl stateMachine = null;
/** Input dialog provider */
private Provider<InputDialog> inputDialogProvider;
/** Admin service reference */
private AdminServiceAsync adm;
/** ACL grid handle */
private GridHandle<AclRow> gridHandle;
/** Effective view flag */
private boolean effectiveView;
/** The rights being edited */
private Rights editRights;
/** Alarm service */
private Alarm alarm;
/** Moda dialogs */
private ModalDialogs dialogs;
/** Place manager reference */
private PlaceManager placeManager;
/**
* Ctor.
*
* @param binder
* GWT binder.
* @param adm
* Admin service.
* @param alarm
* Alarm service.
* @param dialogs
* Modal dialogs.
* @param placeManager
* Place manage.
* @param inputDialogProvider
* Input dialog provider.
*/
@Inject
ACLWidget(Binder binder,
AdminServiceAsync adm,
Alarm alarm,
ModalDialogs dialogs,
PlaceManager placeManager,
Provider<InputDialog> inputDialogProvider)
{
this.adm = adm;
this.alarm = alarm;
this.dialogs = dialogs;
this.placeManager = placeManager;
this.inputDialogProvider = inputDialogProvider;
initWidget(binder.createAndBindUi(this));
//noinspection NonJREEmulationClassesInClientCode
gridHandle = GridHelper.initListGrid(
grid, true,
new GridHelper.ColumnInfo<AclRow>("Instance", r -> r.getValueAt(0), (r1, r2) ->
r1.getValueAt(0).compareTo(r2.getValueAt(0)), aclRow -> handleACLCellEdit(aclRow, 0)),
new GridHelper.ColumnInfo<>("Resource Type", r -> r.getValueAt(1), (r1, r2) ->
r1.getValueAt(1).compareTo(r2.getValueAt(1))),
new GridHelper.ColumnInfo<>("ID", r -> r.getValueAt(2), (r1, r2) ->
r1.getValueAt(2).compareTo(r2.getValueAt(2)), aclRow -> handleACLCellEdit(aclRow, 2)),
new GridHelper.ColumnInfo<>("Resource Instance Name", r -> r.getValueAt(3), (r1, r2) ->
r1.getValueAt(3).compareTo(r2.getValueAt(3)), aclRow -> handleACLCellEdit(aclRow, 3)),
new GridHelper.ColumnInfo<>("Subjects", r -> r.getValueAt(4), (r1, r2) ->
r1.getValueAt(4).compareTo(r2.getValueAt(4)), aclRow -> handleACLCellEdit(aclRow, 4)),
new GridHelper.ColumnInfo<>("Rights", r -> r.getValueAt(5), (r1, r2) ->
r1.getValueAt(5).compareTo(r2.getValueAt(5)), aclRow -> handleACLCellEdit(aclRow, 5))
);
gridHandle.getSelectionModel().addSelectionChangeHandler(
event ->
{
handleACLsSelection();
});
// init view state machine
stateMachine = new ViewStateMachineImpl(new HasEnabled[]
{
bRefresh, bAdd, bDelete, bClone, bMoveUp, bMoveDown, bFilter, bResequence
});
stateMachine.stateMachine(STATE_INITIAL_EMPTY);
}
/**
* Called when the view is navigated to.
*/
public void onNavigation()
{
PlaceRequest place = placeManager.getCurrentPlaceRequest();
String resourceType = place.getParameter("r", null);
String account = place.getParameter("a", null);
String filter = place.getParameter("f", null);
String name = place.getParameter("n", null);
setFilter(resourceType, account, filter, name);
}
/**
* Sets ACL filter.
*
* @param resourceType
* Resource type.
* @param account
* Account id.
* @param filter
* Filter regular expression.
* @param name
* Resource instance name.
*/
public void setFilter(String resourceType, String account, String filter, String name)
{
this.resourceType = resourceType;
this.account = account;
this.resourceName = name;
this.lastFilter = filter;
if (lastFilter == null || lastFilter.trim().isEmpty())
{
lastFilterRegexp = null;
}
else
{
lastFilterRegexp = RegExp.compile(lastFilter);
}
updateACLTitle();
refresh();
}
/**
* Click handler.
*
* @param event
* Click event.
*/
@UiHandler("bRefresh")
public void bRefreshClick(ClickEvent event)
{
refresh();
}
/**
* Click handler.
*
* @param event
* Click event.
*/
@UiHandler("bAdd")
public void bAddClick(ClickEvent event)
{
handleAddACL();
}
/**
* Click handler.
*
* @param event
* Click event.
*/
@UiHandler("bDelete")
public void bDeleteClick(ClickEvent event)
{
handleDeleteSelectedACL();
}
/**
* Click handler.
*
* @param event
* Click event.
*/
@UiHandler("bClone")
public void bCloneClick(ClickEvent event)
{
handleCloneACLItem();
}
/**
* Click handler.
*
* @param event
* Click event.
*/
@UiHandler("bMoveUp")
public void bMoveUpClick(ClickEvent event)
{
handleMoveACLUp();
}
/**
* Click handler.
*
* @param event
* Click event.
*/
@UiHandler("bMoveDown")
public void bMoveDownClick(ClickEvent event)
{
handleMoveACLDown();
}
/**
* Click handler.
*
* @param event
* Click event.
*/
@UiHandler("bFilter")
public void bFilterClick(ClickEvent event)
{
handleFilter();
}
/**
* Click handler.
*
* @param event
* Click event.
*/
@UiHandler("bResequence")
public void bResequenceClick(ClickEvent event)
{
handleResequenceACLs();
}
/**
* Click handler.
*
* @param event
* Click event.
*/
@UiHandler("cpcEv")
public void cpcEvChange(ChangeEvent event)
{
refresh();
}
/**
* Refresh the ACL list from the server.
*/
public void refresh()
{
populateACLList(null);
}
/**
* Grid row selection change handler.
*/
private void handleACLsSelection()
{
Set<AclRow> sel = gridHandle.getSelected();
if (sel.isEmpty())
{
// no selection
setACLsNoSelection();
}
else if (sel.size() == 1)
{
// single selection
setACLsSingleSelection();
}
else
{
// multiple selection
setACLsMultiSelection();
}
}
/**
* Updates the ACL title.
*/
private void updateACLTitle()
{
StringBuilder sb = new StringBuilder();
sb.append("Access Control");
boolean and = false;
if (account != null && !account.trim().isEmpty())
{
sb.append(" for account <" + account + ">");
and = true;
}
if (resourceType != null && !resourceType.trim().isEmpty())
{
sb.append(" for resource <" + resourceType + ">");
and = true;
}
if (resourceName != null && !resourceName.trim().isEmpty())
{
sb.append(" for resource instance name <" + resourceName + ">");
and = true;
}
String prefix = and ? " and" : " for";
sb.append(prefix + " server <" + ClientContext.instance().getServerName() + ">");
if (lastFilterRegexp != null)
{
sb.append(" filtered by <" + lastFilter + ">");
}
aclTitle.setText(sb.toString());
}
/**
* Populate the list of ACLs with fresh data from the server.
*
* @param doneHandler
* The done handler
*/
private void populateACLList(Runnable doneHandler)
{
gridHandle.clearGrid();
// populate the ACL table
loadAclData(list ->
{
if (list != null)
{
if (!list.isEmpty())
{
gridHandle.addRows(list, lastFilterRegexp);
}
}
else
{
// TODO: beep!
//client.beep("no ACLs");
}
if (doneHandler != null)
{
doneHandler.run();
}
});
}
/**
* Load the ACL items into {@link AclRow} instances and return them. Any
* menu filtering is applied at this time.
* <p>
* If filtering by resource type is in effect, this method takes into
* account the state of the Effective View check box and the existence of
* both the shared and private instances. It disables the check box if only
* one instance is found, but the check box remains selected.
*
* @param resultConsumer
* Called with the result, null is passed when an error occurs.
*/
private void loadAclData(Consumer<List<AclRow>> resultConsumer)
{
// load the ACLs from server
if (resourceType != null)
{
String serverName = ClientContext.instance().getServerName();
// filtering by resource type
// figure out the effective versus full view
adm.listAclInstances(resourceType, new AdminCallback<String[]>(adm, alarm)
{
@Override
public void onDone(String[] found)
{
if (found == null || found.length == 0)
{
// nothing to show for this resource now
resultConsumer.accept(null);
return;
}
List<String> foundList = Arrays.asList(found);
boolean isShar = foundList.contains("");
boolean isPriv = foundList.contains(serverName);
if (!isShar && !isPriv)
{
// nothing to show for the live server
resultConsumer.accept(null);
return;
}
if (isShar && isPriv)
{
// either effective or full view is possible
// the views will be different:
// Effective View check box remains enabled and has effect
cpcEv.setEnabled(true);
effectiveView = cpcEv.getValue();
if (effectiveView)
{
// produce the effective view of ACLs
adm.getEffectiveAcls(resourceType, new AdminCallback<AclDef>()
{
@Override
public void onDone(AclDef result)
{
if (result != null)
{
AclDef[] acls = new AclDef[]{result};
loadAclDataStep2(acls, resultConsumer);
}
}
});
}
else
{
// produce full view of both shared and private instances
MultiCallback mc = new MultiCallback();
AdminCallback<AclDef> aSharCb = mc.newCallback();
AdminCallback<AclDef> aPrivCb = mc.newCallback();
mc.onDone(() ->
{
if (mc.isSuccess())
{
AclDef aShar = aSharCb.getResult();
AclDef aPriv = aPrivCb.getResult();
AclDef[] aslcs = new AclDef[]{aShar, aPriv};
loadAclDataStep2(aslcs, resultConsumer);
}
else
{
resultConsumer.accept(null);
}
});
adm.getAcl(resourceType, "", aSharCb);
adm.getAcl(resourceType, serverName, aPrivCb);
}
}
else
{
// effective or full view makes no difference:
// Effective View check box gets disabled
cpcEv.setEnabled(false);
String instance = isShar ? "" : serverName;
adm.getAcl(resourceType, instance, new AdminCallback<AclDef>()
{
@Override
public void onDone(AclDef result)
{
if (result != null)
{
loadAclDataStep2(new AclDef[] {result}, resultConsumer);
}
}
});
// control the ACL sort order: oid
effectiveView = true;
}
}
});
}
else
{
// no filtering by resource type; load full ACLs
cpcEv.setEnabled(false);
adm.getAllAcls(null, new AdminCallback<AclDef[]>(adm, alarm)
{
@Override
public void onDone(AclDef[] result)
{
if (result != null)
{
loadAclDataStep2(result, resultConsumer);
}
}
});
// control the ACL sort order: instance, resource type, oid
effectiveView = false;
}
}
/**
* Load the ACL items into {@link AclRow} instances and return them. Any
* menu filtering is applied at this time. This is step 2 of the loading process.
*
* @param acls
* The ACL items
* @param resultConsumer
* Called with the result, null is passed when an error occurs.
*/
private void loadAclDataStep2(AclDef[] acls, Consumer<List<AclRow>> resultConsumer)
{
// client-side filtering
List<AclRow> list = new ArrayList<>();
allAcls = new ArrayList<>();
int errs = 0;
for (int i = 0; i < acls.length; i++)
{
errs += acls[i].errors;
for (int j = 0; j < acls[i].acls.length; j++)
{
Acl acl = acls[i].acls[j];
boolean ok = account == null;
Set<String> subjectsSet = new TreeSet<String>();
StringBuilder subjects = new StringBuilder();
for (int r = 0; r < acl.subjects.length; r++)
{
String acc = acls[i].subjects.get(acl.subjects[r]);
if (acl.subjects[r] == AclDef.ALL_OTHERS)
{
acc = "all_others";
}
if (acc == null)
{
acc = "*not found*";
}
// filter results by account, if needed
if (!ok && acc.equals(account))
{
ok = true;
}
subjects.append(acc);
if (acl.subjects[r] >= 0)
{
subjectsSet.add(acc);
}
if (r < acl.subjects.length - 1)
{
subjects.append(", ");
}
}
// filter by resource instance name if set
if (resourceName != null && ok)
{
ok = acl.name == null || acl.name.equals(resourceName);
}
AclRow row = new AclRow(acls[i], acl, subjects.toString(), subjectsSet);
allAcls.add(row);
// if the specified account is not among the subjects, do not add
// it to the table.
if (ok)
{
list.add(row);
}
}
}
// report ACLs in errors
if (errs > 0)
{
// TODO: beep
//String msg = String.format("%d loaded ACLs are with errors", errs);
//client.beep(msg);
Window.alert(errs + " loaded ACLs are with errors");
}
else
{
// TODO
//client.ok();
}
// the entire set is sorted by type and then by id
Collections.sort(allAcls);
resultConsumer.accept(list);
}
/**
* Set the state of the screen when no row is selected - some buttons
* are enabled.
*/
private void setACLsNoSelection()
{
// set enabled state of the list accordingly
stateMachine.stateMachine(STATE_INITIAL_EMPTY);
}
/**
* Set the state of the screen when a single row is selected - all buttons
* are enabled.
*/
private void setACLsSingleSelection()
{
// set enabled state of the list accordingly
stateMachine.stateMachine(STATE_ACL_SINGLE);
}
/**
* Set the state of the screen when multiple rows are selected - some
* buttons are enabled.
*/
private void setACLsMultiSelection()
{
// set enabled state of the list accordingly
stateMachine.stateMachine(STATE_ACL_MULTI);
}
/**
* Handle row filtering using a generic filter.
*/
@SuppressWarnings("unchecked")
private void handleFilter()
{
InputDialog dlg = inputDialogProvider.get();
String txt = "Define the filtering specification below. Press the\n" +
"'OK' button without any specification to remove any\n" +
"previous filter.";
Field fInput;
dlg.build("Filter", txt, new Field[]
{
fInput = new Field(null, lastFilter == null ? "" : lastFilter, true)
});
dlg.show(modalPanel, spec ->
{
if (spec == null)
{
return;
}
String val = (String) spec.get(fInput);
if (val.equals(lastFilter))
{
return;
}
if (isPopup())
{
setFilter(resourceType, account, val, resourceName);
}
else
{
navigateHere("f", val);
}
});
}
/**
* Navigates to the view to pass new filter parameters in the view in a form of new browser
* history entry.
*
* @param param
* URL query param name.
* @param value
* URL quert param value.
*/
private void navigateHere(String param, String value)
{
PlaceRequest place = placeManager.getCurrentPlaceRequest();
PlaceRequest.Builder pb = new PlaceRequest.Builder().nameToken(place.getNameToken());
boolean paramFound = false;
for (String currParam : place.getParameterNames())
{
//noinspection NonJREEmulationClassesInClientCode
if (currParam.equalsIgnoreCase(param))
{
paramFound = true;
if (value == null)
{
continue;
}
pb.with(currParam, value);
}
else
{
pb.with(currParam, place.getParameter(currParam, null));
}
}
if (!paramFound) {
pb.with(param, value);
}
placeManager.revealPlace(pb.build(), true);
}
/**
* Handles edit request of the provided ACL record and the grid column.
*
* @param aclItem
* ACL row.
* @param cell
* Cell index.
*/
private void handleACLCellEdit(AclRow aclItem, int cell)
{
if (cell == 0)
{
Field fPrivate;
Field fShared;
// move ACL to another instance
InputDialog dlg = inputDialogProvider.get();
dlg.build("Move ACL to another Instance", "",
new Field[]
{
fPrivate = new Field("Private Instance", aclItem.getInstance()),
fShared = new Field("Shared Instance", "".equals(aclItem.getInstance()))
});
TextBox text = (TextBox) dlg.getWidget(fPrivate);
InlineCheckBox check = (InlineCheckBox) dlg.getWidget(fShared);
text.setEnabled(!check.getValue());
check.addChangeHandler(event -> { text.setEnabled(!check.getValue()); });
dlg.show(this.modalPanel, false, vals ->
{
if (vals != null)
{
String newInstance = (String) vals.get(fPrivate);
Boolean sharedCheck = (Boolean) vals.get(fShared);
adm.moveAcl(aclItem.getResourceType(),
aclItem.getAcl().origin,
aclItem.getAcl().oid,
!sharedCheck ? newInstance : "",
new AdminCallback<Boolean>()
{
@Override
public void onDone(Boolean result)
{
if (!result)
{
alarm.ring();
}
else
{
dlg.dismiss();
}
refresh();
}
});
}
});
}
if (cell == 2)
{
Field fTargetId;
// edit ID
InputDialog dlg = inputDialogProvider.get();
dlg.build("Move ACL around", "",
new Field[]
{
fTargetId = new Field("Target ID", Integer.class, false)
});
dlg.show(this.modalPanel, false, vals ->
{
if (vals != null)
{
Integer targetId = (Integer) vals.get(fTargetId);
adm.moveAcl(aclItem.getResourceType(),
aclItem.getAcl().origin,
aclItem.getAcl().oid,
targetId,
new AdminCallback<Boolean>()
{
@Override
public void onDone(Boolean result)
{
if (!result)
{
alarm.ring();
}
else
{
dlg.dismiss();
}
refresh();
}
});
}
});
}
if (cell == 3)
{
String name = aclItem.isErrorName() ? "" : aclItem.getName();
Field fName, fRegexp;
InputDialog dlg = inputDialogProvider.get();
dlg.build("Change Resource Instance Name", "",
new Field[]
{
fName = new Field(null, name, false),
fRegexp = new Field("Regexp Match (Uncheck for Exact Match)", !aclItem.getExact())
});
dlg.show(this.modalPanel, false, vals ->
{
if (vals != null)
{
String instanceName = (String) vals.get(fName);
Boolean regexpNature = (Boolean) vals.get(fRegexp);
// clear selection before the key field (name) changes, otherwise the selection
// will stay in the selection model forever
gridHandle.clearSelection();
Acl acl = aclItem.getAcl();
acl.name = instanceName;
acl.exact = !regexpNature;
// ensure instance name is provided
if (acl.origin == null)
{
acl.origin = aclItem.getAclDef().server;
}
adm.setAcl(aclItem.getResourceType(),
aclItem.getAcl(),
Collections.emptyMap(),
true, true, false, false,
new AdminCallback<Boolean>()
{
@Override
public void onDone(Boolean result)
{
if (!result)
{
alarm.ring();
}
else
{
dlg.dismiss();
}
refresh();
}
});
}
});
}
if (cell == 4)
{
boolean allOthers = aclItem.getAcl().subjects.length == 1 &&
aclItem.getAcl().subjects[0] == AclDef.ALL_OTHERS;
loadAllSubjects(options ->
{
List<String> subjects = new ArrayList<>(aclItem.getSubjectsSet());
Field fAll, fSubjects;
InputDialog dlg = inputDialogProvider.get();
dlg.build("Change Associated Subjects", "",
new Field[]
{
fAll = new Field("Associate All Accounts", allOthers),
fSubjects = new MultiSelectField(null, options, subjects, false, null)
});
CheckBox allOthersCb = (CheckBox) dlg.getWidget(fAll);
SelectBase select = (SelectBase) dlg.getWidget(fSubjects);
allOthersCb.addChangeHandler(event ->
{
select.setEnabled(!allOthersCb.getValue());
});
dlg.show(this.modalPanel, false, vals ->
{
if (vals != null)
{
Map<Integer, String> subjMap = new HashMap<>();
boolean all = (boolean) vals.get(fAll);
int[] subjs;
if (all)
{
subjMap.put(0, "all_others");
subjs = new int[] {AclDef.ALL_OTHERS};
}
else
{
int i = 0;
List<String> subjectList = (List<String>) vals.get(fSubjects);
subjs = new int[subjectList.size()];
for (String subj : subjectList)
{
subjs[i] = i;
subjMap.put(i++, subj);
}
}
Acl acl = aclItem.getAcl();
acl.subjects = subjs;
// ensure instance name is provided
if (acl.origin == null)
{
acl.origin = aclItem.getAclDef().server;
}
adm.setAcl(aclItem.getResourceType(),
aclItem.getAcl(),
subjMap,
false, false, true, false,
new AdminCallback<Boolean>(adm, alarm)
{
@Override
public void onDone(Boolean result)
{
dlg.dismiss();
refresh();
}
});
}
});
});
}
if (cell == 5)
{
String resourceName = aclItem.getName();
boolean exact = aclItem.getExact();
String type = aclItem.getResourceType();
Rights source = aclItem.getAcl().rights;
if (aclItem.isErrorRights())
{
source = null;
}
editRights(resourceName, exact, type, source, rights ->
{
if (rights != null)
{
aclItem.getAcl().rights = rights;
aclItem.clearErrorRights();
// ensure instance name is provided
if (aclItem.getAcl().origin == null)
{
aclItem.getAcl().origin = aclItem.getAclDef().server;
}
adm.setAcl(
type,
aclItem.getAcl(),
Collections.EMPTY_MAP,
false,
false,
false,
true,
new AdminCallback<Boolean>(adm, alarm)
{
@Override
public void onDone(Boolean result)
{
refresh();
}
});
}
});
}
}
/**
* Loads all subjects.
*
* @param result
* The result consumer.
*/
private void loadAllSubjects(Consumer<List<Option>> result)
{
MultiCallback mc = new MultiCallback();
AdminCallback<TaggedName[]> usersCb = mc.newCallback();
AdminCallback<TaggedName[]> groupsCb = mc.newCallback();
AdminCallback<TaggedName[]> procsCb = mc.newCallback();
mc.onDone(() ->
{
if (!mc.isSuccess())
{
return;
}
List<TaggedName> users = new ArrayList<>(Arrays.asList(usersCb.getResult()));
List<TaggedName> procs = new ArrayList<>(Arrays.asList(procsCb.getResult()));
List<TaggedName> groups = new ArrayList<>(Arrays.asList(groupsCb.getResult()));
List<TaggedName> allSubjects = new ArrayList<>();
allSubjects.addAll(users);
allSubjects.addAll(procs);
allSubjects.addAll(groups);
Collections.sort(allSubjects);
List<Option> optsResult = new ArrayList<>();
for (TaggedName subject : allSubjects)
{
String type;
if (users.contains(subject))
{
type = "user";
}
else if (groups.contains(subject))
{
type = "group";
}
else if (procs.contains(subject))
{
type = "process";
}
else
{
continue;
}
Option opt = new Option();
String name = subject.getName();
opt.setValue(name);
opt.setText(name);
opt.setSubtext("(" + type + ", " + subject.getTag() + ")");
optsResult.add(opt);
}
result.accept(optsResult);
});
adm.listUsers(usersCb);
adm.listGroups(groupsCb);
adm.listProcesses(procsCb);
}
/**
* Handle Add ACL Item button press. Prepares and displays "Add ACL" dialog.
*/
private void handleAddACL()
{
MultiCallback mc = new MultiCallback();
AdminCallback<String[]> instances = mc.newCallback();
AdminCallback<AdminDef> admDef = mc.newCallback();
AdminCallback<List<Option>> subjects = mc.newCallback();
mc.onDone(() ->
{
AdminDef admDefRes = admDef.getResult();
String[] instancesRes = instances.getResult();
List<String> instancesList = new ArrayList<>(instancesRes.length);
for (int i = 0; i < instancesRes.length; i++)
{
String in = instancesRes[i].length() == 0 ? SHARED_INSTANCE : instancesRes[i];
instancesList.add(in);
}
if (admDefRes == null || instancesRes == null)
{
return;
}
AclRow sel = getFirstSelectedRow();
String defInstance;
if (sel != null)
{
defInstance = sel.getInstance();
}
else
{
defInstance = instancesRes.length > 0 ? instancesRes[0] : null;
}
String defType = null;
if (sel != null)
{
defType = sel.getResourceType();
}
List<String> types = new ArrayList<>(admDefRes.customAclEditors.size());
for (String type : admDefRes.customAclEditors)
{
if (defType == null)
{
defType = type;
}
types.add(type);
}
int id = nextId(false, true);
InputGroupButton rightsAddon = new InputGroupButton();
Button editRightsButton = new Button("Edit");
rightsAddon.add(editRightsButton);
Field fServer, fRes, fId, fName, fRegexp, fSubjects, fAll, fRights;
InputDialog dlg = inputDialogProvider.get();
dlg.build("Add ACL Item", "",
new Item[]
{
// allow empty server entry
fServer = new Field("Server this ACL item is for", defInstance, true)
.withSuggestions(instancesList),
fRes = new SelectField("Resource Type", types, defType),
fId = new Field("Access Control ID", id >= 0 ? id : Integer.class, false,
new DecimalMinValidator(0)),
fRegexp = new Field("Regexp Match (Uncheck for Exact Match)", false),
fName = new Field("Resource Instance Name", "", false),
new InputDialog.Span
(
fSubjects = new MultiSelectField("Subjects", subjects.getResult(),
Collections.emptyList(), false, null),
fAll = new Field("Associate All Accounts", true)
),
fRights = new Field("Rights", "", false).withAddon(rightsAddon),
});
Select resourceBox = (Select) dlg.getWidget(fRes);
CheckBox regexpBox = (CheckBox) dlg.getWidget(fRegexp);
MultipleSelect subjBox = (MultipleSelect) dlg.getWidget(fSubjects);
CheckBox allOthersCb = (CheckBox) dlg.getWidget(fAll);
subjBox.setEnabled(false);
allOthersCb.addChangeHandler(event ->
{
subjBox.setEnabled(!allOthersCb.getValue());
});
TextBox rightsBox = (TextBox) dlg.getWidget(fRights);
rightsBox.setReadOnly(true);
Consumer<String> resetRights = val ->
{
RightsEditorContext ctxt = RightsEditorContext.instance();
ctxt.setInputDialog(inputDialogProvider.get());
ctxt.setParent(rightsModalPanel);
RightsEditor ed = ctxt.getEditor(val);
editRights = ed.createDefaultRights();
rightsBox.setValue(editRights.toString());
};
resetRights.accept(defType);
Select resTypeBox = (Select) dlg.getWidget(fRes);
resTypeBox.addValueChangeHandler(val ->
{
if (val != null && val.getValue() != null)
{
resetRights.accept(val.getValue());
}
else
{
resetRights.accept("");
}
});
resourceBox.addValueChangeHandler(event ->
{
resetRights.accept(resTypeBox.getValue());
});
regexpBox.addValueChangeHandler(event ->
{
resetRights.accept(resTypeBox.getValue());
});
editRightsButton.addClickHandler(event ->
{
editRights(dlg.getValue(fName, ""),
!regexpBox.getValue(),
resTypeBox.getValue(),
editRights, rights ->
{
if (rights != null)
{
editRights = rights;
rightsBox.setValue(editRights.toString());
}
});
});
dlg.show(this.modalPanel, false, vals ->
{
if (vals != null)
{
Map<Integer, String> subjMap = new HashMap<>();
boolean all = (boolean) vals.get(fAll);
int[] subjs;
if (all)
{
subjMap.put(0, "all_others");
subjs = new int[] {AclDef.ALL_OTHERS};
}
else
{
int i = 0;
List<String> subjectList = (List<String>) vals.get(fSubjects);
subjs = new int[subjectList.size()];
for (String subj : subjectList)
{
subjs[i] = i;
subjMap.put(i++, subj);
}
}
String instance = (String) vals.get(fServer);
String resType = (String) vals.get(fRes);
int aclId = (int) vals.get(fId);
String resName = (String) vals.get(fName);
boolean regexpNature = (boolean) vals.get(fRegexp);
if (!instancesList.contains(instance))
{
dialogs.showYesNo(
"Warning",
"About to create a new ACL instance " + instance + ". Continue?",
MessageType.WARNING,
r ->
{
// yes
if (r == 0)
{
addACLStep2(instance,
resType,
aclId,
resName,
regexpNature,
subjs,
subjMap,
editRights,
(res) ->
{
if (res)
{
dlg.dismiss();
}
});
}
});
}
else
{
addACLStep2(instance,
resType,
aclId,
resName,
regexpNature,
subjs,
subjMap,
editRights,
(res) ->
{
if (res)
{
dlg.dismiss();
}
});
}
}
});
});
adm.listAclInstances(instances);
adm.getAdminDef(admDef);
loadAllSubjects(l -> subjects.onSuccess(l));
}
/**
* 2nd phase of new ACL dialog.
*
* @param instance
* Instance name.
* @param resType
* Resource type.
* @param aclId
* Acl id.
* @param resName
* Resource name.
* @param regexpNature
* Regexp nature flag.
* @param subjs
* Subject ids.
* @param subjMap
* Subject map.
* @param rights
* Rights object.
* @param doneHandler
* Done handler.
*/
private void addACLStep2(String instance,
String resType,
int aclId,
String resName,
boolean regexpNature,
int[] subjs,
Map<Integer, String> subjMap,
Rights rights,
Consumer<Boolean> doneHandler)
{
//noinspection NonJREEmulationClassesInClientCode
if (instance.equalsIgnoreCase(SHARED_INSTANCE))
{
instance = "";
}
Acl acl = new Acl();
acl.origin = instance;
acl.oid = aclId;
acl.name = resName;
acl.exact = !regexpNature;
acl.subjects = subjs;
acl.rights = rights;
adm.addAcl(resType, acl, subjMap, new AdminCallback<Boolean>(adm, alarm)
{
@Override
public void onDone(Boolean result)
{
doneHandler.accept(result != null && result);
refresh();
}
});
}
/**
* Compute another ID based on the ID of the selected item.
*
* @param up
* <code>true</code> if the new ID must be lower than the item's
* ID. <code>false</code> if the ID must be greater than the
* item's ID.
* @param current
* <code>true</code> if the selected item is considered the
* origin. <code>false</code> if the prior/next item is used,
* depending on the direction (up/down).
*
* @return the new ID
*/
private int nextId(boolean up, boolean current)
{
// find out the first selected acl row based on the defaultly sorted list
AclRow selRow = getFirstSelectedRow();
if (selRow == null)
{
return -1;
}
// check to see whether the selected row is the boundary
int dir = (up ? -1 : 1);
int selIdx = allAcls.indexOf(selRow);
int sid = selRow.getId();
boolean boundary = false;
AclRow immedNext = null;
int immedIdx = -1;
int iid = -1;
if (selIdx == 0 && up)
{
boundary = true;
}
else if (selIdx == allAcls.size() - 1 && !up)
{
boundary = true;
}
else
{
immedIdx = selIdx + dir;
immedNext = allAcls.get(immedIdx);
iid = immedNext.getId();
if (!immedNext.getInstance().equals(selRow.getInstance()) ||
!immedNext.getResourceType().equals(selRow.getResourceType()))
{
boundary = true;
}
}
// handle the boundary case
if (boundary)
{
if (up)
{
if (sid == 1)
{
// no room to move to
return -1;
}
return sid / 2;
}
return sid + 100;
}
// when moving, the next item is used (current flag is false)
// when adding/cloning, the current item must be used
if (current)
{
// place the new ID between the initial selection and immediate next
int distance = Math.abs(iid - sid);
if (distance == 1)
{
// no room to move to
return -1;
}
return sid + dir * distance / 2;
}
// check to see whether the immediate next row is the boundary
int nextIdx = -1;
AclRow nextRow = null;
int nid = -1;
if (immedIdx == 0 && up)
{
boundary = true;
}
else if (immedIdx == allAcls.size() - 1 && !up)
{
boundary = true;
}
else
{
nextIdx = immedIdx + dir;
nextRow = allAcls.get(nextIdx);
nid = nextRow.getId();
if (!nextRow.getInstance().equals(selRow.getInstance()) ||
!nextRow.getResourceType().equals(selRow.getResourceType()))
{
boundary = true;
}
}
// handle the immediate next boundary case
if (boundary)
{
if (up)
{
if (iid == 1)
{
// no room to move to
return -1;
}
return iid / 2;
}
return iid + 100;
}
// found two siblings, place the new ID between them
int distance = Math.abs(nid - iid);
if (distance == 1)
{
// no room to move to
return -1;
}
return iid + dir * distance / 2;
}
/**
* Returns the first selected row.
*
* @return the first selected row
*/
private AclRow getFirstSelectedRow()
{
Set<AclRow> selSet = gridHandle.getSelected();
if (selSet.isEmpty())
{
return null;
}
AclRow selRow = null;
for (AclRow r : allAcls)
{
if (selSet.contains(r))
{
selRow = r;
break;
}
}
return selRow;
}
/**
* Handle Delete Selected ACL button press.
*/
private void handleDeleteSelectedACL()
{
Set<AclRow> selected = gridHandle.getSelected();
// ask confirmation
String quest = null;
String title = null;
if (selected.size() == 1)
{
AclRow row = selected.iterator().next();
StringBuilder sb = new StringBuilder();
sb.append("Deleting ACL ").append(row.getId()).append(" in <")
.append(row.getValueAt(0)).append("><").append(row.getValueAt(1))
.append(">. Continue?");
quest = sb.toString();
title = "Deleting item";
}
else
{
quest = "Deleting " + selected.size() + " items. Continue?";
title = "Deleting items";
}
dialogs.showYesNo(title, quest, MessageType.QUESTION, r ->
{
// yes
if (r == 0)
{
deletedACL(selected);
}
});
}
/**
* Deletes the supplied set of acls.
*
* @param rows
* The acls to delete.
*/
private void deletedACL(Set<AclRow> rows)
{
MultiCallback mc = new MultiCallback(adm, alarm);
mc.onDone(() -> refresh());
List<AdminCallback> cbs = new ArrayList<>(rows.size());
for (int i = 0; i < rows.size(); i++)
{
cbs.add(mc.newCallback());
}
int i = 0;
for (AclRow row : rows)
{
// attempt deletion
adm.deleteAcl(row.getResourceType(),
row.getInstance(),
row.getId(),
cbs.get(i++));
}
}
/**
* Clone the selected ACL item.
*/
private void handleCloneACLItem()
{
int id = nextId(false, true);
AclRow row = getFirstSelectedRow();
Field fId;
InputDialog dlg = inputDialogProvider.get();
dlg.build("Clone ACL Item", "",
new Field[]
{
fId = new Field("Clone ID", id >= 0 ? id : Integer.class, false, new DecimalMinValidator(0)),
});
dlg.show(this.modalPanel, false, vals ->
{
if (vals != null)
{
int newId = (int) vals.get(fId);
adm.cloneAcl(
row.getResourceType(),
row.getInstance(),
row.getId(),
newId,
new AdminCallback<Boolean>(adm, alarm)
{
@Override
public void onDone(Boolean result)
{
dlg.dismiss();
populateACLList(() ->
{
AclRow nRow = locateAcl(row.getResourceType(), row.getInstance(), newId);
if (nRow != null)
{
gridHandle.select(nRow);
}
});
}
});
}
});
}
/**
* Move the ACL item up or down.
*
* @param up
* <code>true</code> if the item needs to be moved up.
*/
private void moveACLItem(boolean up)
{
AclRow row = getFirstSelectedRow();
// safety check
if (row == null)
{
return;
}
// compute a new ID candidate
int newId = nextId(up, false);
if (newId == -1)
{
alarm.ring("No room to move to");
return;
}
// move without confirmation
moveACL(row, newId);
}
/**
* Handle Move ACL Up button press - if not first, it will move the ACL
* one position up.
*/
private void handleMoveACLUp()
{
moveACLItem(true);
}
/**
* Handle Move ACL Down button press - if not last, it will move the ACL
* one position down.
*/
private void handleMoveACLDown()
{
moveACLItem(false);
}
/**
* Move an ACL to a directly specified position.
*
* @param row
* the ACL to move
* @param newId
* new position for the ACL
*/
private void moveACL(AclRow row, int newId)
{
// attempt movement
adm.moveAcl(row.getResourceType(),
row.getInstance(),
row.getId(),
newId,
new AdminCallback<Boolean>(adm, alarm)
{
@Override
public void onDone(Boolean result)
{
populateACLList(() ->
{
AclRow nRow = locateAcl(row.getResourceType(), row.getInstance(), newId);
if (nRow != null)
{
gridHandle.select(nRow);
}
});
}
});
}
/**
* Looks up the list of all ACLs and locates the one with given resource
* type, instance and ID.
*
* @param resourceType
* resource type to search within
* @param instanceName
* ACL instance name to search within
* @param id
* ACL id of interest
*
* @return the requested ACL or <code>null</code>
*/
private AclRow locateAcl(String resourceType, String instanceName, int id)
{
for (int i = 0; i < allAcls.size(); i++)
{
AclRow row = allAcls.get(i);
if (!instanceName.equals(row.getInstance()))
{
continue;
}
if (!resourceType.equals(row.getResourceType()))
{
continue;
}
if (id == row.getId())
{
return row;
}
}
return null;
}
/**
* Clone the selected ACL item.
*/
private void handleResequenceACLs()
{
int id = nextId(false, true);
AclRow row = getFirstSelectedRow();
String title = "Resequence " + row.getResourceType() + " ACLs in " +
row.getValueAt(0) + " instance";
Field fStart, fInc;
InputDialog dlg = inputDialogProvider.get();
dlg.build(title, "",
new Field[]
{
fStart = new Field("Starting Number", Integer.class, false, new DecimalMinValidator(0)),
fInc = new Field("Increment By", Integer.class, false, new DecimalMinValidator(1))
});
dlg.show(this.modalPanel, false, vals ->
{
if (vals != null)
{
int startId = (int) vals.get(fStart);
int step = (int) vals.get(fInc);
adm.resequenceAcls(row.getResourceType(), row.getInstance(), startId, step,
new AdminCallback<Boolean>(adm, alarm)
{
@Override
public void onDone(Boolean result)
{
dlg.dismiss();
refresh();
}
});
}
});
}
/**
* Shows right editor.
*
* @param resourceName
* Resource name.
* @param exact
* Exact match flag.
* @param type
* Resource type.
* @param rights
* The rights object.
* @param doneHandler
* Done handler.
*/
private void editRights(String resourceName,
boolean exact,
String type,
Rights rights,
Consumer<Rights> doneHandler)
{
adm.getAdminDef(new AdminCallback<AdminDef>(adm, alarm)
{
@Override
public void onDone(AdminDef result)
{
if (result == null)
{
doneHandler.accept(null);
return;
}
RightsEditorContext ctxt = RightsEditorContext.instance();
ctxt.setInputDialog(inputDialogProvider.get());
ctxt.setParent(rightsModalPanel);
ctxt.setModalDialogs(dialogs);
RightsEditor ed = ctxt.getEditor(type);
if (ed == null)
{
alarm.ring("Unknown editor of type " + type);
doneHandler.accept(null);
return;
}
Rights source = rights;
if (source == null)
{
source = ed.createDefaultRights();
}
ed.initialize(result.resourceDescription.get(type));
ed.edit(resourceName, exact, source,
rights ->
{
doneHandler.accept(rights);
},
() -> doneHandler.accept(null));
}
});
}
/**
* Checks whether the widget is displayed in a popup.
*
* @return <code>true</code> if in popup.
*/
private boolean isPopup()
{
Widget parent = getParent();
while(parent != null)
{
if (parent instanceof PopupPanel)
{
return true;
}
parent = parent.getParent();
}
return false;
}
}