blob: a19bc9645401d1f90b0726d73f1e27554432bfa9 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 com.cloud.bridge.util;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.json.simple.parser.ContentHandler;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import com.cloud.bridge.service.core.s3.S3BucketPolicy;
import com.cloud.bridge.service.core.s3.S3BucketPolicy.PolicyAccess;
import com.cloud.bridge.service.core.s3.S3ConditionFactory;
import com.cloud.bridge.service.core.s3.S3PolicyAction;
import com.cloud.bridge.service.core.s3.S3PolicyAction.PolicyActions;
import com.cloud.bridge.service.core.s3.S3PolicyCondition;
import com.cloud.bridge.service.core.s3.S3PolicyCondition.ConditionKeys;
import com.cloud.bridge.service.core.s3.S3PolicyConditionBlock;
import com.cloud.bridge.service.core.s3.S3PolicyPrincipal;
import com.cloud.bridge.service.core.s3.S3PolicyStatement;
import com.cloud.bridge.service.exception.PermissionDeniedException;
/**
* This class uses the JSON simple parser to convert the JSON of a Bucket Policy
* into internal objects.
*
* Another way to implement this by use of a stack to keep track of where the current
* parsing is being done. However, since we are only handling a limited JSON sequence
* here simple counts and flags will do the same as a stack.
*/
public class PolicyParser {
protected final static Logger logger = Logger.getLogger(PolicyParser.class);
private S3BucketPolicy bucketPolicy = null;
private S3PolicyPrincipal principals = null;
private S3PolicyStatement statement = null;
private S3PolicyAction actions = null;
private S3PolicyAction convertActions = new S3PolicyAction();
private S3PolicyCondition condition = null;
private S3ConditionFactory condFactory = null;
private S3PolicyConditionBlock block = null;
private PolicyActions notAction = PolicyActions.UnknownAction;
private String id = null;
private String sid = null;
private String effect = null;
private String resource = null;
private String condKey = null; // -> the next key in a condition
private String toUser = null; // -> text to user of a problem
private List<String> valueList = new ArrayList<String>();
private JSONParser jparser = null;
private int entryNesting = 0; // -> startObjectEntry() .. nesting count
private int condNested = 0; // -> at what level of nesting is the condition defined
private int keyNested = 0; // -> at what level of nesting is the condition key defined
private boolean inId = false; // -> currently in an "Id" element
private boolean inSid = false;
private boolean inAWS = false;
private boolean inEffect = false;
private boolean inResource = false;
private boolean inNotAction = false;
private boolean inVersion = false;
private boolean inStatement = false;
public PolicyParser() {
jparser = new JSONParser();
condFactory = new S3ConditionFactory();
}
ContentHandler myHandler = new ContentHandler() {
public boolean endArray() throws ParseException {
logger.debug("endArray()");
return true;
}
public void endJSON() throws ParseException, PermissionDeniedException {
logger.debug("endJSON()");
if (null != statement) {
//System.out.println( "endJSON() - statement");
if (null != block) {
block.verify();
statement.setConditionBlock(block);
}
if (null != bucketPolicy) {
statement.verify();
bucketPolicy.addStatement(statement);
}
statement = null;
block = null;
}
}
public boolean endObject() throws ParseException, PermissionDeniedException {
logger.debug("endObject(), nesting: " + entryNesting);
if (null != statement && 1 >= entryNesting) {
//System.out.println( "endObject() - statement");
if (null != block) {
block.verify();
statement.setConditionBlock(block);
}
if (null != bucketPolicy) {
statement.verify();
bucketPolicy.addStatement(statement);
}
statement = null;
block = null;
}
if (0 == entryNesting)
inStatement = false;
return true;
}
public boolean endObjectEntry() throws ParseException, PermissionDeniedException {
logger.debug("endObjectEntry(), nesting: " + entryNesting);
if (inSid) {
if (null != statement)
statement.setSid(sid);
inSid = false;
} else if (inEffect) {
if (null != statement) {
if (effect.equalsIgnoreCase("Allow"))
statement.setEffect(PolicyAccess.ALLOW);
else if (effect.equalsIgnoreCase("Deny"))
statement.setEffect(PolicyAccess.DENY);
else
badPolicy("Effect", effect);
}
inEffect = false;
} else if (inResource) {
if (null != statement && resource.startsWith("arn:aws:s3:::")) {
String resourcePath = resource.substring(13);
verifySameBucket(resourcePath);
statement.setResource(resourcePath);
}
inResource = false;
} else if (inNotAction) {
if (null != statement)
statement.setNotAction(notAction);
inNotAction = false;
} else if (inVersion) {
inVersion = false;
} else if (inId) {
if (null != bucketPolicy)
bucketPolicy.setId(id);
inId = false;
} else if (null != actions) {
if (null != statement)
statement.setActions(actions);
actions = null;
} else if (null != principals) {
if (inAWS && null != statement)
statement.setPrincipals(principals);
principals = null;
} else if (null != condition) {
//System.out.println( "in condition: " + condNested + " " + entryNesting + " " + keyNested );
// -> is it just the current key that is done?
try {
if (keyNested == entryNesting) {
String[] values = valueList.toArray(new String[0]);
ConditionKeys tempKey = S3PolicyCondition.toConditionKeys(condKey);
if (ConditionKeys.UnknownKey == tempKey)
badPolicy("Condition Key", condKey);
condition.setKey(tempKey, values);
valueList.clear();
condKey = null;
}
} catch (ParseException e) {
logger.error("Policy Parser condition error: ", e);
throw e;
} catch (Exception e) {
logger.error("Policy Parser condition error: ", e);
badPolicy("Condition Key (" + condKey + ")", e.toString());
}
// -> is the condition completely done?
if (condNested == entryNesting) {
condition.verify();
block.addCondition(condition);
condition = null;
}
} else if (null != statement && 1 == entryNesting) {
if (null != block) {
block.verify();
statement.setConditionBlock(block);
}
if (null != bucketPolicy) {
statement.verify();
bucketPolicy.addStatement(statement);
}
statement = null;
block = null;
}
entryNesting--;
return true;
}
public boolean primitive(Object value) throws ParseException, PermissionDeniedException {
logger.debug("primitive(): " + value);
if (inSid) {
sid = (String)value;
} else if (inEffect) {
effect = (String)value;
} else if (inResource) {
resource = (String)value;
} else if (inNotAction) {
notAction = convertActions.toPolicyActions((String)value);
if (notAction == PolicyActions.UnknownAction)
badPolicy("NotAction", (String)value);
} else if (inId) {
id = (String)value;
} else if (null != actions) {
PolicyActions tempAction = convertActions.toPolicyActions((String)value);
if (tempAction == PolicyActions.UnknownAction)
badPolicy("Action", (String)value);
actions.addAction(tempAction);
} else if (null != principals) {
principals.addPrincipal((String)value);
} else if (null != condition) {
// -> a condition key can have one or more values
valueList.add((String)value);
} else if (inVersion) {
String version = (String)value;
if (!version.equals("2008-10-17"))
badPolicy("Version", (String)value);
}
return true;
}
public boolean startArray() throws ParseException {
logger.debug("startArray()");
return true;
}
public void startJSON() throws ParseException {
logger.debug("startJSON()");
}
public boolean startObject() throws ParseException {
logger.debug("startObject(), nesting: " + entryNesting);
if (1 == entryNesting && inStatement)
statement = new S3PolicyStatement();
return true;
}
/**
* Note: A statement does not have to have a condition block to be valid.
*/
public boolean startObjectEntry(String key) throws ParseException {
entryNesting++;
logger.debug("startObjectEntry(), key: [" + key + "]");
inSid = false;
inAWS = false;
inEffect = false;
inResource = false;
inNotAction = false;
inVersion = false;
inId = false;
if (key.equalsIgnoreCase("Statement"))
inStatement = true;
else if (key.equalsIgnoreCase("Action"))
actions = new S3PolicyAction();
else if (key.equalsIgnoreCase("Principal"))
principals = new S3PolicyPrincipal();
else if (key.equalsIgnoreCase("Condition"))
block = new S3PolicyConditionBlock();
else if (key.equalsIgnoreCase("AWS") && null != principals)
inAWS = true;
else if (key.equalsIgnoreCase("CanonicalUser") && null != principals)
inAWS = true;
else if (key.equalsIgnoreCase("Sid"))
inSid = true;
else if (key.equalsIgnoreCase("Effect"))
inEffect = true;
else if (key.equalsIgnoreCase("Resource"))
inResource = true;
else if (key.equalsIgnoreCase("NotAction"))
inNotAction = true;
else if (key.equalsIgnoreCase("Version"))
inVersion = true;
else if (key.equalsIgnoreCase("Id"))
inId = true;
else if (null != condition) {
condKey = key;
keyNested = entryNesting;
} else if (null != block) {
condition = condFactory.createCondition(key);
condNested = entryNesting;
if (null == condition)
badPolicy("Condition type", key);
} else
logger.debug("startObjectEntry() no match");
return true;
}
};
public S3BucketPolicy parse(String policy, String bucketName) throws ParseException, PermissionDeniedException {
bucketPolicy = new S3BucketPolicy();
bucketPolicy.setBucketName(bucketName);
jparser.parse(policy, myHandler);
return bucketPolicy;
}
/**
* From Amazon on S3 Policies:
* "Each policy must cover only a single bucket and resources within that bucket (when writing a
* policy, don't include statements that refer to other buckets or resources in other buckets)"
*
* @param resourcePath
*/
private void verifySameBucket(String resourcePath) throws PermissionDeniedException {
String testBucketName = resourcePath;
String bucketName = bucketPolicy.getBucketName();
// -> extract just the bucket name
int offset = testBucketName.indexOf("/");
if (-1 != offset)
testBucketName = testBucketName.substring(0, offset);
if (!testBucketName.equals(bucketName))
throw new PermissionDeniedException("The S3 Bucket Policy must only refer to the single bucket: \"" + bucketName +
"\", but it referres to the following resource: \"" + resourcePath + "\"");
}
public static void badPolicy(String place, String badValue) throws ParseException {
String toUser = new String("S3 Bucket Policy " + place + " of: \"" + badValue + "\" is unknown");
throw new ParseException(ParseException.ERROR_UNEXPECTED_TOKEN, toUser);
}
}