/*
* Copyright 1999-2008 University of Chicago
*
* 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.globus.workspace.groupauthz;
import org.globus.workspace.service.binding.vm.VirtualMachine;
import org.globus.workspace.service.binding.vm.VirtualMachineDeployment;
import org.globus.workspace.service.binding.vm.VirtualMachinePartition;
import org.globus.workspace.service.binding.authorization.Decision;
import org.nimbustools.api.services.rm.AuthorizationException;
import org.nimbustools.api.services.rm.ResourceRequestDeniedException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.security.NoSuchAlgorithmException;
import java.net.URI;
import java.net.URISyntaxException;
public class DecisionLogic {
private static final Log logger = LogFactory.getLog(Group.class.getName());
public DecisionLogic()
{
}
/**
*
* @param dn may not be null
* @param rights may not be null
* @param bindings may not be null, can be zerolen array
* @param elapsedMins may not be null or negative
* @param reservedMins may not be null or negative
* @param numWorkspaces number of OTHER, current workspaces
* @param chargeRatio ratio to compute the real minutes charge, typically <= 1.0 and > 0
* @return Decision Integer
* @throws AuthorizationException processing problem
* @throws ResourceRequestDeniedException error w/ explanation to client
*/
public Integer decide(String dn,
GroupRights rights,
VirtualMachine[] bindings,
Long elapsedMins,
Long reservedMins,
int numWorkspaces,
double chargeRatio)
throws AuthorizationException,
ResourceRequestDeniedException {
if (!HashUtil.isInitialized()) {
throw new AuthorizationException("Cannot give an authorization " +
"decision without a properly initialized hashing system");
}
if (dn == null) {
throw new IllegalArgumentException("dn is null");
}
if (rights == null) {
throw new IllegalArgumentException("rights is null");
}
if (bindings == null) {
throw new IllegalArgumentException("bindings is null");
}
if (elapsedMins == null) {
throw new IllegalArgumentException("elapsedMins is null");
} else if (elapsedMins.longValue() < 0) {
throw new IllegalArgumentException("elapsedMins is negative");
}
if (reservedMins == null) {
throw new IllegalArgumentException("reservedMins is null");
} else if (reservedMins.longValue() < 0) {
throw new IllegalArgumentException("reservedMins is negative");
}
if (chargeRatio <= 0) {
throw new IllegalArgumentException("expecting charge ratio to be positive");
}
final StringBuffer buf = new StringBuffer("\n\nConsidering caller: '");
buf.append(dn)
.append("'.\nCurrent elapsed minutes: ")
.append(elapsedMins)
.append(".\nCurrent reserved minutes: ")
.append(reservedMins)
.append(".\nNumber of VMs in request: ")
.append(bindings.length)
.append(".\nCharge ratio for request: ")
.append(chargeRatio)
.append(".\nNumber of VMs caller is already currently running: ")
.append(numWorkspaces)
.append(".\nRights:\n")
.append(rights)
.append("\n\n");
final int maxCurrentPolicy = (int) rights.getMaxWorkspaceNumber();
if (maxCurrentPolicy > 0) {
if (numWorkspaces + bindings.length > maxCurrentPolicy) {
final StringBuffer newbuf =
new StringBuffer("\nDenied: Request for ");
newbuf.append(bindings.length)
.append(" workspaces");
if (numWorkspaces != 0) {
newbuf.append(", together with number of currently " +
"running workspaces (")
.append(numWorkspaces)
.append("),");
}
newbuf.append(" exceeds the maximum, which is ")
.append(maxCurrentPolicy)
.append(" concurrently running workspaces.");
final String msg = newbuf.toString();
buf.append(msg);
logger.warn(buf.toString());
throw new ResourceRequestDeniedException(msg);
}
}
long requestDur = 0;
for (int i = 0; i < bindings.length; i++) {
final VirtualMachineDeployment dep = bindings[i].getDeployment();
if (dep == null) {
final String msg = "ERROR: No deployment information in " +
"binding, can't make decision.";
buf.append(msg);
logger.error(buf.toString());
throw new AuthorizationException(msg);
}
final long seconds = dep.getMinDuration();
requestDur += seconds / 60;
}
final Double doubleRequestDur = requestDur * chargeRatio;
requestDur = doubleRequestDur.longValue();
if (bindings.length > 1) {
buf.append("Duration total of all requests in group: ");
} else {
buf.append("Duration request: ");
}
buf.append(requestDur)
.append("\n");
// zero or below means no check should be made
if (rights.getMaxCPUs() > 0) {
final long maxCPUs = rights.getMaxCPUs();
for (int i = 0; i < bindings.length; i++) {
final VirtualMachineDeployment dep = bindings[i].getDeployment();
if (dep == null) {
final String msg = "ERROR: No deployment information in " +
"binding, can't make decision.";
buf.append(msg);
logger.error(buf.toString());
throw new AuthorizationException(msg);
}
final long currentCPUs = dep.getIndividualCPUCount();
if (currentCPUs > maxCPUs) {
buf.append("\nDenied: Requested CPU count (")
.append(currentCPUs)
.append(") + is greater or equal to maximum CPU count (")
.append(maxCPUs)
.append(").\n");
logger.warn(buf.toString());
throw new ResourceRequestDeniedException(
"You requested too many CPUs (" +
currentCPUs + "), the " +
"maximum is " +
maxCPUs + " CPUs.");
}
}
}
// zero or below means no check should be made
if (rights.getMaxReservedMinutes() > 0) {
final long max = rights.getMaxReservedMinutes();
final long current = reservedMins.longValue();
if (requestDur + current > max) {
buf.append("\nDenied: Request duration (")
.append(requestDur)
.append(") + current reserved tally (")
.append(current)
.append(") + is greater or equal to maximum reserved (")
.append(max)
.append(").\n");
logger.warn(buf.toString());
throw new ResourceRequestDeniedException(
"Your request is for too much time (" +
requestDur + "), the " +
"maximum reserved at once is " +
max + " minutes. You currently have " +
current + " other reserved minutes.");
}
}
// zero or below means no check should be made
if (rights.getMaxElapsedReservedMinutes() > 0) {
final long max = rights.getMaxElapsedReservedMinutes();
final long currentElapsed = elapsedMins.longValue();
final long currentReserved = reservedMins.longValue();
final long tally = currentElapsed + currentReserved;
if (requestDur + tally > max) {
buf.append("\nDenied: Request duration (")
.append(requestDur)
.append(") + current reserved+elapsed tally (")
.append(tally)
.append(") + is greater or equal to maximum reserved+elapsed (")
.append(max)
.append(").\n");
logger.warn(buf.toString());
throw new ResourceRequestDeniedException(
"Your request is for too much time (" +
requestDur + "), this would exceed the " +
"maximum you can have both used in the " +
"past and have reserved currently. " +
"This maximum is " +
max + " minutes. You currently have " +
currentElapsed + " elapsed minutes " +
"and " + currentReserved +
" reserved minutes and the request for " +
requestDur + " minutes would exceed this.");
}
}
final String dnhash;
if (rights.isDirHashMode()) {
try {
dnhash = HashUtil.hashDN(dn);
} catch (NoSuchAlgorithmException e) {
final String msg = "ERROR: DN hash required but it " +
"is not available: " + e.getMessage();
buf.append(msg);
logger.error(buf.toString());
throw new AuthorizationException(msg);
}
} else {
dnhash = null;
}
for (int i = 0; i < bindings.length; i++) {
final VirtualMachinePartition[] parts =
bindings[i].getPartitions();
if (parts == null) {
final String msg = "ERROR: No partition information in " +
"binding, can't make decision.";
buf.append(msg);
logger.error(buf.toString());
throw new AuthorizationException(msg);
}
checkImages(parts, rights, buf, dn, dnhash);
}
buf.append("\n");
logger.info(buf.toString());
return Decision.PERMIT;
}
protected void checkImages(VirtualMachinePartition[] parts,
GroupRights rights,
StringBuffer buf,
String dn,
String dnhash)
throws AuthorizationException,
ResourceRequestDeniedException {
final String basedir = rights.getImageBaseDirectory();
final String comparisonDir = normalize(basedir, buf);
if (basedir != null && !basedir.equals(comparisonDir)) {
logger.debug("Configured base directory policy normalized from '" +
basedir + "' into '" + comparisonDir + "'");
}
final String hostname = rights.getImageNodeHostname();
String subdir = null;
if (dnhash != null) {
subdir = comparisonDir + "/" + dnhash;
}
for (int i = 0; i < parts.length; i++) {
if (!parts[i].isPropRequired() && !parts[i].isUnPropRequired()) {
logger.debug("groupauthz not examining '" +
parts[i].getImage() + "': no prop/unprop needed");
continue;
}
final String imgString = parts[i].getImage();
final String altTargetString = parts[i].getAlternateUnpropTarget();
try {
final URI imgURI = new URI(imgString);
URI altTargetURI = null;
if (altTargetString != null) {
altTargetURI = new URI(altTargetString);
}
if (hostname != null) {
checkNodeHostname(hostname, buf, imgURI, altTargetURI);
}
if (basedir != null) {
checkNodeBasedir(comparisonDir,
buf,
imgURI,
altTargetURI);
}
if (subdir != null) {
checkNodeBasedir(subdir,
buf,
imgURI,
altTargetURI);
}
} catch (URISyntaxException e) {
final String msg = "ERROR: Partition in " +
"binding is not a valid URI? Can't make decision. " +
" Error message: " + e.getMessage();
buf.append(msg);
logger.error(buf.toString());
throw new AuthorizationException(msg);
}
}
}
protected void checkNodeBasedir(String path,
StringBuffer buf,
URI imgURI,
URI altTargetURI)
throws ResourceRequestDeniedException {
if (!imgURI.getPath().startsWith(path)) {
buf.append("Request denied because image path in request ('")
.append(imgURI.getPath())
.append("') does not start with directory in rights ('")
.append(path)
.append("').\n");
logger.warn(buf.toString());
throw new ResourceRequestDeniedException(
"You may only use images under directory '" + path + "'");
}
if (altTargetURI != null &&
!altTargetURI.getPath().startsWith(path)) {
buf.append("Request denied because alternate target" +
" image path in request ('")
.append(altTargetURI.getHost())
.append("') does not start with directory in rights ('")
.append(path)
.append("').\n");
logger.warn(buf.toString());
throw new ResourceRequestDeniedException(
"You may only save images to alternate " +
"locations starting with base directory '" +
path + "'");
}
}
protected void checkNodeHostname(String hostname,
StringBuffer buf,
URI imgURI,
URI altTargetURI)
throws ResourceRequestDeniedException {
if (!hostname.equalsIgnoreCase(imgURI.getHost())) {
buf.append("Request denied because image node in request ('")
.append(imgURI.getHost())
.append("') does not match image node in rights ('")
.append(hostname)
.append("').\n");
logger.warn(buf.toString());
throw new ResourceRequestDeniedException(
"You may only use images from host '" + hostname + "'");
}
if (altTargetURI != null &&
!imgURI.getHost().equalsIgnoreCase(
altTargetURI.getHost())) {
buf.append("Request denied because alternate target" +
" image node in request ('")
.append(altTargetURI.getHost())
.append("') does not match approved image source " +
"node in request ('")
.append(imgURI.getHost())
.append("').\n");
logger.warn(buf.toString());
throw new ResourceRequestDeniedException(
"You may only save images to alternate " +
"locations on host '" + hostname + "'");
}
}
public Integer checkNewAltTargetURI(GroupRights rights,
URI altTargetURI,
String dn)
throws AuthorizationException {
if (altTargetURI == null) {
throw new IllegalArgumentException("altTargetURI may not be null");
}
if (rights == null) {
throw new IllegalArgumentException("rights may not be null");
}
if (dn == null) {
throw new IllegalArgumentException("DN may not be null");
}
final String dnhash;
if (rights.isDirHashMode()) {
try {
dnhash = HashUtil.hashDN(dn);
} catch (NoSuchAlgorithmException e) {
final String msg = "ERROR: DN hash required but it " +
"is not available: " + e.getMessage();
throw new AuthorizationException(msg);
}
} else {
dnhash = null;
}
final String hostname = rights.getImageNodeHostname();
if (hostname != null) {
if (!hostname.equalsIgnoreCase(altTargetURI.getHost())) {
throw new AuthorizationException(
"You may only use images from host '" + hostname + "'");
}
}
final String basedir = rights.getImageBaseDirectory();
if (basedir == null) {
return Decision.PERMIT; // *** EARLY RETURN ***
}
final String comparisonDir;
try {
comparisonDir = normalize(basedir, null);
} catch (ResourceRequestDeniedException e) {
throw new AuthorizationException(e.getMessage(), e);
}
if (!basedir.equals(comparisonDir)) {
logger.debug("Configured base directory policy normalized from '" +
basedir + "' into '" + comparisonDir + "'");
}
String subdir = null;
if (dnhash != null) {
subdir = comparisonDir + "/" + dnhash;
}
if (!altTargetURI.getPath().startsWith(comparisonDir)) {
throw new AuthorizationException(
"You may only save images to alternate " +
"locations starting with base directory '" +
comparisonDir + "'");
}
if (subdir != null &&
!altTargetURI.getPath().startsWith(subdir)) {
throw new AuthorizationException(
"You may only save images to alternate " +
"locations starting with base directory '" +
subdir + "'");
}
return Decision.PERMIT;
}
protected String normalize(String input, StringBuffer buf)
throws ResourceRequestDeniedException {
if (input == null) {
return "";
}
if (input.trim().length() == 0) {
return "";
}
final String prefix = "Input path '" + input + "' contains " +
"illegal relative path construction";
final String[] parts = input.split("/");
for (int i = 0; i < parts.length; i++) {
String msg = null;
if (parts[i].equals(".")) {
msg = prefix + " '/./' (it's benign but unhandled in " +
"matching code)";
} else if (parts[i].equals("..")) {
msg = prefix + " '/../'";
}
if (msg != null) {
if (buf != null) {
buf.append(msg);
logger.error(buf.toString());
}
throw new ResourceRequestDeniedException(msg);
}
}
final StringBuffer newbuf = new StringBuffer(input.length());
final char[] chars = input.trim().toCharArray();
boolean lastWasSeparator = false;
for (int i = 0; i < chars.length; i++) {
if (lastWasSeparator && chars[i] == '/') {
continue;
} else if (lastWasSeparator) {
lastWasSeparator = false;
} else if (chars[i] == '/') {
lastWasSeparator = true;
}
newbuf.append(chars[i]);
}
return newbuf.toString();
}
void testNormalize() throws Exception {
final String[] inputs =
{"///abc/def/g", "/abc/def//g", "/abc/def///", " ", null, "/asd\n"};
final String[] expects =
{"/abc/def/g", "/abc/def/g", "/abc/def/", "", "", "/asd"};
for (int i = 0; i < inputs.length; i++) {
final String ret = normalize(inputs[i], null);
if (!ret.equals(expects[i])) {
throw new Exception("'" + ret + "' from '" + inputs[i] + "'");
}
}
final String[] allShouldFail =
{"/../abc/def/g", "/abc/def//g/../asd", "/abc/def///../..//"};
for (int i = 0; i < allShouldFail.length; i++) {
try {
normalize(allShouldFail[i], null);
} catch (ResourceRequestDeniedException e) {
continue;
}
throw new Exception("'" + allShouldFail[i] + "' did not fail");
}
}
}