org.opentestsystem.shared.security.service.AbsractRolesAndPermissionsService.java Source code

Java tutorial

Introduction

Here is the source code for org.opentestsystem.shared.security.service.AbsractRolesAndPermissionsService.java

Source

/*******************************************************************************
 * 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();

}