| // 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 org.apache.cloudstack.acl; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.inject.Inject; |
| import javax.naming.ConfigurationException; |
| |
| import com.cloud.exception.UnavailableCommandException; |
| |
| import org.apache.cloudstack.api.APICommand; |
| |
| import com.cloud.exception.PermissionDeniedException; |
| import com.cloud.user.Account; |
| import com.cloud.user.AccountService; |
| import com.cloud.user.User; |
| import com.cloud.utils.PropertiesUtil; |
| import com.cloud.utils.component.AdapterBase; |
| import com.cloud.utils.component.PluggableService; |
| |
| // This is the default API access checker that grab's the user's account |
| // based on the account type, access is granted |
| @Deprecated |
| public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIAclChecker { |
| |
| |
| private Set<String> commandPropertyFiles = new HashSet<String>(); |
| private Set<String> commandNames = new HashSet<String>(); |
| private Set<String> commandsPropertiesOverrides = new HashSet<String>(); |
| private Map<RoleType, Set<String>> commandsPropertiesRoleBasedApisMap = new HashMap<RoleType, Set<String>>(); |
| private Map<RoleType, Set<String>> annotationRoleBasedApisMap = new HashMap<RoleType, Set<String>>(); |
| private List<PluggableService> services; |
| |
| @Inject |
| private AccountService accountService; |
| @Inject |
| private RoleService roleService; |
| |
| public StaticRoleBasedAPIAccessChecker() { |
| super(); |
| for (RoleType roleType : RoleType.values()) { |
| commandsPropertiesRoleBasedApisMap.put(roleType, new HashSet<String>()); |
| annotationRoleBasedApisMap.put(roleType, new HashSet<String>()); |
| } |
| } |
| |
| /** |
| * Only one strategy should be used between StaticRoleBasedAPIAccessChecker and DynamicRoleBasedAPIAccessChecker |
| * Default behavior is to use the Dynamic version. The StaticRoleBasedAPIAccessChecker is the legacy version. |
| * If roleService is enabled, then it uses the DynamicRoleBasedAPIAccessChecker, otherwise, it will use the |
| * StaticRoleBasedAPIAccessChecker. |
| */ |
| @Override |
| public boolean isEnabled() { |
| if (roleService.isEnabled()) { |
| logger.debug("RoleService is enabled. We will use it instead of StaticRoleBasedAPIAccessChecker."); |
| } |
| return !roleService.isEnabled(); |
| } |
| |
| @Override |
| public List<String> getApisAllowedToUser(Role role, User user, List<String> apiNames) throws PermissionDeniedException { |
| if (!isEnabled()) { |
| return apiNames; |
| } |
| |
| RoleType roleType = role.getRoleType(); |
| apiNames.removeIf(apiName -> !isApiAllowed(apiName, roleType)); |
| |
| return apiNames; |
| } |
| |
| @Override |
| public boolean checkAccess(User user, String commandName) throws PermissionDeniedException { |
| if (!isEnabled()) { |
| return true; |
| } |
| |
| Account account = accountService.getAccount(user.getAccountId()); |
| if (account == null) { |
| throw new PermissionDeniedException(String.format("The account with id [%s] for user with uuid [%s] is null.", user.getAccountId(), user.getUuid())); |
| } |
| |
| return checkAccess(account, commandName); |
| } |
| |
| @Override |
| public boolean checkAccess(Account account, String commandName) { |
| if (!isEnabled()) { |
| return true; |
| } |
| |
| RoleType roleType = accountService.getRoleType(account); |
| if (isApiAllowed(commandName, roleType)) { |
| return true; |
| } |
| |
| if (commandNames.contains(commandName)) { |
| throw new PermissionDeniedException(String.format("Request to API [%s] was denied. The role type [%s] is not allowed to request it.", commandName, roleType.toString())); |
| } else { |
| throw new UnavailableCommandException(String.format("The API [%s] does not exist or is not available for this account.", commandName)); |
| } |
| } |
| |
| /** |
| * Verifies if the API is allowed for the given RoleType. |
| * |
| * @param apiName API command to be verified |
| * @param roleType to be verified |
| * @return 'true' if the API is allowed for the given RoleType, otherwise, 'false'. |
| */ |
| public boolean isApiAllowed(String apiName, RoleType roleType) { |
| if (commandsPropertiesOverrides.contains(apiName)) { |
| return commandsPropertiesRoleBasedApisMap.get(roleType).contains(apiName); |
| } |
| return annotationRoleBasedApisMap.get(roleType).contains(apiName); |
| } |
| |
| @Override |
| public boolean configure(String name, Map<String, Object> params) throws ConfigurationException { |
| super.configure(name, params); |
| |
| for (String commandPropertyFile : commandPropertyFiles) { |
| processMapping(PropertiesUtil.processConfigFile(new String[] { commandPropertyFile })); |
| } |
| 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()); |
| } |
| if (!commandNames.contains(command.name())) { |
| commandNames.add(command.name()); |
| } |
| } |
| } |
| return super.start(); |
| } |
| |
| private void processMapping(Map<String, String> configMap) { |
| for (Map.Entry<String, String> entry : configMap.entrySet()) { |
| String apiName = entry.getKey(); |
| String roleMask = entry.getValue(); |
| if (!commandNames.contains(apiName)) { |
| commandNames.add(apiName); |
| } |
| commandsPropertiesOverrides.add(apiName); |
| try { |
| short cmdPermissions = Short.parseShort(roleMask); |
| for (RoleType roleType : RoleType.values()) { |
| if ((cmdPermissions & roleType.getMask()) != 0) |
| commandsPropertiesRoleBasedApisMap.get(roleType).add(apiName); |
| } |
| } catch (NumberFormatException nfe) { |
| logger.error(String.format("Malformed key=value pair for entry: [%s].", entry)); |
| } |
| } |
| } |
| |
| public List<PluggableService> getServices() { |
| return services; |
| } |
| |
| @Inject |
| public void setServices(List<PluggableService> services) { |
| this.services = services; |
| } |
| |
| public Set<String> getCommandPropertyFiles() { |
| return commandPropertyFiles; |
| } |
| |
| public void setCommandPropertyFiles(Set<String> commandPropertyFiles) { |
| this.commandPropertyFiles = commandPropertyFiles; |
| } |
| |
| } |