blob: ce9d9755aef19bd884c079a8beeafca7cf5ce6f3 [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.iam;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.inject.Inject;
import org.apache.log4j.Logger;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.PermissionScope;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.iam.api.IAMGroup;
import org.apache.cloudstack.iam.api.IAMPolicy;
import org.apache.cloudstack.iam.api.IAMPolicyPermission;
import org.apache.cloudstack.iam.api.IAMService;
import com.cloud.acl.DomainChecker;
import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.user.Account;
import com.cloud.user.AccountService;
public class RoleBasedEntityAccessChecker extends DomainChecker implements SecurityChecker {
private static final Logger s_logger = Logger.getLogger(RoleBasedEntityAccessChecker.class.getName());
@Inject
AccountService _accountService;
@Inject DomainDao _domainDao;
@Inject
IAMService _iamSrv;
@Override
public boolean checkAccess(Account caller, ControlledEntity entity, AccessType accessType)
throws PermissionDeniedException {
return checkAccess(caller, entity, accessType, null);
}
private String buildAccessCacheKey(Account caller, ControlledEntity entity, AccessType accessType, String action) {
StringBuffer key = new StringBuffer();
key.append(caller.getAccountId());
key.append("-");
String entityType = null;
if (entity != null && entity.getEntityType() != null) {
entityType = entity.getEntityType().getSimpleName();
if (entity instanceof InternalIdentity) {
entityType += ((InternalIdentity)entity).getId();
}
}
key.append(entityType != null ? entityType : "null");
key.append("-");
key.append(accessType != null ? accessType.toString() : "null");
key.append("-");
key.append(action != null ? action : "null");
return key.toString();
}
@Override
public boolean checkAccess(Account caller, ControlledEntity entity, AccessType accessType, String action)
throws PermissionDeniedException {
if (caller == null) {
throw new InvalidParameterValueException("Caller cannot be passed as NULL to IAM!");
}
if (entity == null && action == null) {
throw new InvalidParameterValueException("Entity and action cannot be both NULL in checkAccess!");
}
// check IAM cache first
String accessKey = buildAccessCacheKey(caller, entity, accessType, action);
CheckAccessResult allowDeny = (CheckAccessResult)_iamSrv.getFromIAMCache(accessKey);
if (allowDeny != null) {
s_logger.debug("IAM access check for " + accessKey + " from cache: " + allowDeny.isAllow());
if (allowDeny.isAllow()) {
return true;
} else {
if (allowDeny.getDenyMsg() != null) {
throw new PermissionDeniedException(allowDeny.getDenyMsg());
} else {
return false;
}
}
}
if (entity == null && action != null) {
// check if caller can do this action
List<IAMPolicy> policies = _iamSrv.listIAMPolicies(caller.getAccountId());
boolean isAllowed = _iamSrv.isActionAllowedForPolicies(action, policies);
if (!isAllowed) {
String msg = "The action '" + action + "' not allowed for account " + caller;
_iamSrv.addToIAMCache(accessKey, new CheckAccessResult(msg));
throw new PermissionDeniedException(msg);
}
_iamSrv.addToIAMCache(accessKey, new CheckAccessResult(true));
return true;
}
// if a Project entity, skip
Account entityAccount = _accountService.getAccount(entity.getAccountId());
if (entityAccount != null && entityAccount.getType() == Account.ACCOUNT_TYPE_PROJECT) {
_iamSrv.addToIAMCache(accessKey, new CheckAccessResult(false));
return false;
}
String entityType = null;
if (entity.getEntityType() != null) {
entityType = entity.getEntityType().getSimpleName();
}
if (accessType == null) {
accessType = AccessType.UseEntry;
}
// get all Policies of this caller by considering recursive domain group policy
List<IAMPolicy> policies = getEffectivePolicies(caller);
HashMap<IAMPolicy, Boolean> policyPermissionMap = new HashMap<IAMPolicy, Boolean>();
for (IAMPolicy policy : policies) {
List<IAMPolicyPermission> permissions = new ArrayList<IAMPolicyPermission>();
if (action != null) {
permissions = _iamSrv.listPolicyPermissionByActionAndEntity(policy.getId(), action, entityType);
if (permissions.isEmpty()) {
if (accessType != null) {
for (AccessType type : AccessType.values()) {
if (type.ordinal() >= accessType.ordinal()) {
permissions.addAll(_iamSrv.listPolicyPermissionByAccessAndEntity(policy.getId(),
type.toString(), entityType));
}
}
}
}
} else {
if (accessType != null) {
for (AccessType type : AccessType.values()) {
if (type.ordinal() >= accessType.ordinal()) {
permissions.addAll(_iamSrv.listPolicyPermissionByAccessAndEntity(policy.getId(),
type.toString(), entityType));
}
}
}
}
for (IAMPolicyPermission permission : permissions) {
if (checkPermissionScope(caller, permission.getScope(), permission.getScopeId(), entity)) {
if (permission.getEntityType().equals(entityType)) {
policyPermissionMap.put(policy, permission.getPermission().isGranted());
break;
} else if (permission.getEntityType().equals("*")) {
policyPermissionMap.put(policy, permission.getPermission().isGranted());
}
}
}
if (policyPermissionMap.containsKey(policy) && policyPermissionMap.get(policy)) {
_iamSrv.addToIAMCache(accessKey, new CheckAccessResult(true));
return true;
}
}
if (!policies.isEmpty()) { // Since we reach this point, none of the
// roles granted access
String msg = "Account " + caller + " does not have permission to access resource " + entity
+ " for access type: " + accessType;
if (s_logger.isDebugEnabled()) {
s_logger.debug(msg);
}
_iamSrv.addToIAMCache(accessKey, new CheckAccessResult(msg));
throw new PermissionDeniedException(msg);
}
_iamSrv.addToIAMCache(accessKey, new CheckAccessResult(false));
return false;
}
@Override
public boolean checkAccess(Account caller, AccessType accessType, String action, ControlledEntity... entities)
throws PermissionDeniedException {
// operate access on multiple entities?
if (accessType != null && accessType == AccessType.OperateEntry) {
// In this case caller MUST own n-1 entities.
for (ControlledEntity entity : entities) {
checkAccess(caller, entity, accessType, action);
boolean otherEntitiesAccess = true;
for (ControlledEntity otherEntity : entities) {
if (otherEntity.getAccountId() == caller.getAccountId()
|| (checkAccess(caller, otherEntity, accessType, action) && otherEntity.getAccountId() == entity
.getAccountId())) {
continue;
} else {
otherEntitiesAccess = false;
break;
}
}
if (otherEntitiesAccess) {
return true;
}
}
throw new PermissionDeniedException(caller
+ " does not have permission to perform this operation on these resources");
} else {
for (ControlledEntity entity : entities) {
if (!checkAccess(caller, entity, accessType, action)) {
return false;
}
}
return true;
}
}
private boolean checkPermissionScope(Account caller, String scope, Long scopeId, ControlledEntity entity) {
if(scopeId != null && !scopeId.equals(new Long(IAMPolicyPermission.PERMISSION_SCOPE_ID_CURRENT_CALLER))){
//scopeId is set
if (scope.equals(PermissionScope.ACCOUNT.name())) {
if(scopeId == entity.getAccountId()){
return true;
}
} else if (scope.equals(PermissionScope.DOMAIN.name())) {
if (_domainDao.isChildDomain(scopeId, entity.getDomainId())) {
return true;
}
} else if (scope.equals(PermissionScope.RESOURCE.name())) {
if (entity instanceof InternalIdentity) {
InternalIdentity entityWithId = (InternalIdentity) entity;
if(scopeId.equals(entityWithId.getId())){
return true;
}
}
}
} else if (scopeId == null || scopeId.equals(new Long(IAMPolicyPermission.PERMISSION_SCOPE_ID_CURRENT_CALLER))) {
if (scope.equals(PermissionScope.ACCOUNT.name())) {
if(caller.getAccountId() == entity.getAccountId()){
return true;
}
} else if (scope.equals(PermissionScope.DOMAIN.name())) {
if (_domainDao.isChildDomain(caller.getDomainId(), entity.getDomainId())) {
return true;
}
}
}
return false;
}
private List<IAMPolicy> getEffectivePolicies(Account caller) {
List<IAMPolicy> policies = _iamSrv.listIAMPolicies(caller.getId());
List<IAMGroup> groups = _iamSrv.listIAMGroups(caller.getId());
for (IAMGroup group : groups) {
// for each group find the grand parent groups.
List<IAMGroup> parentGroups = _iamSrv.listParentIAMGroups(group.getId());
for (IAMGroup parentGroup : parentGroups) {
policies.addAll(_iamSrv.listRecursiveIAMPoliciesByGroup(parentGroup.getId()));
}
}
return policies;
}
private class CheckAccessResult {
boolean allow;
String denyMsg;
public CheckAccessResult(boolean aw) {
this(aw, null);
}
public CheckAccessResult(String msg) {
this(false, msg);
}
public CheckAccessResult(boolean aw, String msg) {
allow = aw;
denyMsg = msg;
}
public boolean isAllow() {
return allow;
}
public void setAllow(boolean aw) {
allow = aw;
}
public String getDenyMsg() {
return denyMsg;
}
public void setDenyMsg(String denyMsg) {
this.denyMsg = denyMsg;
}
}
}