DirectoryResource.java
/*
** Module : DirectoryResource.java
** Abstract : Implements "directory" abstract resource.
**
** Copyright (c) 2004-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------Description----------------------
** 001 NVS 20050420 @20800 Created the initial version. This class is
** responsible for the "directory" abstract
** resource which offers controllable access to
** the P2J directory objects.
** 002 NVS 20050524 @21266 Some methods and members are moved to base
** abstract class.
** 003 NVS 20090701 @42998 Put the DirectoryRights class into a separate file
** due to a new requirement for all rights classes
* to be separate and serializable.
** 004 NVS 20090707 @43081 Added getRightsEditorName() method.
** 005 EVL 20160222 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 006 HC 20170612 Changes related to implementation of new GWT-based Admin client.
** 007 SVL 20180530 Made access mode constants public.
** 008 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
*/
/*
** 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.directory;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.util.logging.*;
/**
* Implements the "directory" abstract resource. Instances of this resource
* control access to the P2J directory objects.
* <p>The following is the defined access modes:
* <ul>
* <li>enumerate - lists nodes and attributes;
* <li>read - reads nodes and attribute values;
* <li>write - changes nodes and attribute values;
* <li>add - adds new attributes or values to attributes;
* <li>create - creates new nodes and attributes;
* <li>delete - deletes nodes and attributes
* </ul>
* <p>Rights objects for this resource are made of one mandatory bitfield
* having 8 bits of permissions and one optional boolean expression.
* <p>
* The permissions bits are defined as follows:
* <ul>
* <li>enumerate
* <li>read
* <li>write
* <li>add
* <li>create
* <li>delete
* <li>denied
* <li>logic
* </ul>
* Bits from "enumerate" through "delete" control the corresponding access
* modes. Bit "denied" explicitly denies any access, regardless of the
* boolean expression and the "logic" bit (veto).
* <p>
* Bit "logic" specifies the operation of the optional boolean expression and
* is only taken into consideration when the expression is present:
* <ul>
* <li>if set to 1, the <b>AND</b> operation is performed between the result
* of the permissions check and the boolean expression; the effect of
* the expression in this case is restrictive;
* <li>if set to 0, the <b>OR</b> operation is performed between the result
* of the permissions check and the boolean expression; the effect of
* the expression in this case is permissive;
* </ul>
* <p>
* The directory package uses package private methods defined in this class
* for access rights checks. The interpretation of various access modes and
* the meaning of particular operations against the directory nodes and
* attributes is beyond control of this resource plugin.
*/
public class DirectoryResource
extends AbstractResource
{
/** ENUMERATE access mode for directory resource */
public static final int DIR_ENUMERATE_ACCESS = 1;
/** READ access mode for directory resource */
public static final int DIR_READ_ACCESS = 2;
/** WRITE access mode for directory resource */
public static final int DIR_WRITE_ACCESS = 4;
/** ADD access mode for directory resource */
public static final int DIR_ADD_ACCESS = 8;
/** CREATE access mode for directory resource */
public static final int DIR_CREATE_ACCESS = 16;
/** DELETE access mode for directory resource */
public static final int DIR_DELETE_ACCESS = 32;
/** Logger. */
private static final CentralLogger LOG = CentralLogger.get(DirectoryResource.class);
/**
* Constructor.
*/
public DirectoryResource()
{
}
/**
* Returns the plugin resource type name as a string.
*
* @return plugin resource type name
*/
public String getTypeName()
{
return "directory";
}
/**
* Returns an array of descriptions, one object per the plugin's access
* rights item.
*
* @return array of <code>Description</code>s, one per each plugin's access
* rights item.
*/
public Description[] describeRights()
{
Description[] items = new Description[2];
items[0] = new Description(AttributeType.ATTR_BITFIELD, // bitfield
false, // mandatory
false, // fix.size
8, // size
"permissions", // text label
"bit set to ON enables access",
new String[] // bit names
{
"enumerate",
"read",
"write",
"add",
"create",
"delete",
"denied",
"logic",
},
new BitSet(8) // no unused
);
items[1] = new Description(AttributeType.ATTR_STRING, // string
true, // optional
true, // var.size
0, // size
"condition", // text label
"extra condition"
);
return items;
}
/**
* Instantiates a plugin's class that implements the Rights interface,
* using the array of objects representing a set of access rights fields.
*
* @param rights
* an array of objects of proper types representing items in a set
* of access rights
*
* @return the reference to the interface.
*/
public Rights getRightsInstance(Object[] rights)
{
return new DirectoryRights(BitSetHelper.fromBitField((BitField)rights[0]),
(String)rights[1]);
}
/**
* Checks whether a given string is a valid resource name
* for this resource type.
* <p>Valid names are:
* <ul>
* <li>empty string for the root object;
* <li>multiple links in the path, separated with '/', where every link
* is made of alpha-numeric characters, dashes and underscores and is
* no longer than 256 characters.
* </ul>
* <p>
* All these checks are performed by IdUtils.normalize method.
*
* @param resource
* string naming a resource
*
* @return <code>true</code> if the name is syntactically correct
*/
public boolean isInstanceNameValid(String resource)
{
if (IdUtils.normalize(resource) == null)
return false;
return true;
}
/**
* Checks whether a given array of objects representing a set of access
* rights fields is acceptable.
* <p>This method returns <code>true</code> only if:
* <ul>
* <li>fields are of proper number and type;
* <li>DENIED bit is not set, or
* <li>DENIED bit is set and no other bit from ENUMERATE through DELETE
* is set.
* </ul>
*
* @param rights
* an array of objects of proper types representing items in a set
* of access rights
*
* @return <code>true</code> if the array is acceptable for an instance
* of access rights
*/
public boolean isRightsSetValid(Object[] rights)
{
if (rights.length != 2)
return false;
if (rights[0] == null)
return false;
if (!rights[0].getClass().isInstance(new BitField(1)))
return false;
if (rights[1] != null)
{
if (!rights[1].getClass().isInstance(""))
return false;
}
BitSet bs = BitSetHelper.fromBitField((BitField)rights[0]);
DirectoryRights newRights = new DirectoryRights(bs, (String)rights[1]);
boolean denied = newRights.getPermission(DirectoryRights.
BIT_DENIED_ACCESS);
if (!denied)
return true;
boolean set = newRights.getPermission(DirectoryRights.
BIT_ENUMERATE_ACCESS) |
newRights.getPermission(DirectoryRights.
BIT_READ_ACCESS) |
newRights.getPermission(DirectoryRights.
BIT_WRITE_ACCESS) |
newRights.getPermission(DirectoryRights.
BIT_ADD_ACCESS) |
newRights.getPermission(DirectoryRights.
BIT_CREATE_ACCESS) |
newRights.getPermission(DirectoryRights.
BIT_DELETE_ACCESS);
if (set)
return false;
return true;
}
/**
* Returns the plugin's exported variables and functions.
* <p>
* This implementation exports a library.
*
* @return plugin's instance of a library class for symbol resolution
*/
public Object getLibrary()
{
return new DirectoryPool();
}
/**
* Checks the given node for the specified access.
*
* @param oId
* directory object ID - resource instance name
*
* @param mode
* requested access mode
*
* @return <code>true</code> if access is allowed.
*/
boolean checkAccess(String oId, int mode)
{
return dirAccessWorker(oId, mode);
}
/**
* Implements generalized access rights check worker.
* As this resource is a hierarchy of names, the full name is
* checked first. In case there was no rights objects found for the full
* name, the partial names are checked.
*
* @param oId
* directory object ID - resource instance name
*
* @param mode
* requested access mode
*
* @return <code>true</code> if access is allowed.
*/
private boolean dirAccessWorker(String oId, int mode)
{
Boolean cached = null;
boolean decision = false;
int check = 0;
String instanceName = oId;
// check to see if there is a cached decision
cached = sm.getCachedDecision(resourceIndex, instanceName, mode);
if (cached != null)
{
return cached.booleanValue();
}
// initiate the ACL search
int handle = sm.openRightsSearch(resourceIndex, instanceName, mode);
LOG.finer("Search open: resId " + resourceIndex + ", instance " +
instanceName + ", mode " + mode + ", handle " + handle);
// rights check loop
DirectoryRights rights = (DirectoryRights)sm.getNextRights(handle);
if (rights == null)
{
// no rights defined for this name; climb the hierarchy up
if (instanceName.length() == 0) // reached the root
return false;
int last = instanceName.lastIndexOf('/');
if (last == -1) // safety net; never happens
return false;
instanceName = instanceName.substring(0, last);
decision = dirAccessWorker(instanceName, mode);
}
else
{
loop:
while (rights != null)
{
LOG.finer("Search next: handle " + handle + ", rights " + rights);
check = checkSingle(instanceName, mode, rights);
switch (check)
{
case -1:
break loop;
case 0:
break;
case 1:
decision = true;
break loop;
}
rights = (DirectoryRights)sm.getNextRights(handle);
}
}
// close the ACL search
LOG.finer("Search done: handle " + handle + ", decision " + decision +
", cache true");
sm.closeRightsSearch(handle, decision, true);
return decision;
}
/**
* Implements a worker that performs access rights check on a single
* instance of the DirectoryRights object.
*
* @param oId
* directory object ID - resource instance name
*
* @param mode
* requested access mode
*
* @param rights
* instance of DirectoryRights to check
*
* @return one of the following values:
* <ul>
* <li>-1 if access is explicitly denied or access mode is
* invalid
* <li>0 if decision for this access mode is FALSE
* <li>1 if decision for this access mode is TRUE
* </ul>
*/
private int checkSingle(String oId, int mode, DirectoryRights rights)
{
// look at the DENIED permission first
if (rights.getPermission(DirectoryRights.BIT_DENIED_ACCESS))
return -1;
// second, query the appropriate permission bit
int bit = 0;
switch (mode)
{
case DIR_ENUMERATE_ACCESS:
bit = DirectoryRights.BIT_ENUMERATE_ACCESS;
break;
case DIR_READ_ACCESS:
bit = DirectoryRights.BIT_READ_ACCESS;
break;
case DIR_WRITE_ACCESS:
bit = DirectoryRights.BIT_WRITE_ACCESS;
break;
case DIR_ADD_ACCESS:
bit = DirectoryRights.BIT_ADD_ACCESS;
break;
case DIR_CREATE_ACCESS:
bit = DirectoryRights.BIT_CREATE_ACCESS;
break;
case DIR_DELETE_ACCESS:
bit = DirectoryRights.BIT_DELETE_ACCESS;
break;
default:
return -1;
}
boolean decision = rights.getPermission(bit);
boolean logic = rights.getPermission(DirectoryRights.BIT_LOGIC);
// third, figure out if the decision can be made now:
// - no condition ==> return decision (0 or 1);
// - logic is OR and decision is TRUE ==> return 1;
// - logic is AND and decision is FALSE ==> return 0
String condition = rights.getCondition();
if (condition == null)
{
if (decision)
return 1;
else
return 0;
}
if (logic == false && decision == true)
return 1;
if (logic == true && decision == false)
return 0;
// fourth, evaluate the condition and make the final decision
SharedData share = new SharedData(oId, mode);
Boolean result = null;
Object o = sm.evaluate(resourceIndex, share, condition);
if (o == null || !Boolean.class.isInstance(o))
result = null;
else
result = (Boolean)o;
boolean cond = result == null ? false : result.booleanValue();
if (logic == false)
decision = decision | cond;
else
decision = decision & cond;
if (decision)
return 1;
else
return 0;
}
/**
* Implements the directory variable pool.
* <p>
* Variables defined in this private pool are:
* <ul>
* <li>node - directory node name currently being accessed;
* <li>mode - access mode for the current operation.
* </ul>
* <p>
* Methods defined in this private pool are:
* <ul>
* <li>boolean under(String path, String branch)
* <br>checks to see whether the directory node 'path' is under the
* specified tree branch.
* </ul>
*/
public class DirectoryPool
{
/**
* Constructor.
*/
public DirectoryPool()
{
}
/**
* Gets the directory node under consideration.
*
* @return directory node name
*/
public String getNode()
{
return ((SharedData)getLink()).getNode();
}
/**
* Gets the access mode of the current operation.
*
* @return the string representation of the access mode as follows:
* <ul>
* <li>"E" for "enumerate";
* <li>"R" for "read";
* <li>"W" for "write";
* <li>"A" for "add";
* <li>"C" for "create";
* <li>"D" for "delete";
* </ul>
*/
public String getMode()
{
int mode = ((SharedData)getLink()).getMode();
switch (mode)
{
case DIR_ENUMERATE_ACCESS:
return "E";
case DIR_READ_ACCESS:
return "R";
case DIR_WRITE_ACCESS:
return "W";
case DIR_ADD_ACCESS:
return "A";
case DIR_CREATE_ACCESS:
return "C";
case DIR_DELETE_ACCESS:
return "D";
}
return "U"; // never happens as mode is verified by now
}
/**
* Relates the current directory node to a directory branch.
* <p>
* Since the implementation simply compares the strings using the
* String.startsWith() method, the branch parameter should be made of
* one or more complete links to produce a meaningful result.
*
* @param branch
* directory tree branch to compare with
*
* @return <code>true</code> if the current path is under the branch,
* <code>false</code> otherwise.<br>
* <code>false</code> is returned if path is the directory root
* "".<br>
* <code>true</code> is returned if branch is the directory root
* "" (unless the path is the root, too).
*/
public boolean isUnder(String branch)
{
return ((SharedData)getLink()).getNode().startsWith(branch);
}
}
/**
* Implements the directory variable pool shared data.
* These are pieces of information taken from the access check being
* performed. As these are maintained on per thread basis, the convenient
* way to pass them between the access check method and the variable pool
* methods is the link provided by AbstractResource class.
* <p>
* Variables defined in this private pool are:
* <ul>
* <li>node - directory node name currently being accessed;
* <li>mode - access mode for the current operation.
* </ul>
*/
private class SharedData
{
/** node name */
private String node = null;
/** access mode */
private int mode = 0;
/**
* Constructor.
*
* @param node
* node being checked
*
* @param mode
* requested access mode
*/
SharedData(String node, int mode)
{
this.node = node;
this.mode = mode;
}
/**
* Gets the directory node name.
*
* @return node name saved in this shared data
*/
String getNode()
{
return node;
}
/**
* Gets the access mode.
*
* @return access mode saved in this shared data
*/
int getMode()
{
return mode;
}
}
}