AdminRightsEditor.java

/*
** Module   : AdminRightsEditor.java
** Abstract : Admin rights editor
**
** Copyright (c) 2017, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------Description-----------------------------------
** 001 HC  20170612 Initial version.
*/
/*
** 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.editors;

import com.goldencode.p2j.admin.client.widget.dialog.*;
import com.goldencode.p2j.admin.client.widget.dialog.InputDialog.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.security.BitSet;
import com.goldencode.p2j.security.Description;
import com.goldencode.p2j.security.AdminResourceNamespace.*;
import org.gwtbootstrap3.client.ui.*;

import java.util.*;
import java.util.function.*;

/**
 * Admin rights editor.
 */
public class AdminRightsEditor
implements RightsEditor
{
   /**
    * Returns the resource type name this editor is capable to edit.
    *
    * @return  see above
    */
   public static String getResourceType()
   {
      return "admin";
   }

   /**
    * Gives chance to editors to initialize their data structures.
    * <p>
    * This is a one time call for this kind of resource for the life time
    * of the admin session.
    *
    * @param   description
    *          rights structure description
    */
   @Override
   public void initialize(Description[] description)
   {
   }

   /**
    * Creates an instance of the <code>Rights</code>, which does not take
    * any input and is considered a default starting point in editing.
    *
    * @return  an instance of <code>Rights</code>
    */
   @Override
   public Rights createDefaultRights()
   {
      return new AdminRights();
   }

   /**
    * Edits the given instance of the <code>Rights</code>.
    * The implementation should prepare its modal dialog using the initial state
    * from the passed instance of the <code>Rights</code>, show the dialog,
    * provide event processing until the Save or Cancel action is applied, then
    * hide the dialog and return the appropriate result.
    *
    * @param   name
    *          name of the resource instance being edited
    * @param   exact
    *          nature of the name
    * @param   rights
    *          instance of <code>Rights</code> to be edited
    * @param   submitHandler
    *          Submit handler.
    * @param   cancelHandler
    *          Cancel handler.
    */
   @Override
   public void edit(String name,
                    boolean exact,
                    Rights rights,
                    Consumer<Rights> submitHandler,
                    Runnable cancelHandler)
   {
      // determine the nature of the rights and replace the passed instance
      // with the proper one if the default doesn't fit
      Node[] path = AdminResourceNamespace.findPath(name);
      if (path == null)
      {
         RightsEditorContext ctxt = RightsEditorContext.instance();
         ctxt.getModalDialogs().showWarning("Unknown admin resource '" + name + "'",
                                            () -> submitHandler.accept(rights));
         return;
      }

      int reqType = path[path.length - 1].getType();
      int type = ((AdminRights) rights).getType();

      AdminRights ar = reqType == type ? (AdminRights) rights :
                                         new AdminRights(reqType);
      type = reqType;

      // prefill the edit panel with the current rights
      BitSet perms = ar.getPermissions();

      switch (type)
      {
         case AdminRights.ADMT_PATH:
            editPath(perms, submitHandler, cancelHandler);
            break;
         case AdminRights.ADMT_LEAF_REFRESH:
            editRefresh(perms, submitHandler, cancelHandler);
            break;
         case AdminRights.ADMT_LEAF_USER:
            editUser(perms, submitHandler, cancelHandler);
            break;
      }
   }

   /**
    * Edit path rights.
    *
    * @param   perms
    *          Permissions bit set.
    * @param   submitHandler
    *          Submit handler.
    * @param   cancelHandler
    *          Cancel handler.
    */
   private void editPath(BitSet perms, Consumer<Rights> submitHandler, Runnable cancelHandler)
   {
      boolean unlimited = perms.isSet(AdminRights.ADTP_UNLIMITED_ACCESS);
      boolean denied = perms.isSet(AdminRights.ADTP_DENIED_ACCESS);
      if (denied)
      {
         unlimited = false;
      }

      RightsEditorContext ctxt = RightsEditorContext.instance();
      InputDialog dlg = ctxt.getInputDialog();

      // 1st row - Unlimited permission
      Field[] fields = new Field[2];
      Field fUnlimited = new Field("U (unlimited)", unlimited)
                           .withTooltip("allows unlimited access to this level and below");
      fields[0] = fUnlimited;

      // 2nd row - Denied permission
      Field fDenied = new Field("N (denied)", denied)
                        .withTooltip("denies any and all accesses to this level and below");
      fields[1] = fDenied;

      dlg.build("Path Admin Rights", fields);

      CheckBox chkUnlimited = (CheckBox) dlg.getWidget(fUnlimited);
      CheckBox chkDenied = (CheckBox) dlg.getWidget(fDenied);

      chkUnlimited.setEnabled(!denied);
      chkUnlimited.addValueChangeHandler(event -> chkDenied.setEnabled(!event.getValue()));

      chkDenied.setEnabled(!unlimited);
      chkDenied.addValueChangeHandler(event -> chkUnlimited.setEnabled(!event.getValue()));

      dlg.show(ctxt.getParent(), vals ->
      {
         if (vals != null)
         {
            boolean deniedVal = (Boolean) vals.get(fDenied);
            perms.set(AdminRights.ADTP_DENIED_ACCESS, deniedVal);
            perms.set(AdminRights.ADTP_UNLIMITED_ACCESS,
                      (Boolean) vals.get(fUnlimited) && !deniedVal);
            submitHandler.accept(new AdminRights(AdminRights.ADMT_PATH, perms));
         }
         else
         {
            cancelHandler.run();
         }
      });
   }

   /**
    * Edit refresh rights.
    *
    * @param   perms
    *          Permissions bit set.
    * @param   submitHandler
    *          Submit handler.
    * @param   cancelHandler
    *          Cancel handler.
    */
   private void editRefresh(BitSet perms, Consumer<Rights> submitHandler, Runnable cancelHandler)
   {
      boolean refresh = perms.isSet(AdminRights.ADLR_REFRESH_ACCESS);
      boolean denied = perms.isSet(AdminRights.ADTP_DENIED_ACCESS);
      if (denied)
      {
         refresh = false;
      }

      RightsEditorContext ctxt = RightsEditorContext.instance();
      InputDialog dlg = ctxt.getInputDialog();

      // 1st row - Unlimited permission
      Field[] fields = new Field[2];
      Field fRefresh = new Field("R (refresh)", refresh)
                         .withTooltip("allows access to refresh function");
      fields[0] = fRefresh;

      // 2nd row - Denied permission
      Field fDenied = new Field("N (denied)", denied)
                        .withTooltip("denies accesses to refresh function");
      fields[1] = fDenied;

      dlg.build("Refresh Admin Rights", fields);

      CheckBox chkRefresh = (CheckBox) dlg.getWidget(fRefresh);
      CheckBox chkDenied = (CheckBox) dlg.getWidget(fDenied);

      chkRefresh.setEnabled(!denied);
      chkRefresh.addValueChangeHandler(event -> chkDenied.setEnabled(!event.getValue()));

      chkDenied.setEnabled(!refresh);
      chkDenied.addValueChangeHandler(event -> chkRefresh.setEnabled(!event.getValue()));

      dlg.show(ctxt.getParent(), vals ->
      {
         if (vals != null)
         {
            boolean deniedVal = (Boolean) vals.get(fDenied);
            perms.set(AdminRights.ADTP_DENIED_ACCESS, deniedVal);
            perms.set(AdminRights.ADLR_REFRESH_ACCESS,
                      (Boolean) vals.get(fRefresh) && !deniedVal);
            submitHandler.accept(new AdminRights(AdminRights.ADMT_LEAF_REFRESH, perms));
         }
         else
         {
            cancelHandler.run();
         }
      });
   }

   /**
    * Edit user rights.
    *
    * @param   perms
    *          Permissions bit set.
    * @param   submitHandler
    *          Submit handler.
    * @param   cancelHandler
    *          Cancel handler.
    */
   private void editUser(BitSet perms, Consumer<Rights> submitHandler, Runnable cancelHandler)
   {
      List<Field> userFields = new ArrayList<>();

      // Enumerate permission
      Field fEnumerate =
         new Field("E (enumerate)", perms.isSet(AdminRights.ADLU_ENUMERATE_ACCESS))
         .withTooltip("allows enumeration of user accounts");
      userFields.add(fEnumerate);

      // Password permission
      Field fPassword =
         new Field("P (password)", perms.isSet(AdminRights.ADLU_PASSWORD_ACCESS))
         .withTooltip("allows changing passwords");
      userFields.add(fPassword);

      // Group permission
      Field fGroup = new Field("G (group)", perms.isSet(AdminRights.ADLU_GROUP_ACCESS))
                     .withTooltip("allows assigning groups");
      userFields.add(fGroup);

      // Read access
      Field fRead = new Field("R (read)", perms.isSet(AdminRights.ADLU_READ_ACCESS))
                    .withTooltip("allows reading account details");
      userFields.add(fRead);

      // Write access
      Field fWrite = new Field("W (write)", perms.isSet(AdminRights.ADLU_WRITE_ACCESS))
                     .withTooltip("allows writing account details");
      userFields.add(fWrite);

      // Create access
      Field fCreate = new Field("C (create)", perms.isSet(AdminRights.ADLU_CREATE_ACCESS))
                      .withTooltip("allows creating new users");
      userFields.add(fCreate);

      // Delete access
      Field fDelete = new Field("D (delete)", perms.isSet(AdminRights.ADLU_DELETE_ACCESS))
                      .withTooltip("allows deletion of users");
      userFields.add(fDelete);

      // Denied access
      boolean denied = perms.isSet(AdminRights.ADLU_DENIED_ACCESS);
      Field fDenied = new Field("N (denied)", denied)
                      .withTooltip("denies any and all accesses to user accounts");

      RightsEditorContext ctxt = RightsEditorContext.instance();
      InputDialog dlg = ctxt.getInputDialog();


      Item[] fields = new Item[]
      {
         new Span(fEnumerate, fPassword, fGroup, fRead),
         new Span(fWrite, fCreate, fDelete, fDenied)
      };

      dlg.build("User Account Admin Rights", fields);

      CheckBox chkDenied = (CheckBox) dlg.getWidget(fDenied);

      Consumer<Boolean> updateFields = (d) ->
      {
         for (Field f : userFields)
         {
            CheckBox chk = (CheckBox) dlg.getWidget(f);
            chk.setEnabled(!d);
         }
      };

      updateFields.accept(denied);
      chkDenied.addValueChangeHandler(event -> updateFields.accept(event.getValue()));

      dlg.show(ctxt.getParent(), vals ->
      {
         if (vals != null)
         {
            boolean d = (Boolean) vals.get(fDenied);
            perms.set(AdminRights.ADLU_ENUMERATE_ACCESS, (Boolean) vals.get(fEnumerate) && !d);
            perms.set(AdminRights.ADLU_PASSWORD_ACCESS, (Boolean) vals.get(fPassword) && !d);
            perms.set(AdminRights.ADLU_GROUP_ACCESS, (Boolean) vals.get(fGroup) && !d);
            perms.set(AdminRights.ADLU_READ_ACCESS, (Boolean) vals.get(fRead) && !d);
            perms.set(AdminRights.ADLU_WRITE_ACCESS, (Boolean) vals.get(fWrite) && !d);
            perms.set(AdminRights.ADLU_CREATE_ACCESS, (Boolean) vals.get(fCreate) && !d);
            perms.set(AdminRights.ADLU_DELETE_ACCESS, (Boolean) vals.get(fDelete) && !d);
            perms.set(AdminRights.ADTP_DENIED_ACCESS, d);

            submitHandler.accept(new AdminRights(AdminRights.ADMT_LEAF_USER, perms));
         }
         else
         {
            cancelHandler.run();
         }
      });
   }
}