org.globus.workspace.groupauthz.DecisionLogic.java Source code

Java tutorial

Introduction

Here is the source code for org.globus.workspace.groupauthz.DecisionLogic.java

Source

/*
 * 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");
        }

    }
}