From 9f7b4884a7a0bf0b15d94777cff449fde4664af2 Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Thu, 24 Oct 2013 15:05:05 -0700 Subject: [PATCH] Make commands.properties optional for non-ACS code Currently any new API extension to CloudStack must edit commands.properties to add the appropriate ACLs. This generally works fine for ACS as we control the contents of that file and distribute all the code ourself. The hang up comes when somebody develops code outside of ACS and want to add their code to an existing ACS installation. The Spring work that has been done has made this much easier, but you are still required to manually edit commands.properties. This change introduces the following logic. First check commands.properties for ACL info. If ACL info exists, use that to authorize the command. If no ACL information exists (ie null), then look at the @APICommand annotation. The defaults of @APICommand will provide no ACL info. If the @APICommand annotation provides no ACL info, use that. --- .../org/apache/cloudstack/api/APICommand.java | 4 ++ .../acl/StaticRoleBasedAPIAccessChecker.java | 37 ++++++++++++++++--- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/api/src/org/apache/cloudstack/api/APICommand.java b/api/src/org/apache/cloudstack/api/APICommand.java index 4d024c15a5d..7c9e6fec2e7 100644 --- a/api/src/org/apache/cloudstack/api/APICommand.java +++ b/api/src/org/apache/cloudstack/api/APICommand.java @@ -22,6 +22,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.apache.cloudstack.acl.RoleType; + @Retention(RetentionPolicy.RUNTIME) @Target({ TYPE }) public @interface APICommand { @@ -36,4 +38,6 @@ public @interface APICommand { boolean includeInApiDoc() default true; String since() default ""; + + RoleType[] authorized() default {}; } diff --git a/plugins/acl/static-role-based/src/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java b/plugins/acl/static-role-based/src/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java index affcf919daf..bf3acf5f427 100644 --- a/plugins/acl/static-role-based/src/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java +++ b/plugins/acl/static-role-based/src/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java @@ -26,6 +26,7 @@ import javax.ejb.Local; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.api.APICommand; import org.apache.log4j.Logger; import com.cloud.exception.PermissionDeniedException; @@ -43,7 +44,10 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIC protected static final Logger s_logger = Logger.getLogger(StaticRoleBasedAPIAccessChecker.class); - private static Map> s_roleBasedApisMap = + Set commandsPropertiesOverrides = new HashSet(); + Map> commandsPropertiesRoleBasedApisMap = + new HashMap>(); + Map> annotationRoleBasedApisMap = new HashMap>(); List _services; @@ -51,8 +55,10 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIC protected StaticRoleBasedAPIAccessChecker() { super(); - for (RoleType roleType: RoleType.values()) - s_roleBasedApisMap.put(roleType, new HashSet()); + for (RoleType roleType: RoleType.values()) { + commandsPropertiesRoleBasedApisMap.put(roleType, new HashSet()); + annotationRoleBasedApisMap.put(roleType, new HashSet()); + } } @Override @@ -64,7 +70,10 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIC } RoleType roleType = _accountService.getRoleType(account); - boolean isAllowed = s_roleBasedApisMap.get(roleType).contains(commandName); + boolean isAllowed = commandsPropertiesOverrides.contains(commandName) ? + commandsPropertiesRoleBasedApisMap.get(roleType).contains(commandName) : + annotationRoleBasedApisMap.get(roleType).contains(commandName); + if (!isAllowed) { throw new PermissionDeniedException("The API does not exist or is blacklisted. Role type=" + roleType.toString() + " is not allowed to request the api: " + commandName); } @@ -80,15 +89,32 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIC return true; } + + @Override + public boolean start() { + for ( PluggableService service : _services ) { + for ( Class clz : service.getCommands() ) { + APICommand command = clz.getAnnotation(APICommand.class); + for ( RoleType role : command.authorized() ) { + Set commands = annotationRoleBasedApisMap.get(role); + if (!commands.contains(command.name())) + commands.add(command.name()); + } + } + } + return super.start(); + } + private void processMapping(Map configMap) { for (Map.Entry entry: configMap.entrySet()) { String apiName = entry.getKey(); String roleMask = entry.getValue(); + commandsPropertiesOverrides.add(apiName); try { short cmdPermissions = Short.parseShort(roleMask); for (RoleType roleType: RoleType.values()) { if ((cmdPermissions & roleType.getValue()) != 0) - s_roleBasedApisMap.get(roleType).add(apiName); + commandsPropertiesRoleBasedApisMap.get(roleType).add(apiName); } } catch (NumberFormatException nfe) { s_logger.info("Malformed key=value pair for entry: " + entry.toString()); @@ -104,4 +130,5 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIC public void setServices(List _services) { this._services = _services; } + }