edu.wpi.margrave.SQSReader.java Source code

Java tutorial

Introduction

Here is the source code for edu.wpi.margrave.SQSReader.java

Source

/*
 Copyright 2009-2010 Brown University and Worcester Polytechnic Institute.
    
This file is part of Margrave.
    
Margrave is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
Margrave 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 Lesser General Public License for more details.
    
You should have received a copy of the GNU Lesser General Public License
along with Margrave.  If not, see <http://www.gnu.org/licenses/>.
*/

// tn

package edu.wpi.margrave;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import kodkod.ast.Expression;
import kodkod.ast.Formula;

import org.json.*;

public class SQSReader {
    protected static MVocab createSQSVocab(String polId) throws MGEBadIdentifierName, MGEUnknownIdentifier {
        MVocab env = new MVocab();

        env.addSort("Principal");
        env.addSort("Action");
        env.addSort("Resource");
        env.addSort("Condition");

        // All of these sorts are pairwise disjoint
        // since they are incomparable in the ordering.

        // Decisions will be Alloy, Deny
        // ReqVars will be: p, a, r, c.

        return env;
    }

    protected static Formula handleStatementCondition(JSONObject obj, Formula theCondition, MVocab vocab)
            throws JSONException, MGEBadIdentifierName, MGEUnknownIdentifier, MGEManagerException {
        // Condition block is a conjunctive list of conditions. all must apply.
        // Each condition is a conjunctive list of value sets. 
        // Each value set is a disjunctive list of values
        // Keys in the condition block are functions to apply
        // Keys in a condition are attribute names. Values are values.

        // Condition block
        //    Function : {}
        //    Function : {}
        // ...

        JSONArray conditionNames = obj.names();
        for (int iCondition = 0; iCondition < conditionNames.length(); iCondition++) {
            String cFunction = (String) conditionNames.get(iCondition);
            JSONObject condition = (JSONObject) obj.get(cFunction);
            //MEnvironment.errorStream.println(cFunction + ": "+condition);

            // condition is a key:value pair or multiple such pairs. The value may be an array.
            // Each sub-condition must be met.
            HashSet<Formula> thisCondition = new HashSet<Formula>();

            JSONArray subConditionNames = condition.names();
            for (int iSubCondition = 0; iSubCondition < subConditionNames.length(); iSubCondition++) {
                String cSubKey = (String) subConditionNames.get(iSubCondition);
                Object subcondition = condition.get(cSubKey);

                // Subcondition: is it an array or a single value?
                if (subcondition instanceof JSONArray) {
                    JSONArray subarr = (JSONArray) subcondition;
                    HashSet<Formula> valuedisj = new HashSet<Formula>();
                    for (int iValue = 0; iValue < subarr.length(); iValue++) {
                        //MEnvironment.errorStream.println(cFunction+"("+cSubKey+", "+subarr.get(iValue)+")");
                        Formula theatom = makeSQSAtom(vocab, "c", "Condition",
                                cFunction + "<" + cSubKey + "><" + subarr.get(iValue) + ">");
                        valuedisj.add(theatom);
                    }

                    thisCondition.add(MFormulaManager.makeDisjunction(valuedisj));
                } else {
                    //MEnvironment.errorStream.println(cFunction+"("+cSubKey+", "+subcondition+")");   
                    Formula theatom = makeSQSAtom(vocab, "c", "Condition",
                            cFunction + "<" + cSubKey + "><" + subcondition + ">");
                    thisCondition.add(theatom);
                }

            }

            theCondition = MFormulaManager.makeAnd(theCondition, MFormulaManager.makeConjunction(thisCondition));
        }

        return theCondition;
    }

    protected static Formula makeSQSAtom(MVocab vocab, String varname, String parentsortname, String predname)
            throws MGEBadIdentifierName, MGEUnknownIdentifier, MGEManagerException {
        // Get the variable for this varname. 
        Expression thevar = MFormulaManager.makeVariable(varname);

        // Add the sort (if it doesn't already exist.)

        predname = MVocab.validateIdentifierFromExternalPolicy("is" + predname, true);
        parentsortname = MVocab.validateIdentifierFromExternalPolicy(parentsortname, true);

        // Kludge (for now) to make * contain subsorts:

        //if(predname.equalsIgnoreCase("principal.aws=*")) 
        //   parentsortname = "Principal";
        //else

        //if(predname.equalsIgnoreCase("action=sqs:*"))
        //   parentsortname = "action";
        //else

        vocab.addPredicate(predname, parentsortname);

        if (predname.startsWith("principal.aws")) {
            vocab.addPredicate("principal.aws=*", "Principal");
            vocab.axioms.addConstraintSubset(predname, "principal.aws=*");
        } else if (predname.startsWith("action=sqs")) {
            vocab.addPredicate("action=sqs:*", "Action");
            vocab.axioms.addConstraintSubset(predname, "action=sqs:*");
        }

        //vocab.addSubSort(parentsortname, sortname);

        //MEnvironment.errorStream.println(sortname);

        // TODO Infer disjointness where appropriate
        // (e.g., Principal.AWS=555566667777 and Principal.AWS=123456789012 should be disjoint, but
        //  Principal.AWS=555566667777 with and without dashes should not be
        // More ugly code:

        // This should be a *specific* AWS. Disjoint from all other SPECIFIC ones.
        if (predname.startsWith("principal.aws=") && !predname.contains("*")) {
            Set<String> other_candidates = vocab.getSortNamesWithPrefix("principal.aws=");

            // Don't disj ones with a * in them.
            Set<String> others = new HashSet<String>();
            for (String s : other_candidates)
                if (!s.contains("*") && !s.equals(predname))
                    others.add(s);

            vocab.axioms.addConstraintDisjoint(predname, others);
        }

        // This should be a *specific* resource ID. Disjoint from all other specific ones
        if (predname.startsWith("resource=") && !predname.contains("*") && !predname.contains("&")) {
            Set<String> other_candidates = vocab.getSortNamesWithPrefix("resource=");

            // Don't disj ones with a * or & in them.
            Set<String> others = new HashSet<String>();
            for (String s : other_candidates)
                if (!s.contains("*") && !s.contains("&") && !s.equals(predname))
                    others.add(s);

            vocab.axioms.addConstraintDisjoint(predname, others);
        }

        // From docs:
        // Although most of the information in this appendix is ***service-agnostic***, 
        // there are some SQS-specific details you need to know. For more information, 
        // see Special Information for SQS Policies.

        // So we support only the SQS-specific stuff here for now.

        MPredicate thepred = vocab.predicates.get(predname);
        return MFormulaManager.makeAtom(thevar, thepred.rel);
    }

    protected static Formula handleStatementPAR(Object obj, String varname, String parentsortname,
            Formula theTarget, MVocab vocab, String prepend) throws MGEUnsupportedSQS, JSONException,
            MGEBadIdentifierName, MGEUnknownIdentifier, MGEManagerException {
        // obj may be a JSONObject (Principal examples)
        //   with a child with a value OR array of values

        //"Principal": {
        //"AWS": "*"
        //}

        //"Principal": {
        //"AWS": ["123456789012","555566667777"]
        //}

        // may also be a simple value (Action example)
        // or an array of values (Resource examples)

        // "Action": ["SQS:SendMessage","SQS:ReceiveMessage"],
        // "Resource": "/987654321098/queue1"

        // TODO: the example principal from "Element Descriptions" doesn't parse...
        //"Principal":[
        //             "AWS": "123456789012",
        //             "AWS": "999999999999"
        //          ]
        // is this just a bad example?

        // *****************************
        // Step 1: Is this a JSONObject? If so, it should have only one key. Prepend that key
        // to all predicate names produced by its value.
        if (obj instanceof JSONObject) {
            JSONObject jobj = (JSONObject) obj;
            JSONArray names = jobj.names();
            if (names.length() != 1)
                throw new MGEUnsupportedSQS("Number of keys != 1 as expected: " + obj.toString());

            Object inner = jobj.get((String) names.get(0));
            return handleStatementPAR(inner, varname, parentsortname, theTarget, vocab,
                    prepend + MEnvironment.sIDBSeparator + names.get(0));
        }

        // Now if obj is a simple value, we have a predicate name.
        // If it is an array of length n, we have n predicate names.      
        if (obj instanceof JSONArray) {
            JSONArray jarr = (JSONArray) obj;
            HashSet<Formula> possibleValues = new HashSet<Formula>();

            for (int ii = 0; ii < jarr.length(); ii++) {
                //MEnvironment.errorStream.println(prepend+"="+jarr.get(ii));

                Formula theatom = makeSQSAtom(vocab, varname, parentsortname, prepend + "=" + jarr.get(ii));
                possibleValues.add(theatom);

            }

            theTarget = MFormulaManager.makeAnd(theTarget, MFormulaManager.makeDisjunction(possibleValues));

        } else {
            Formula theatom = makeSQSAtom(vocab, varname, parentsortname, prepend + "=" + obj);
            theTarget = MFormulaManager.makeAnd(theTarget, theatom);
        }

        return theTarget;
    }

    protected static void handleSQSStatement(JSONObject thisStatement, MPolicyLeaf result, int counter)
            throws JSONException, MGEUnsupportedSQS, MGEBadIdentifierName, MGEUnknownIdentifier,
            MGEManagerException {
        String ruleId = result.name + "_rule_" + counter;

        // Documentation says the statement ID is optional.
        if (!thisStatement.isNull("Sid"))
            ruleId = thisStatement.getString("Sid");

        String effect;
        if (!thisStatement.isNull("Effect"))
            effect = thisStatement.getString("Effect");
        else
            return; // no effect means no need to add the rule

        List<String> ruleNameOrdering = new ArrayList<String>(4);
        ruleNameOrdering.add("p");
        ruleNameOrdering.add("a");
        ruleNameOrdering.add("r");
        ruleNameOrdering.add("c");

        Formula theTarget = Formula.TRUE;
        Formula theCondition = Formula.TRUE;

        // target starts as true. All criteria must be met, so just "and" each on.

        // *************************
        // "Principal": disjunction
        if (thisStatement.isNull("Principal"))
            return; // never applies, so don't create the rule.            
        theTarget = handleStatementPAR(thisStatement.get("Principal"), "p", "Principal", theTarget, result.vocab,
                "Principal");

        // *************************      
        // "Action": disjunction
        if (thisStatement.isNull("Action"))
            return; // never applies, so don't create the rule.
        theTarget = handleStatementPAR(thisStatement.get("Action"), "a", "Action", theTarget, result.vocab,
                "Action");

        // *************************
        // "Resource": disjunction
        if (thisStatement.isNull("Resource"))
            return; // never applies, so don't create the rule.
        theTarget = handleStatementPAR(thisStatement.get("Resource"), "r", "Resource", theTarget, result.vocab,
                "Resource");

        // *************************
        // "Condition": more complex
        // Condition optional? TODO -- for now if no condition, just finalize the rule. 
        // (Why are P/A/R special?)
        if (!thisStatement.isNull("Condition"))
            theCondition = handleStatementCondition(thisStatement.getJSONObject("Condition"), theTarget,
                    result.vocab);

        // Finalize the rule.
        result.addRule(ruleId, effect, ruleNameOrdering, theTarget, theCondition);
    }

    protected static MPolicy loadSQS(String polId, String sFileName) throws MUserException {
        // Convert filename
        sFileName = MPolicy.convertSeparators(sFileName);

        // Read in the JSON text

        BufferedReader reader;
        try {
            reader = new BufferedReader(new FileReader(sFileName));
            StringBuffer target = new StringBuffer();
            while (reader.ready()) {
                String line = reader.readLine();
                target.append(line + "\n");
            }
            reader.close();

            JSONObject json = new JSONObject(target.toString());

            // Use provided policy ID
            //String polId;         
            //if(!json.isNull("Id"))
            //   polId = json.getString("Id");
            //else
            //   throw new MGEUnsupportedSQS("Id element must be present.");

            MVocab env = createSQSVocab(polId);
            MPolicyLeaf result = new MPolicyLeaf(polId, env);

            result.declareVariable("p", "Principal");
            result.declareVariable("a", "Action");
            result.declareVariable("r", "Resource");
            result.declareVariable("c", "Condition");

            // Get statements; may be an array or a single statement object.
            Object statement_s = json.get("Statement");
            if (statement_s instanceof JSONArray) {
                JSONArray statements = json.getJSONArray("Statement");

                for (int ii = 0; ii < statements.length(); ii++) {
                    JSONObject thisStatement = statements.getJSONObject(ii);
                    handleSQSStatement(thisStatement, result, ii);
                }
            } else
                handleSQSStatement((JSONObject) statement_s, result, 0);

            // "Allow overrides a `default deny' but never an explicit deny."
            // Default deny is N/a.
            Set<String> denySet = new HashSet<String>();
            denySet.add("Deny");
            result.rCombineWhatOverrides.put("Allow", denySet); // Allow < {Deny}

            result.initIDBs();

            return result;

        } catch (IOException e) {
            throw new MGEUnsupportedSQS(e.toString());
        } catch (JSONException e) {
            throw new MGEUnsupportedSQS(e.toString());
        }

    }
}