/**
* Copyright 2012 Comcast Corporation
*
* 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 com.comcast.cmb.common.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.comcast.cmb.common.util.CMBErrorCodes;
import com.comcast.cmb.common.util.CMBException;
/**
* Class used to represent a policy for CNS and CQS. Each Policy has a list of Statements.
* @author bwolf, vvenkatraman, baosen, tina
*/
public class CMBPolicy {
public static final List<String> POLICY_ATTRIBUTES = Arrays.asList("Version", "Id", "Statement");
public static final List<String> STATEMENT_ATTRIBUTES = Arrays.asList("Sid", "Effect", "Principal", "Action", "Resource", "Condition");
public enum SERVICE {
CQS, CNS
};
public static final List<String> CQS_ACTIONS = Arrays.asList("AddPermission", "ChangeMessageVisibility", "ChangeMessageVisibilityBatch", "CreateQueue", "DeleteMessage", "DeleteMessageBatch", "DeleteQueue", "GetQueueAttributes", "GetQueueUrl", "ListQueues", "ReceiveMessage", "RemovePermission", "SendMessage", "SendMessageBatch", "SetQueueAttributes");
public static final List<String> CNS_ACTIONS = Arrays.asList("AddPermission", "ConfirmSubscription", "CreateTopic", "DeleteTopic", "GetSubscriptionAttributes", "GetTopicAttributes", "ListSubscriptions", "ListSubscriptionsByTopic", "ListTopics", "Publish", "RemovePermission", "SetSubscriptionAttributes", "SetTopicAttributes", "Subscribe", "Unsubscribe");
protected List<CMBStatement> statements = null;
protected String id;
protected String version;
/**
* construct a new policy
*/
public CMBPolicy() {
this.id = UUID.randomUUID().toString();
this.version = "2012-09-13";
this.statements = new ArrayList<CMBStatement>();
}
/**
* parse the policy string to fill statements
* @param policy json encoded string of policy
* @throws Exception
*/
public CMBPolicy(String policyString) throws Exception {
this();
fromString(policyString);
}
/**
* Add a statement to this policy identified by sid. One cannot override an existing statement
* @param service
* @param sid
* @param effect
* @param userList
* @param actionList
* @param resource
* @param condition
* @return true if statement was added, false otherwise
* @throws CMBException
*/
public boolean addStatement(SERVICE service, String sid, String effect, List<String> userList, List<String> actionList, String resource, CMBCondition condition) throws CMBException {
if (this.statements.size() > 0) { // no duplicate label can be set
for (CMBStatement stmt : this.statements) {
if (stmt.getSid().equals(sid)) {
return false;
}
}
}
List<String> normalizedActionList = new ArrayList<String>();
for (String action : actionList) {
if (action.equals("")) {
throw new CMBException(CMBErrorCodes.ValidationError, "Blank action parameter is invalid");
}
if (!CNS_ACTIONS.contains(action) && !CQS_ACTIONS.contains(action) && !action.equals("*")) {
throw new CMBException(CMBErrorCodes.InvalidParameterValue, "Invalid action parameter " + action);
}
normalizedActionList.add(action.contains(":") ? action : service + ":" + action);
}
this.statements.add(new CMBStatement(sid, effect, userList, normalizedActionList, resource, condition));
return true;
}
/**
*
* @param sid
* @return true if statement was removed/false otherwise
*/
public boolean removeStatement(String sid) {
if (this.statements.size() > 0) {
for (Iterator<CMBStatement> it = statements.iterator(); it.hasNext();) {
CMBStatement stmt = it.next();
if (stmt.getSid().equals(sid)) {
it.remove();
return true;
}
}
}
return false;
}
public List<CMBStatement> getStatements() {
return this.statements;
}
/**
* check all statements matching user/action, return false upon the first Deny effect
* or no Allow effect; otherwise return true.
* @param user
* @param action
* @return
*/
public boolean isAllowed(User user, String action) {
if (statements == null) {
return false;
}
boolean allow = false;
String actionPrefix = action.substring(0, action.lastIndexOf(':') + 1);
for (CMBStatement stmt : statements) {
if (stmt.getAction().contains(action) || stmt.getAction().contains(actionPrefix + "*")) {
if (stmt.getPrincipal().contains(user.getUserId()) || stmt.getPrincipal().contains("*")) {
if (stmt.getEffect() == CMBStatement.EFFECT.Deny) {
return false;
} else {
allow = true;
}
}
}
}
return allow;
}
@Override
public String toString() {
StringBuffer out = new StringBuffer();
out.append("{\n").append("\"Version\": \"").append(this.version).append("\",\n");
out.append("\"Id\": \"").append(this.id).append("\",\n");
out.append("\"Statement\": [\n");
int count = 0;
for (CMBStatement stmt : this.statements) {
out.append("{\n").append(stmt.toString()).append("}");
count++;
out.append(count != this.statements.size() ? ",\n" : "\n");
}
out.append("]\n").append("}");
return out.toString();
}
/**
* Parse and populate this object given policyString
* @param policyString
* @throws Exception
*/
public void fromString(String policyString) throws Exception {
if (policyString == null || policyString.isEmpty()) {
return;
}
// check if valid json
JSONObject json = new JSONObject(policyString);
// validate semantics
Iterator<String> policyIterator = json.keys();
while (policyIterator.hasNext()) {
String policyAttribute = policyIterator.next();
if (!CMBPolicy.POLICY_ATTRIBUTES.contains(policyAttribute)) {
throw new CMBException(CMBErrorCodes.InvalidAttributeValue, "Invalid value for the parameter Policy");
}
}
JSONArray stmts = json.getJSONArray("Statement");
if (stmts != null) {
for (int i=0; i<stmts.length(); i++) {
Iterator<String> stmtIterator = stmts.getJSONObject(i).keys();
while (stmtIterator.hasNext()) {
String statementAttribute = stmtIterator.next();
if (!CMBPolicy.STATEMENT_ATTRIBUTES.contains(statementAttribute)) {
throw new CMBException(CMBErrorCodes.InvalidAttributeValue, "Invalid value for the parameter Policy");
}
}
}
}
// parse content
this.statements = new ArrayList<CMBStatement>();
if (json.has("Id")) {
id = json.getString("Id");
}
version = json.getString("Version");
for (int i = 0; i < stmts.length(); i++) {
JSONObject obj = (JSONObject) stmts.get(i);
CMBStatement statement = new CMBStatement();
statement.setSid(obj.getString("Sid"));
statement.setEffect(obj.getString("Effect"));
if (obj.has("Condition")) {
statement.setCondition(new CMBCondition(obj.getString("Condition")));
}
String principal = obj.getJSONObject("Principal").getString(CMBStatement.PRINCIPAL_FIELD);
if (principal.contains("[")) {
List<String> accessList = getStringList(obj.getJSONObject("Principal").getJSONArray(CMBStatement.PRINCIPAL_FIELD));
statement.setPrincipal(accessList);
} else {
statement.setPrincipal(Arrays.asList(principal));
}
String action = obj.getString("Action");
if (action.contains("[")) {
List<String> actionList = getStringList(obj.getJSONArray("Action"));
statement.setAction(actionList);
} else {
statement.setAction(Arrays.asList(action));
}
if (obj.has("Resource")) {
statement.setResource(obj.getString("Resource"));
}
this.statements.add(statement);
}
}
private List<String> getStringList(JSONArray jsonArr) throws JSONException {
List<String> list = new ArrayList<String>();
for (int i = 0; i < jsonArr.length(); i++) {
list.add((String) jsonArr.get(i));
}
return list;
}
}