Java tutorial
/******************************************************************************* * Educational Online Test Delivery System * Copyright (c) 2014 American Institutes for Research * * Distributed under the AIR Open Source License, Version 1.0 * See accompanying file AIR-License-1_0.txt or at * https://bitbucket.org/sbacoss/eotds/wiki/AIR_Open_Source_License ******************************************************************************/ package org.opentestsystem.shared.security.service; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.opentestsystem.shared.exception.LocalizedException; import org.opentestsystem.shared.progman.client.domain.Tenant; import org.opentestsystem.shared.progman.client.domain.TenantType; import org.opentestsystem.shared.security.domain.SbacEntity; import org.opentestsystem.shared.security.domain.SbacRole; import org.opentestsystem.shared.security.domain.SbacUser; import org.opentestsystem.shared.security.domain.permission.RoleToPermissionMapping; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; public abstract class AbsractRolesAndPermissionsService implements RolesAndPermissionsService { private static final Logger LOGGER = LoggerFactory.getLogger(AbsractRolesAndPermissionsService.class); private static final int ROLE_FIELD_COUNT = 18; private static final String ROLE_ENTITY_LEVEL = "Level"; private static final String ROLE_ENTITY_NAME = "RoleEntityName"; private static final String ROLE_NAME = "Name"; private static final Map<Integer, String> NON_ENTITY_INDEX_MAP = ImmutableMap.of(1, ROLE_ENTITY_NAME, 2, ROLE_NAME, 3, ROLE_ENTITY_LEVEL); private static final ImmutableMap<Integer, TenantType> ENTITY_INDEX_MAP = ImmutableMap .<Integer, TenantType>builder().put(4, TenantType.CLIENT).put(6, TenantType.STATE_GROUP) .put(8, TenantType.STATE).put(10, TenantType.DISTRICT_GROUP).put(12, TenantType.DISTRICT) .put(14, TenantType.INSTITUTION_GROUP).put(16, TenantType.INSTITUTION).build(); public static final Predicate<SbacRole> ROLE_APPLICABLE_TO_COMPONENT_FILTER = new Predicate<SbacRole>() { @Override public boolean apply(final SbacRole sbacRole) { return sbacRole.isApplicableToComponent(); } }; private static final Function<SbacRole, String> ROLE_NAME_MAP_TRANSFORMER = new Function<SbacRole, String>() { @Override public String apply(final SbacRole role) { return role.getEntityByType(role.getRoleEntityLevel()).getEntityId() + "|" + role.getRoleEntityLevel() + "@" + role.getRoleName(); } }; private static final Function<SbacRole, Collection<SbacEntity>> ENTITY_TRANSFORMER = new Function<SbacRole, Collection<SbacEntity>>() { @Override public Collection<SbacEntity> apply(final SbacRole role) { return role.getEntities(); } }; private static final Function<Tenant, String> TENANT_TRANSFORMER = new Function<Tenant, String>() { @Override public String apply(final Tenant tenant) { return tenant.getName() + "|" + tenant.getType(); } }; private static final Predicate<SbacEntity> BLANK_ENTITY_ID_FILTER = new Predicate<SbacEntity>() { @Override public boolean apply(final SbacEntity sbacEntity) { return StringUtils.isEmpty(sbacEntity.getEntityId()); } }; protected abstract List<Tenant> getApplicableTenants(HashSet<SbacEntity> inNewHashSet); protected Multimap<String, SbacRole> extractUserRoles(final String pipeDelimitedTenancyChain, final Map<String, RoleToPermissionMapping> roleToPermissionMappings) { List<String[]> roleStringsDelimited = new ArrayList<String[]>(); LOGGER.debug("tenantChain: " + pipeDelimitedTenancyChain); if (StringUtils.isNotEmpty(pipeDelimitedTenancyChain)) { final String[] attr = pipeDelimitedTenancyChain.split("[|]", -1); // split one long string into individual rows for parsing final int roleCount = attr.length / ROLE_FIELD_COUNT; for (int i = 0; i < roleCount; i++) { final Object[] roleStringSet = ArrayUtils.subarray(attr, i * ROLE_FIELD_COUNT, (i + 1) * ROLE_FIELD_COUNT); roleStringsDelimited.add((String[]) roleStringSet); } } final Multimap<String, SbacRole> userRoles = buildRolesFromParsedStrings(roleToPermissionMappings, roleStringsDelimited); setupEffectiveTenantsForRoles(Lists.newArrayList(userRoles.values())); return userRoles; } protected Multimap<String, SbacRole> extractUserRoles(final String[] pipeDelimitedTenancyChains, final Map<String, RoleToPermissionMapping> roleToPermissionMappings) { LOGGER.debug("tenantChain: " + pipeDelimitedTenancyChains); List<String[]> roleStringsDelimited = new ArrayList<String[]>(); for (final String roleChain : pipeDelimitedTenancyChains) { if (StringUtils.isNotEmpty(roleChain)) { roleStringsDelimited.add(roleChain.split("[|]", -1)); } } final Multimap<String, SbacRole> userRoles = buildRolesFromParsedStrings(roleToPermissionMappings, roleStringsDelimited); setupEffectiveTenantsForRoles(Lists.newArrayList(userRoles.values())); return userRoles; } private void setupEffectiveTenantsForRoles(final List<SbacRole> roleList) { final Map<String, SbacRole> roleNameMap = Maps.uniqueIndex( Iterables.filter(roleList, ROLE_APPLICABLE_TO_COMPONENT_FILTER), ROLE_NAME_MAP_TRANSFORMER); for (final Map.Entry<String, SbacRole> roleMapEntry : roleNameMap.entrySet()) { SbacRole role = roleMapEntry.getValue(); final List<Tenant> tenantsComponentRole = getApplicableTenants(Sets.newHashSet(role.getEntities())); // may not always be present due to tenancy roles if (tenantsComponentRole != null) { Collections.sort(tenantsComponentRole, Collections.reverseOrder(new Tenant.TenantTypeComparator())); for (Tenant tenant : tenantsComponentRole) { if (role.getEffectiveEntity().getEntityType().equals(tenant.getType()) && role.getEffectiveEntity().getEntityId().equals(tenant.getName())) { //first check to see if the effective entity (the one the role is assigned at) has a valid tenant) //if so, just use that one roleMapEntry.getValue().setEffectiveTenant(tenant); } else if (!TenantType.CLIENT.equals(tenant.getType())) { //otherwise 'walk the tenant tree' to see if there is a non-client tenant that matches the roles hierarchy. for (SbacEntity entity : role.getEntities()) { if (entity.getEntityId() != null && entity.getEntityId().equals(tenant.getName()) && entity.getEntityType().equals(tenant.getType())) { //build a new tenant (since one wasn't matched) with the roles effective tenant level, //but with the subscription details from the matched tenant higher in the hierarchy. Tenant newTenant = new Tenant(); newTenant.setTenantSubscriptions(tenant.getTenantSubscriptions()); newTenant.setId(role.getEffectiveEntity().getEntityId()); newTenant.setName(role.getEffectiveEntity().getEntityName()); newTenant.setType(role.getEffectiveEntity().getEntityType()); roleMapEntry.getValue().setEffectiveTenant(newTenant); } } } } } } } /** * * @param roleToPermissionMappings app specific roles (those that contain at least 1 permission for the app) * @param roleStrings Strings that represent the role from the user store. * @return setIsApplicabbleToTenant = false if the role doesn't apply for this app. */ private SbacRole buildRoleFromParsedStrings(final Map<String, RoleToPermissionMapping> roleToPermissionMappings, final String[] roleStrings) { final SbacRole role = extractRole(roleStrings); //the roleToPermissionsMappings map only contains roles that have at least one permissions for the given application RoleToPermissionMapping appEffectiveRole = roleToPermissionMappings.get(role.getRoleName()); if (appEffectiveRole != null) { role.setIsProtected(appEffectiveRole.isProtected()); role.setPermissions(appEffectiveRole.getSbacPermissions()); role.setIsApplicableToTenant(true); } return role; } protected Multimap<String, SbacRole> buildRolesFromParsedStrings( final Map<String, RoleToPermissionMapping> roleToPermissionMappings, final List<String[]> roleStrings) { final Multimap<String, SbacRole> userRoles = ArrayListMultimap.create(); if (roleStrings != null) { for (String[] roleString : roleStrings) { final SbacRole role = buildRoleFromParsedStrings(roleToPermissionMappings, roleString); //if the user's role doesn't have a permission for this app, don't bother adding to the user object if (role != null) { userRoles.put(role.getRoleName(), role); } } } return userRoles; } /** * Format of the String * 00|blank (due to leading pipe) * 01|Effective EntityName * 02|Role Name * 03|Effective EntityLevel * 04|Client Entity Id * 05|Client Entity Name * 06|GroupOfStates Entity Id * 07|GroupOfStates Entity Name * 08|State Entity Id * 09|StateEntity Name * 10|GroupOfDistricts Entity Id * 11|GroupOfDistricts Entity Name * 12|District Entity Id * 13|District Entity Name * 14|GroupOfInstitutions Entity Id * 15|GroupOfInstitutions Entity Name * 16|Institution Entity Id * 17|Institution Entity Name */ protected SbacRole extractRole(final Object[] attr) { final SbacRole role = new SbacRole(); for (int i = 0; i < attr.length; i++) { String out = "index: [" + i + "] key: "; final String attrElement = (String) attr[i]; if (ENTITY_INDEX_MAP.get(i) != null) { final String entityType = (String) attr[i + 1]; role.addEntityValue(ENTITY_INDEX_MAP.get(i), attrElement, entityType); out += ENTITY_INDEX_MAP.get(i).name() + " -- " + attrElement + ", " + entityType; i++;// we consumed two indexes above (one for entity ID & one for entity name) } else if (NON_ENTITY_INDEX_MAP.get(i) != null) { final String nonEntityValue = NON_ENTITY_INDEX_MAP.get(i); if (ROLE_ENTITY_NAME.equals(nonEntityValue)) { role.setRoleEntityName(attrElement); } else if (ROLE_NAME.equals(nonEntityValue)) { role.setRoleName(attrElement); } else if (ROLE_ENTITY_LEVEL.equals(nonEntityValue)) { role.setRoleEntityLevel(attrElement); } out += NON_ENTITY_INDEX_MAP.get(i) + " -- " + attr[i]; } LOGGER.debug("Parsed saml roles: " + out); } return role; } @Override public <T extends SbacUser> T createUser(final String attrTenantChain, final Map<String, String> inUserAtts, final Class<T> clazz) { final Multimap<String, SbacRole> userRoles = extractUserRoles(attrTenantChain, getKnownRoles()); return buildUser(inUserAtts, clazz, userRoles); } @Override public <T extends SbacUser> T createUser(final String[] attrTenantChain, final Map<String, String> inUserAtts, final Class<T> clazz) { final Multimap<String, SbacRole> userRoles = extractUserRoles(attrTenantChain, getKnownRoles()); return buildUser(inUserAtts, clazz, userRoles); } private <T extends SbacUser> T buildUser(final Map<String, String> inUserAtts, final Class<T> clazz, final Multimap<String, SbacRole> userRoles) { T user = null; try { user = clazz.getDeclaredConstructor(Multimap.class, Map.class).newInstance(userRoles, inUserAtts); } catch (final Throwable t) { throw new RuntimeException("could not create user object: ", t); } if (user == null) { throw new LocalizedException("user has no permissions for component:" + getComponentName()); } return user; } protected abstract String getComponentName(); }