NetResource.java
/*
** Module : NetResource.java
** Abstract : security mgr plugin for the "net" abstract resource.
**
** Copyright (c) 2005-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------Description----------------------
** 001 NVS 20050413 @20695 Created the initial version. This class is
** responsible for the "net" abstract resource
** which offers controllable access to the
** exported server's entry points
** 002 NVS 20050414 @20710 toString now encloses permissions in {}.
** Added rights search traceing for debugging.
** 003 NVS 20050415 @20738 Added class description to javadoc.
** 004 NVS 20050421 @20825 Fixed an object cast bug in isRightsSetValid.
** 005 NVS 20050524 @21267 Some methods and members are moved to base
** abstract class.
** 006 NVS 20060426 @25727 Fixed bug in nested rights search.
** 007 ECF 20080214 @37068 Minor optimization. Replaced string
** concatenation with StringBuilder.
** 008 GES 20081025 @40317 Minor optimization. Eliminate string
** concatenation for logging unless the logging
** level is high enough to force logging to
** occur.
** 009 NVS 20090630 @42999 Put the NetRights class into a separate file
** due to a new requirement for all rights classes
** to be separate and serializable.
** 010 NVS 20090707 @43080 Added getRightsEditorName() method.
** 011 HC 20170612 Changes related to implementation of new GWT-based Admin client.
** 012 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.net;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.directory.*;
import com.goldencode.p2j.security.BitSet;
import com.goldencode.p2j.util.logging.*;
import java.util.logging.*;
/**
* Implements the "net" abstract resource. Instances of this resource
* control access to exported server's entry points. They form a two level
* hierarchy of names, with the top level name being a group name and the
* bottom level name being a member of the group. The syntax for this
* composite name is group:api.
* <p>
* There are three access modes:
* <ul>
* <li>read - getting routing key from group:api;
* <li>write - add a group:api method to the registry (export);
* <li>execute - call a method
* </ul>
* <p>
* Rights objects for this resource are made of one bitfield with 4 bits.
* First three bits correspond to the listed access modes above. The last bit
* is for explicit denial of access. When printed, they are shown as RWXN.
* <p>
* There is a predefined "system" group of methods, containing the following
* method names:
* <ul>
* <li>route<br>
* this method is used by peers to resolve exported symbolic API names
* into registry indices
* <li>authenticate<br>
* this method is used internally by the net package to do remote
* subject authentication in a routed configuration
* <li>terminate<br>
* this method is used internally by the net package to do remote
* session termination in a routed configuration
* <li>shutdown<br>
* this method is used to request the net package shutdown, which
* normally also initiates the server shutdown
* </ul>
* <p>
* These names can be used in ACLs. The server's account should provide ACLs
* that allow the server's process have RWX rights for the "system" group.
* On the other hand, application related accounts never need any access to
* system:authenticate and system:terminate.
* They need RX access to system:route to be able to perform remote calls.
* system:shutdown may be given RX or N access.
* <p>
* Non-system API access control is completely up to the admin.
*/
public class NetResource
extends AbstractResource
{
/** READ access mode for net resource */
static final int NET_READ_ACCESS = 1;
/** WRITE access mode for net resource */
static final int NET_WRITE_ACCESS = 2;
/** EXECUTE access mode for net resource */
static final int NET_EXECUTE_ACCESS = 4;
/** Logger. */
private static final CentralLogger LOG = CentralLogger.get(NetResource.class);
/**
* Constructor.
*/
public NetResource()
{
}
/**
* Returns the plugin resource type name as a string.
*
* @return plugin resource type name
*/
public String getTypeName()
{
return "net";
}
/**
* Returns an array of descriptions, one object per the plugin's access
* rights item.
* <p>
* This plugin uses one field of type BitField with three bits: read, write
* end execute.
* @return array of <code>Description</code>s, one per each plugin's access
* rights item.
*/
public Description[] describeRights()
{
Description[] items = new Description[1];
items[0] = new Description(AttributeType.ATTR_BITFIELD, // string
false, // mandatory
false, // fix.size
4, // size
"permissions", // text label
"bit set to ON enables access",
new String[] // bit names
{
"read",
"write",
"execute",
"denied",
},
new BitSet(4) // no unused
);
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 NetRights(BitSetHelper.fromBitField((BitField)rights[0]));
}
/**
* Checks whether a given string is a valid resource name
* for this resource type.
* <p>Valid names contain no slashes and 0 or 1 colon.
*
* @param resource
* string naming a resource
*
* @return <code>true</code> if the name is syntactically correct
*/
public boolean isInstanceNameValid(String resource)
{
if (resource.indexOf('/') != -1)
return false;
if (resource.indexOf(':') != resource.lastIndexOf(':'))
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>DENIED bit is not set, or
* <li>DENIED bit is set and no other bit 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 != 1)
return false;
if (!rights[0].getClass().isInstance(new BitField(1)))
return false;
BitSet bs = BitSetHelper.fromBitField((BitField)rights[0]);
NetRights newRights = new NetRights(bs);
boolean denied = newRights.get(NetRights.BIT_DENIED_ACCESS);
if (!denied)
return true;
boolean set = newRights.get(NetRights.BIT_READ_ACCESS) |
newRights.get(NetRights.BIT_WRITE_ACCESS) |
newRights.get(NetRights.BIT_EXECUTE_ACCESS);
if (set)
return false;
return true;
}
/**
* Checks READ access rights of the current subject with regards to the
* given instance of the "net" abstract resource.
*
* @param group
* group name
*
* @param api
* method name within the group
*
* @return <code>true</code> if access is allowed.
*/
boolean checkReadAccess(String group, String api)
{
return netAccessWorker(group, api, NET_READ_ACCESS);
}
/**
* Checks WRITE access rights of the current subject with regards to the
* given instance of the "net" abstract resource.
*
* @param group
* group name
*
* @param api
* method name within the group
*
* @return <code>true</code> if access is allowed.
*/
boolean checkWriteAccess(String group, String api)
{
return netAccessWorker(group, api, NET_WRITE_ACCESS);
}
/**
* Checks EXECUTE access rights of the current subject with regards to the
* given instance of the "net" abstract resource.
*
* @param group
* group name
*
* @param api
* method name within the group
*
* @return <code>true</code> if access is allowed.
*/
boolean checkExecuteAccess(String group, String api)
{
return netAccessWorker(group, api, NET_EXECUTE_ACCESS);
}
/**
* Implements generalized access rights check worker.
* As this resource is a two level hierarchy of names, the full name is
* checked first. In case there was no rights objects found for the full
* name, the group name is checked.
*
* @param group
* group name
*
* @param api
* method name within the group
*
* @param mode
* an integer value encoding the access mode specific to this
* resource
*
* @return <code>true</code> if access is allowed.
*/
private boolean netAccessWorker(String group, String api, int mode)
{
boolean isFinerLoggable = LOG.isLoggable(Level.FINER);
Boolean cached = null;
boolean decision = false;
int check = 0;
StringBuilder buf = new StringBuilder(group);
buf.append(":");
buf.append(api);
String instanceName = buf.toString();
// 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);
if (isFinerLoggable)
{
LOG.finer("Search open: resId " + resourceIndex + ", instance " +
instanceName + ", mode " + mode + ", handle " + handle);
}
// rights check loop
NetRights rights = (NetRights) sm.getNextRights(handle);
if (rights == null)
{
// no rights defined for the fully qualified name; try group only
if (isFinerLoggable)
{
LOG.finer("Search done: handle " + handle + ", decision " +
decision + ", cache false");
}
sm.closeRightsSearch(handle, decision, false);
handle = sm.openRightsSearch(resourceIndex, group, mode);
if (isFinerLoggable)
{
LOG.finer("Search open: resId " + resourceIndex + ", instance " +
group + ", mode " + mode + ", handle " + handle);
}
rights = (NetRights) sm.getNextRights(handle);
}
loop:
while (rights != null)
{
if (isFinerLoggable)
{
LOG.finer("Search next: handle " + handle + ", rights " + rights);
}
check = checkSingle(rights, mode);
switch (check)
{
case -1:
break loop;
case 0:
break;
case 1:
decision = true;
break loop;
}
rights = (NetRights) sm.getNextRights(handle);
}
// close the ACL search
if (isFinerLoggable)
{
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 NetRights object.
*
* @param rights
* instance of NetRights to check
*
* @param mode
* an integer value encoding the access mode specific to this
* resource
*
* @return one of the following values:
* <ul>
* <li>-1 if access is explicitly denied
* <li>0 if permissions bit for this access mode was OFF
* <li>1 if permissions bit for this access mode was ON
* </ul>
*/
private int checkSingle(NetRights rights, int mode)
{
if (rights.get(NetRights.BIT_DENIED_ACCESS))
return -1;
int bit = 0;
switch (mode)
{
case NET_READ_ACCESS:
bit = NetRights.BIT_READ_ACCESS;
break;
case NET_WRITE_ACCESS:
bit = NetRights.BIT_WRITE_ACCESS;
break;
case NET_EXECUTE_ACCESS:
bit = NetRights.BIT_EXECUTE_ACCESS;
break;
default:
return -1;
}
if (rights.get(bit))
return 1;
return 0;
}
}