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.
This commit is contained in:
Darren Shepherd 2013-10-24 15:05:05 -07:00
parent ad74948480
commit 9f7b4884a7
2 changed files with 36 additions and 5 deletions

View File

@ -22,6 +22,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.apache.cloudstack.acl.RoleType;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ TYPE }) @Target({ TYPE })
public @interface APICommand { public @interface APICommand {
@ -36,4 +38,6 @@ public @interface APICommand {
boolean includeInApiDoc() default true; boolean includeInApiDoc() default true;
String since() default ""; String since() default "";
RoleType[] authorized() default {};
} }

View File

@ -26,6 +26,7 @@ import javax.ejb.Local;
import javax.inject.Inject; import javax.inject.Inject;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import org.apache.cloudstack.api.APICommand;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import com.cloud.exception.PermissionDeniedException; 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); protected static final Logger s_logger = Logger.getLogger(StaticRoleBasedAPIAccessChecker.class);
private static Map<RoleType, Set<String>> s_roleBasedApisMap = Set<String> commandsPropertiesOverrides = new HashSet<String>();
Map<RoleType, Set<String>> commandsPropertiesRoleBasedApisMap =
new HashMap<RoleType, Set<String>>();
Map<RoleType, Set<String>> annotationRoleBasedApisMap =
new HashMap<RoleType, Set<String>>(); new HashMap<RoleType, Set<String>>();
List<PluggableService> _services; List<PluggableService> _services;
@ -51,8 +55,10 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIC
protected StaticRoleBasedAPIAccessChecker() { protected StaticRoleBasedAPIAccessChecker() {
super(); super();
for (RoleType roleType: RoleType.values()) for (RoleType roleType: RoleType.values()) {
s_roleBasedApisMap.put(roleType, new HashSet<String>()); commandsPropertiesRoleBasedApisMap.put(roleType, new HashSet<String>());
annotationRoleBasedApisMap.put(roleType, new HashSet<String>());
}
} }
@Override @Override
@ -64,7 +70,10 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIC
} }
RoleType roleType = _accountService.getRoleType(account); 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) { 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); 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; 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<String> commands = annotationRoleBasedApisMap.get(role);
if (!commands.contains(command.name()))
commands.add(command.name());
}
}
}
return super.start();
}
private void processMapping(Map<String, String> configMap) { private void processMapping(Map<String, String> configMap) {
for (Map.Entry<String, String> entry: configMap.entrySet()) { for (Map.Entry<String, String> entry: configMap.entrySet()) {
String apiName = entry.getKey(); String apiName = entry.getKey();
String roleMask = entry.getValue(); String roleMask = entry.getValue();
commandsPropertiesOverrides.add(apiName);
try { try {
short cmdPermissions = Short.parseShort(roleMask); short cmdPermissions = Short.parseShort(roleMask);
for (RoleType roleType: RoleType.values()) { for (RoleType roleType: RoleType.values()) {
if ((cmdPermissions & roleType.getValue()) != 0) if ((cmdPermissions & roleType.getValue()) != 0)
s_roleBasedApisMap.get(roleType).add(apiName); commandsPropertiesRoleBasedApisMap.get(roleType).add(apiName);
} }
} catch (NumberFormatException nfe) { } catch (NumberFormatException nfe) {
s_logger.info("Malformed key=value pair for entry: " + entry.toString()); 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<PluggableService> _services) { public void setServices(List<PluggableService> _services) {
this._services = _services; this._services = _services;
} }
} }