/*
* Copyright (c) 2008-2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.api.service.authorization;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.emc.storageos.db.client.model.*;
import com.emc.storageos.db.common.VdcUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;
import com.emc.storageos.api.service.impl.resource.ArgValidator;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.exceptions.DatabaseException;
import com.emc.storageos.model.auth.ACLAssignmentChanges;
import com.emc.storageos.model.auth.ACLEntry;
import com.emc.storageos.model.auth.RoleAssignmentChanges;
import com.emc.storageos.model.auth.RoleAssignmentEntry;
import com.emc.storageos.security.authorization.ACL;
import com.emc.storageos.security.authorization.BasePermissionsHelper;
import com.emc.storageos.security.authorization.PermissionsKey;
import com.emc.storageos.security.validator.StorageOSPrincipal;
import com.emc.storageos.security.validator.Validator;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.emc.storageos.svcs.errorhandling.resources.InternalException;
/**
* Class derived helper methods for role/acl db access and implements methods converting them for api access
*/
public class PermissionsHelper extends BasePermissionsHelper {
private int _maxRoleAclEntries = 100;
public void setMaxRoleAclEntries(int count) {
_maxRoleAclEntries = count;
}
/**
* Constructor - takes db client
*
* @param dbClient
*/
public PermissionsHelper(DbClient dbClient) {
super(dbClient);
}
/**
* Converts StringSetMap of permissions into a list of assignment entries as used by the API
*
* @param roleAssignments
* @param forZone
* @return
*/
public ArrayList<RoleAssignmentEntry> convertToRoleAssignments(StringSetMap roleAssignments, boolean forZone) {
ArrayList<RoleAssignmentEntry> assignments = new ArrayList<RoleAssignmentEntry>();
if (roleAssignments != null && !roleAssignments.isEmpty()) {
for (Map.Entry<String, AbstractChangeTrackingSet<String>> roleAssignment : roleAssignments.entrySet()) {
PermissionsKey rowKey = new PermissionsKey();
rowKey.parseFromString(roleAssignment.getKey());
RoleAssignmentEntry entry = new RoleAssignmentEntry();
if (rowKey.getType().equals(PermissionsKey.Type.GROUP)) {
entry.setGroup(rowKey.getValue());
} else if (rowKey.getType().equals(PermissionsKey.Type.SID)) {
entry.setSubjectId(rowKey.getValue());
}
for (String role : roleAssignment.getValue()) {
if ((forZone && isRoleZoneLevel(role)) || (!forZone && isRoleTenantLevel(role))) {
entry.getRoles().add(role);
}
}
if (!entry.getRoles().isEmpty()) {
assignments.add(entry);
}
}
}
return assignments;
}
/**
* Abstract class for filtering role entries from input
*/
public static abstract class RoleInputFilter {
protected TenantOrg _tenant;
protected List<String> _groups;
protected List<String> _users;
protected List<String> _localUsers;
protected List<String> _rootRoleAssignments;
protected RoleInputFilter(TenantOrg tenant) {
_tenant = tenant;
}
protected PermissionsKey getPermissionKeyForEntry(RoleAssignmentEntry entry)
throws SecurityException {
PermissionsKey key;
StorageOSPrincipal principal = new StorageOSPrincipal();
if (entry.getGroup() != null) {
entry.setGroup(entry.getGroup().trim());
key = new PermissionsKey(PermissionsKey.Type.GROUP, entry.getGroup());
principal.setName(entry.getGroup());
principal.setType(StorageOSPrincipal.Type.Group);
} else if (entry.getSubjectId() != null) {
entry.setSubjectId(entry.getSubjectId().trim());
key = new PermissionsKey(PermissionsKey.Type.SID, entry.getSubjectId());
principal.setName(entry.getSubjectId());
principal.setType(StorageOSPrincipal.Type.User);
} else {
throw APIException.badRequests.invalidEntryForRoleAssignmentSubjectIdAndGroupAreNull();
}
return key;
}
protected void initLists() {
_users = new ArrayList<String>();
_groups = new ArrayList<String>();
_localUsers = new ArrayList<String>();
}
protected StringSetMap convertFromRolesNoLocalUsers(
List<RoleAssignmentEntry> roles) {
StringSetMap resulStringSetMap = convertFromRoleAssignments(roles);
if (!CollectionUtils.isEmpty(_localUsers)) {
throw APIException.badRequests
.invalidEntryForRoleAssignmentSubjectIdsCannotBeALocalUsers(StringUtils
.join(_localUsers, ", "));
}
return resulStringSetMap;
}
protected StringSetMap convertFromRoleAssignments(
List<RoleAssignmentEntry> assignments) {
initLists();
StringSetMap rolesMap = new StringSetMap();
if (!CollectionUtils.isEmpty(assignments)) {
for (RoleAssignmentEntry roleAssignment : assignments) {
PermissionsKey key = getPermissionKeyForEntry(roleAssignment);
if (key.getType().equals(PermissionsKey.Type.SID)
&& Validator.isUserLocal(key.getValue())) {
_localUsers.add(key.getValue());
if (key.getValue().equalsIgnoreCase("root")) {
_rootRoleAssignments = roleAssignment.getRoles();
}
}
if (CollectionUtils.isEmpty(roleAssignment.getRoles())) {
throw APIException.badRequests.noRoleSpecifiedInAssignmentEntry();
}
for (String role : roleAssignment.getRoles()) {
if (!isValidRole(role)) {
throw APIException.badRequests
.invalidEntryForRoleAssignment(role);
}
rolesMap.put(key.toString(), role.toUpperCase());
}
addPrincipalToList(key, roleAssignment);
}
}
return rolesMap;
}
protected abstract boolean isValidRole(String ace);
protected abstract void validatePrincipals();
protected abstract void addPrincipalToList(PermissionsKey key,
RoleAssignmentEntry roleAssignment);
public abstract StringSetMap convertFromRolesRemove(
List<RoleAssignmentEntry> remove);
public abstract StringSetMap convertFromRolesAdd(List<RoleAssignmentEntry> add,
boolean validate);
}
/**
* Update role assignments on a given tenant
*
* @param tenant TenantOrg object
* @param changes RoleAssignmentChanges
* @param filter RoleInputFilter for the roles allowed
*/
public void updateRoleAssignments(TenantOrg tenant, RoleAssignmentChanges changes,
RoleInputFilter filter) {
// in GEO scenario, root shouldn't be assigned with any tenant roles
if (!CollectionUtils.isEmpty(changes.getAdd()) && !VdcUtil.isLocalVdcSingleSite()) {
StringSetMap roleAssignments =
filter.convertFromRolesAdd(changes.getAdd(), false);
for (Map.Entry<String, AbstractChangeTrackingSet<String>> roleAssignment : roleAssignments.entrySet()) {
if (roleAssignment.getKey().equalsIgnoreCase("root")) {
throw APIException.badRequests.invalidRoleAssignments(
"root can't be assigned with tenant roles in GEO scenario"
);
}
}
}
updateRoleAssignments(tenant.getRoleAssignments(), changes, filter);
}
/**
* Update role assignments on a given virtual data center
*
* @param vdc VirtualDataCenter object
* @param changes RoleAssignmentChanges
* @param filter RoleInputFilter for the roles allowed
*/
public void updateRoleAssignments(VirtualDataCenter vdc, RoleAssignmentChanges changes,
RoleInputFilter filter) {
updateRoleAssignments(vdc.getRoleAssignments(), changes, filter);
}
private void updateRoleAssignments(StringSetMap originalRoleAssignments, RoleAssignmentChanges changes,
RoleInputFilter filter) {
if (!CollectionUtils.isEmpty(changes.getRemove())) {
StringSetMap rolesRemoved =
filter.convertFromRolesRemove(changes.getRemove());
for (Map.Entry<String, AbstractChangeTrackingSet<String>> roleAssignment : rolesRemoved.entrySet()) {
String rowKey = roleAssignment.getKey();
for (String role : roleAssignment.getValue()) {
originalRoleAssignments.remove(rowKey, role);
}
}
}
if (!CollectionUtils.isEmpty(changes.getAdd())) {
StringSetMap roleAssignments =
filter.convertFromRolesAdd(changes.getAdd(), false);
int current = 0;
if (originalRoleAssignments != null) {
current = originalRoleAssignments.size();
}
if (current + roleAssignments.size() > _maxRoleAclEntries) {
throw APIException.badRequests.exceedingRoleAssignmentLimit(
_maxRoleAclEntries, current + roleAssignments.entrySet().size());
}
roleAssignments = filter.convertFromRolesAdd(changes.getAdd(), true);
for (Map.Entry<String, AbstractChangeTrackingSet<String>> roleAssignment : roleAssignments.entrySet()) {
String rowKey = roleAssignment.getKey();
for (String role : roleAssignment.getValue()) {
originalRoleAssignments.put(rowKey, role);
}
}
}
}
/**
* Abstract class for filtering acl entries from input
*/
public static abstract class ACLInputFilter {
protected abstract PermissionsKey getPermissionKeyForEntry(ACLEntry entry)
throws InternalException;
protected abstract void validate();
protected abstract boolean isValidACL(String ace);
/**
* Converts a list of ACLEntries as used by the API into StringSetMap saved to
* Project
*
* @param entries
* @param validatePrincipals
* @return
*/
public StringSetMap convertFromACLEntries(List<ACLEntry> entries,
boolean validatePrincipals) {
StringSetMap assignments = new StringSetMap();
initLists();
if (entries != null && !entries.isEmpty()) {
for (ACLEntry entry : entries) {
PermissionsKey key = getPermissionKeyForEntry(entry);
ArgValidator.checkFieldNotEmpty(entry.getAces(), "privilege");
int countACLType = 0;
if (entry.getTenant() != null) {
countACLType++;
}
if (entry.getGroup() != null) {
countACLType++;
}
if (entry.getSubjectId() != null) {
countACLType++;
}
if (countACLType == 0) {
throw APIException.badRequests.invalidACLTypeEmptyNotAllowed();
} else if (countACLType > 1) {
throw APIException.badRequests.invalidACLTypeMultipleNotAllowed();
}
for (String ace : entry.getAces()) {
// owner is not settable through acls, we save it as acl for
// indexing
if (!isValidACL(ace)) {
throw APIException.badRequests.invalidACL(ace);
}
assignments.put(key.toString(), ace.toUpperCase());
}
addPrincipalToList(key);
}
}
if (validatePrincipals) {
validate();
}
return assignments;
}
protected abstract void addPrincipalToList(PermissionsKey key);
protected abstract void initLists();
}
/**
* Filter class for validating input args for usage ACLs
*/
public static class UsageACLFilter extends PermissionsHelper.ACLInputFilter {
private final BasePermissionsHelper _helper;
private final String _specifier;
public UsageACLFilter(BasePermissionsHelper helper, String specifier) {
_helper = helper;
_specifier = specifier;
}
public UsageACLFilter(BasePermissionsHelper helper) {
this(helper, null);
}
@Override
public PermissionsKey getPermissionKeyForEntry(ACLEntry entry)
throws DatabaseException {
if (entry.getTenant() == null) {
throw APIException.badRequests.invalidEntryACLEntryMissingTenant();
}
TenantOrg tenant = _helper.getObjectById(URI.create(entry.getTenant()), TenantOrg.class);
if (tenant == null) {
throw APIException.badRequests.invalidEntryForUsageACL(entry.getTenant());
}
PermissionsKey key;
if (_specifier == null) {
key = new PermissionsKey(PermissionsKey.Type.TENANT, entry.getTenant());
} else {
key = new PermissionsKey(PermissionsKey.Type.TENANT, entry.getTenant(), _specifier);
}
return key;
}
@Override
public boolean isValidACL(String ace) {
return (isUsageACL(ace));
}
@Override
protected void validate() {
// Usage ACLs don't pertain to users or group. Nothing to validate here.
return;
}
@Override
protected void addPrincipalToList(PermissionsKey key) {
// Don't need to add anything here since no validation is done
}
@Override
protected void initLists() {
// No lists to initialize
}
}
/**
* Update the acls from request
*
* @param obj Object on which acls need to be updated
* @param changes ACLAssignmentChanges from the request
* @param filter ACLInputFilter for this object
*/
public void updateACLs(DataObjectWithACLs obj, ACLAssignmentChanges changes,
ACLInputFilter filter) {
if (changes.getRemove() != null && !changes.getRemove().isEmpty()) {
StringSetMap acls = filter.convertFromACLEntries(changes.getRemove(), false);
for (Map.Entry<String, AbstractChangeTrackingSet<String>> aclAssignment : acls.entrySet()) {
String rowKey = aclAssignment.getKey();
for (String ace : aclAssignment.getValue()) {
obj.removeAcl(rowKey, ace);
}
}
}
if (changes.getAdd() != null && !changes.getAdd().isEmpty()) {
// call this once, just to get the number of acls
StringSetMap acls = filter.convertFromACLEntries(changes.getAdd(), false);
int current = 0;
if (obj.getAcls() != null) {
current = obj.getAcls().size();
}
if (current + acls.size() > _maxRoleAclEntries) {
throw APIException.badRequests.exceedingRoleAssignmentLimit(_maxRoleAclEntries, current + acls.size());
}
// now that we're sure the total number of acls is less than the
// max, we can validate the users.
acls = filter.convertFromACLEntries(changes.getAdd(), true);
for (Map.Entry<String, AbstractChangeTrackingSet<String>> aclAssignment : acls.entrySet()) {
String rowKey = aclAssignment.getKey();
for (String ace : aclAssignment.getValue()) {
obj.addAcl(rowKey, ace);
}
}
}
}
public void checkTenantHasAccessToVirtualArray(URI tenantId,
VirtualArray varray) {
if (!tenantHasUsageACL(tenantId, varray)) {
throw APIException.forbidden.tenantCannotAccessVirtualArray(varray.getLabel());
}
}
public void checkTenantHasAccessToVirtualPool(URI tenantId, VirtualPool vpool) {
if (!tenantHasUsageACL(tenantId, vpool)) {
throw APIException.forbidden.tenantCannotAccessVirtualPool(vpool.getLabel());
}
}
public void checkTenantHasAccessToComputeVirtualPool(URI tenantId, ComputeVirtualPool vcpool) {
if (!tenantHasUsageACL(tenantId, vcpool)) {
throw APIException.forbidden.tenantCannotAccessComputeVirtualPool(vcpool.getLabel());
}
}
/**
* Update the acls for the DiscoveredComputedSystems objects (vCenter)
* from request.
*
* @param obj Object on which acls need to be updated
* @param changes ACLAssignmentChanges from the request
* @param filter ACLInputFilter for this object
*/
public void updateACLs(DiscoveredComputeSystemWithAcls obj, ACLAssignmentChanges changes,
ACLInputFilter filter) {
if (changes.getRemove() != null && !changes.getRemove().isEmpty()) {
StringSetMap acls = filter.convertFromACLEntries(changes.getRemove(), false);
for (Map.Entry<String, AbstractChangeTrackingSet<String>> aclAssignment : acls.entrySet()) {
String rowKey = aclAssignment.getKey();
obj.removeAcl(rowKey);
}
}
if (changes.getAdd() != null && !changes.getAdd().isEmpty()) {
// call this once, just to get the number of acls
StringSetMap acls = filter.convertFromACLEntries(changes.getAdd(), false);
int current = 0;
if (obj.getAcls() != null) {
current = obj.getAcls().size();
}
if (current + acls.size() > _maxRoleAclEntries) {
throw APIException.badRequests.exceedingRoleAssignmentLimit(_maxRoleAclEntries, current + acls.size());
}
// now that we're sure the total number of acls is less than the
// max, we can validate the users.
acls = filter.convertFromACLEntries(changes.getAdd(), true);
for (Map.Entry<String, AbstractChangeTrackingSet<String>> aclAssignment : acls.entrySet()) {
String rowKey = aclAssignment.getKey();
for (String ace : aclAssignment.getValue()) {
obj.addAcl(rowKey, ace);
}
}
}
}
}