blob: ae471b2486ca06e728ffb87e7e67cd6ee015ee9d [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 org.apache.cloudstack.acl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.inject.Inject;
import org.apache.cloudstack.acl.dao.RoleDao;
import org.apache.cloudstack.acl.dao.RolePermissionsDao;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.admin.acl.CreateRoleCmd;
import org.apache.cloudstack.api.command.admin.acl.CreateRolePermissionCmd;
import org.apache.cloudstack.api.command.admin.acl.DeleteRoleCmd;
import org.apache.cloudstack.api.command.admin.acl.DeleteRolePermissionCmd;
import org.apache.cloudstack.api.command.admin.acl.ListRolePermissionsCmd;
import org.apache.cloudstack.api.command.admin.acl.ListRolesCmd;
import org.apache.cloudstack.api.command.admin.acl.UpdateRoleCmd;
import org.apache.cloudstack.api.command.admin.acl.UpdateRolePermissionCmd;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.dao.AccountDao;
import com.cloud.utils.ListUtils;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.component.PluggableService;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.db.TransactionStatus;
import com.google.common.base.Strings;
public class RoleManagerImpl extends ManagerBase implements RoleService, Configurable, PluggableService {
private Logger logger = Logger.getLogger(getClass());
@Inject
private AccountDao accountDao;
@Inject
private RoleDao roleDao;
@Inject
private RolePermissionsDao rolePermissionsDao;
@Inject
private AccountManager accountManager;
private void checkCallerAccess() {
if (!isEnabled()) {
throw new PermissionDeniedException("Dynamic api checker is not enabled, aborting role operation");
}
Account caller = getCurrentAccount();
if (caller == null || caller.getRoleId() == null) {
throw new PermissionDeniedException("Restricted API called by an invalid user account");
}
Role callerRole = findRole(caller.getRoleId());
if (callerRole == null || callerRole.getRoleType() != RoleType.Admin) {
throw new PermissionDeniedException("Restricted API called by an user account of non-Admin role type");
}
}
@Override
public boolean isEnabled() {
return RoleService.EnableDynamicApiChecker.value();
}
@Override
public Role findRole(Long id) {
if (id == null || id < 1L) {
logger.trace(String.format("Role ID is invalid [%s]", id));
return null;
}
RoleVO role = roleDao.findById(id);
if (role == null) {
logger.trace(String.format("Role not found [id=%s]", id));
return null;
}
Account account = getCurrentAccount();
if (!accountManager.isRootAdmin(account.getId()) && RoleType.Admin == role.getRoleType()) {
logger.debug(String.format("Role [id=%s, name=%s] is of 'Admin' type and is only visible to 'Root admins'.", id, role.getName()));
return null;
}
return role;
}
/**
* Simple call to {@link CallContext#current()} to retrieve the current calling account.
* This method facilitates unit testing, it avoids mocking static methods.
*/
protected Account getCurrentAccount() {
return CallContext.current().getCallingAccount();
}
@Override
public RolePermission findRolePermission(final Long id) {
if (id == null) {
return null;
}
return rolePermissionsDao.findById(id);
}
@Override
public RolePermission findRolePermissionByUuid(final String uuid) {
if (Strings.isNullOrEmpty(uuid)) {
return null;
}
return rolePermissionsDao.findByUuid(uuid);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_ROLE_CREATE, eventDescription = "creating Role")
public Role createRole(final String name, final RoleType roleType, final String description) {
checkCallerAccess();
if (roleType == null || roleType == RoleType.Unknown) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role type provided");
}
return Transaction.execute(new TransactionCallback<RoleVO>() {
@Override
public RoleVO doInTransaction(TransactionStatus status) {
return roleDao.persist(new RoleVO(name, roleType, description));
}
});
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_ROLE_UPDATE, eventDescription = "updating Role")
public Role updateRole(final Role role, final String name, final RoleType roleType, final String description) {
checkCallerAccess();
if (roleType != null && roleType == RoleType.Unknown) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unknown is not a valid role type");
}
RoleVO roleVO = (RoleVO)role;
if (!Strings.isNullOrEmpty(name)) {
roleVO.setName(name);
}
if (roleType != null) {
if (role.getId() <= RoleType.User.getId()) {
throw new PermissionDeniedException("The role type of default roles cannot be changed");
}
List<? extends Account> accounts = accountDao.findAccountsByRole(role.getId());
if (accounts == null || accounts.isEmpty()) {
roleVO.setRoleType(roleType);
} else {
throw new PermissionDeniedException("Found accounts that have role in use, won't allow to change role type");
}
}
if (!Strings.isNullOrEmpty(description)) {
roleVO.setDescription(description);
}
roleDao.update(role.getId(), roleVO);
return role;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_ROLE_DELETE, eventDescription = "deleting Role")
public boolean deleteRole(final Role role) {
checkCallerAccess();
if (role == null) {
return false;
}
if (role.getId() <= RoleType.User.getId()) {
throw new PermissionDeniedException("Default roles cannot be deleted");
}
List<? extends Account> accounts = accountDao.findAccountsByRole(role.getId());
if (accounts == null || accounts.size() == 0) {
return Transaction.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
List<? extends RolePermission> rolePermissions = rolePermissionsDao.findAllByRoleIdSorted(role.getId());
if (rolePermissions != null && !rolePermissions.isEmpty()) {
for (RolePermission rolePermission : rolePermissions) {
rolePermissionsDao.remove(rolePermission.getId());
}
}
if (roleDao.remove(role.getId())) {
RoleVO roleVO = roleDao.findByIdIncludingRemoved(role.getId());
roleVO.setName(null);
return roleDao.update(role.getId(), roleVO);
}
return false;
}
});
}
throw new PermissionDeniedException("Found accounts that have role in use, won't allow to delete role");
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_ROLE_PERMISSION_CREATE, eventDescription = "creating Role Permission")
public RolePermission createRolePermission(final Role role, final Rule rule, final RolePermission.Permission permission, final String description) {
checkCallerAccess();
return Transaction.execute(new TransactionCallback<RolePermissionVO>() {
@Override
public RolePermissionVO doInTransaction(TransactionStatus status) {
return rolePermissionsDao.persist(new RolePermissionVO(role.getId(), rule.toString(), permission, description));
}
});
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_ROLE_PERMISSION_UPDATE, eventDescription = "updating Role Permission order")
public boolean updateRolePermission(final Role role, final List<RolePermission> newOrder) {
checkCallerAccess();
return role != null && newOrder != null && rolePermissionsDao.update(role, newOrder);
}
@Override
public boolean updateRolePermission(Role role, RolePermission rolePermission, RolePermission.Permission permission) {
checkCallerAccess();
return role != null && rolePermissionsDao.update(role, rolePermission, permission);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_ROLE_PERMISSION_DELETE, eventDescription = "deleting Role Permission")
public boolean deleteRolePermission(final RolePermission rolePermission) {
checkCallerAccess();
return rolePermission != null && rolePermissionsDao.remove(rolePermission.getId());
}
@Override
public List<Role> findRolesByName(String name) {
List<? extends Role> roles = null;
if (StringUtils.isNotBlank(name)) {
roles = roleDao.findAllByName(name);
}
removeRootAdminRolesIfNeeded(roles);
return ListUtils.toListOfInterface(roles);
}
/**
* Removes roles of the given list that have the type '{@link RoleType#Admin}' if the user calling the method is not a 'root admin'.
* The actual removal is executed via {@link #removeRootAdminRoles(List)}. Therefore, if the method is called by a 'root admin', we do nothing here.
*/
protected void removeRootAdminRolesIfNeeded(List<? extends Role> roles) {
Account account = getCurrentAccount();
if (!accountManager.isRootAdmin(account.getId())) {
removeRootAdminRoles(roles);
}
}
/**
* Remove all roles that have the {@link RoleType#Admin}.
*/
protected void removeRootAdminRoles(List<? extends Role> roles) {
if (CollectionUtils.isEmpty(roles)) {
return;
}
Iterator<? extends Role> rolesIterator = roles.iterator();
while (rolesIterator.hasNext()) {
Role role = rolesIterator.next();
if (RoleType.Admin == role.getRoleType()) {
rolesIterator.remove();
}
}
}
@Override
public List<Role> findRolesByType(RoleType roleType) {
if (roleType == null || RoleType.Admin == roleType && !accountManager.isRootAdmin(getCurrentAccount().getId())) {
return Collections.emptyList();
}
List<? extends Role> roles = roleDao.findAllByRoleType(roleType);
return ListUtils.toListOfInterface(roles);
}
@Override
public List<Role> listRoles() {
List<? extends Role> roles = roleDao.listAll();
removeRootAdminRolesIfNeeded(roles);
return ListUtils.toListOfInterface(roles);
}
@Override
public List<RolePermission> findAllPermissionsBy(final Long roleId) {
List<? extends RolePermission> permissions = rolePermissionsDao.findAllByRoleIdSorted(roleId);
if (permissions != null) {
return new ArrayList<>(permissions);
}
return Collections.emptyList();
}
@Override
public String getConfigComponentName() {
return RoleService.class.getSimpleName();
}
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {RoleService.EnableDynamicApiChecker};
}
@Override
public List<Class<?>> getCommands() {
final List<Class<?>> cmdList = new ArrayList<>();
cmdList.add(CreateRoleCmd.class);
cmdList.add(ListRolesCmd.class);
cmdList.add(UpdateRoleCmd.class);
cmdList.add(DeleteRoleCmd.class);
cmdList.add(CreateRolePermissionCmd.class);
cmdList.add(ListRolePermissionsCmd.class);
cmdList.add(UpdateRolePermissionCmd.class);
cmdList.add(DeleteRolePermissionCmd.class);
return cmdList;
}
}