org.jasig.ssp.service.impl.UPortalPersonAttributesService.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.ssp.service.impl.UPortalPersonAttributesService.java

Source

/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo 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 the following location:
 *
 *   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.jasig.ssp.service.impl;

import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.jasig.portal.api.sso.SsoPersonLookupService;
import org.jasig.portlet.utils.rest.CrossContextRestApiInvoker;
import org.jasig.portlet.utils.rest.RestResponse;
import org.jasig.ssp.security.PersonAttributesResult;
import org.jasig.ssp.security.exception.UPortalSecurityException;
import org.jasig.ssp.security.uportal.RequestAndResponseAccessFilter;
import org.jasig.ssp.service.ObjectNotFoundException;
import org.jasig.ssp.service.PersonAttributesService;
import org.jasig.ssp.service.reference.ConfigService;
import org.jasig.ssp.util.http.PatchedSimpleCrossContextRestApiInvoker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.context.request.async.WebAsyncUtils;

import javax.portlet.PortletRequest;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class UPortalPersonAttributesService implements PersonAttributesService, ServletContextAware {

    private static final String PARAM_USERNAME = "username";
    private static final String REST_URI_PERSON = "/ssp-platform/api/people/{" + PARAM_USERNAME + "}.json";
    private static final String PARAM_SEARCH_TERMS = "searchTerms%5B%5D";
    private static final String REST_URI_SEARCH_PREFIX = "/ssp-platform/api/people.json?{" + PARAM_SEARCH_TERMS
            + "}";
    private static final String PERSON_KEY = "person";
    private static final String PEOPLE_KEY = "people";
    private static final String USERNAME_KEY = "name";
    private static final String ATTRIBUTES_KEY = "attributes";

    //
    private static final String ATTRIBUTE_SCHOOLID = "schoolId";
    private static final String ATTRIBUTE_FIRSTNAME = "firstName";
    private static final String ATTRIBUTE_LASTNAME = "lastName";
    private static final String ATTRIBUTE_PRIMARYEMAILADDRESS = "primaryEmailAddress";

    private static final Logger LOGGER = LoggerFactory.getLogger(UPortalPersonAttributesService.class);

    /**
     * The default ("SSP_ROLES":"COACH") matches the default permissions setup
     * for coaches, and should be good for many or most adopters. Those who wish
     * to define coaches in SSP in a non-default way -- such as through an AD
     * group -- will need to adjust this setting to query for coaches
     * appropriately via {@link ConfigService}.
     */
    public static final Map<String, String> DEFAULTS_COACHES_QUERY = Collections.singletonMap("SSP_ROLES",
            "SSP_COACH");

    /**
     * {@link ConfigService} entry key for config overriding
     * {@link #DEFAULTS_COACHES_QUERY}
     */
    public static final String COACHES_QUERY_CONFIG = "up_coach_query";

    @Autowired
    private transient RequestAndResponseAccessFilter requestAndResponseAccessFilter;

    @Autowired
    private ConfigService configService;

    private ServletContext servletContext;

    @SuppressWarnings("unchecked")
    protected Map<String, String> getCoachesQuery() {
        return configService.getObjectByNameOrDefault(COACHES_QUERY_CONFIG, Map.class, DEFAULTS_COACHES_QUERY);
    }

    @SuppressWarnings("unchecked")
    @Override
    public PersonAttributesResult getAttributes(final String username) throws ObjectNotFoundException {

        LOGGER.debug("Fetching attributes for user '{}'", username);

        final Map<String, String[]> params = new HashMap<String, String[]>();
        params.put(PARAM_USERNAME, new String[] { username });

        final CrossContextRestApiInvoker rest = new PatchedSimpleCrossContextRestApiInvoker();
        final HttpServletRequest req = requestForCrossContextGet();
        final HttpServletResponse res = responseForCrossContextGet();
        final Object origWebAsyncManager = req.getAttribute(WebAsyncUtils.WEB_ASYNC_MANAGER_ATTRIBUTE);
        req.removeAttribute(WebAsyncUtils.WEB_ASYNC_MANAGER_ATTRIBUTE);

        RestResponse rr;
        try {
            rr = rest.invoke(req, res, REST_URI_PERSON, params);
        } finally {
            req.setAttribute(WebAsyncUtils.WEB_ASYNC_MANAGER_ATTRIBUTE, origWebAsyncManager);
        }

        final ObjectMapper mapper = new ObjectMapper();
        Map<String, Map<String, Map<String, List<String>>>> value = null;
        try {
            value = mapper.readValue(rr.getWriterOutput(), Map.class);
        } catch (final Exception e) {
            final String msg = "Failed to access attributes for the specified person:  " + username;
            throw new UPortalSecurityException(msg, e);
        }

        final Map<String, Map<String, List<String>>> person = value.get(PERSON_KEY);
        if (person == null) {
            // No match...
            throw new ObjectNotFoundException("The specified person is unrecognized", username);
        }
        final Map<String, List<String>> rslt = person.get(ATTRIBUTES_KEY);
        if (rslt == null) {
            throw new ObjectNotFoundException("No attributes are available for the specified person", username);
        }

        LOGGER.debug("Retrieved the following attributes for user {}:  {}", username, rslt.toString());

        return convertAttributes(rslt);
    }

    @Override
    public PersonAttributesResult getAttributes(String username, PortletRequest portletRequest) {
        @SuppressWarnings("unchecked")
        Map<String, String> userInfo = (Map<String, String>) portletRequest.getAttribute(PortletRequest.USER_INFO);
        if (userInfo == null) {
            return new PersonAttributesResult();
        }
        return convertAttributesSingleValued(userInfo);
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<Map<String, Object>> searchForUsers(final Map<String, String> query) {

        LOGGER.debug("Searching for users with query terms '{}'", query);

        // Assemble searchTerms[] in expected way
        final List<String> searchTerms = new ArrayList<String>();
        final Map<String, String[]> params = new HashMap<String, String[]>();
        for (final Map.Entry<String, String> y : query.entrySet()) {
            searchTerms.add(y.getKey());
            params.put(y.getKey(), new String[] { y.getValue() });
        }

        // Build the URL
        final StringBuilder bld = new StringBuilder(REST_URI_SEARCH_PREFIX);
        for (final String key : params.keySet()) {
            bld.append("&{").append(key).append("}");
        }
        final String url = bld.toString();

        LOGGER.debug("Invoking REST enpoint with URL '{}'", url);

        // Add serchTerms[] to the params
        params.put(PARAM_SEARCH_TERMS, searchTerms.toArray(new String[0]));

        final CrossContextRestApiInvoker rest = new PatchedSimpleCrossContextRestApiInvoker();
        final HttpServletRequest req = requestForCrossContextGet();
        final HttpServletResponse res = responseForCrossContextGet();
        final Object origWebAsyncManager = req.getAttribute(WebAsyncUtils.WEB_ASYNC_MANAGER_ATTRIBUTE);
        req.removeAttribute(WebAsyncUtils.WEB_ASYNC_MANAGER_ATTRIBUTE);

        RestResponse rr;
        try {
            rr = rest.invoke(req, res, url, params);
        } finally {
            req.setAttribute(WebAsyncUtils.WEB_ASYNC_MANAGER_ATTRIBUTE, origWebAsyncManager);
        }

        final ObjectMapper mapper = new ObjectMapper();
        Map<String, List<Map<String, Object>>> value = null;
        try {
            value = mapper.readValue(rr.getWriterOutput(), Map.class);
        } catch (final Exception e) {
            final String msg = "Failed to search for users with the specified query:  " + query;
            throw new PersonAttributesSearchException(msg, e);
        }

        final List<Map<String, Object>> rslt = value.get(PEOPLE_KEY);
        if (rslt == null) {
            // Odd... should at least be an empty list
            final String msg = "Search for users returned no list for the specified query:  " + query;
            throw new PersonAttributesSearchException(msg);
        }

        LOGGER.debug("Retrieved the following people for query {}:  {}", query, rslt);

        return rslt;

    }

    @Override
    public List<Map<String, Object>> searchForSsoUsers(String attribute, String value) {
        try {
            return SsoPersonLookupService.IMPL.get().findSsoPerson(attribute, value);
        } catch (RuntimeException e) {
            throw new PersonAttributesSearchException("System failure looking up user having attribute ["
                    + attribute + "] with value [" + value + "]", e);
        }
    }

    /**
     * <strong>NOTE:</strong> This method probably belongs somewhere else. It's
     * at a different level of abstraction verses the other methods on this
     * class. We should probably look to move it when we have more clarity as to
     * where it could land.
     */
    @Override
    public Collection<String> getCoaches() {

        final List<String> rslt = new ArrayList<String>();

        final List<Map<String, Object>> people = searchForUsers(getCoachesQuery());
        for (final Map<String, Object> person : people) {
            rslt.add((String) person.get(USERNAME_KEY));
        }

        return rslt;

    }

    /*
     * Private Stuff
     */

    private HttpServletRequest requestForCrossContextGet() {
        HttpServletRequest currReq = requestAndResponseAccessFilter.getHttpServletRequest();
        if (currReq == null || StringUtils.isBlank(currReq.getMethod())
                || !(currReq.getMethod().equalsIgnoreCase("GET"))) {
            return currReq = new MockHttpServletRequest(servletContext, "GET", "/ssp");
        }
        return currReq;
    }

    private HttpServletResponse responseForCrossContextGet() {
        HttpServletResponse currRes = requestAndResponseAccessFilter.getHttpServletResponse();
        if (currRes == null) {
            return new MockHttpServletResponse();
        }
        return currRes;
    }

    private PersonAttributesResult convertAttributes(final Map<String, List<String>> attr) {

        final PersonAttributesResult person = new PersonAttributesResult();

        if (attr.containsKey(ATTRIBUTE_SCHOOLID)) {
            person.setSchoolId(attr.get(ATTRIBUTE_SCHOOLID).get(0));
        }
        if (attr.containsKey(ATTRIBUTE_FIRSTNAME)) {
            person.setFirstName(attr.get(ATTRIBUTE_FIRSTNAME).get(0));
        }
        if (attr.containsKey(ATTRIBUTE_LASTNAME)) {
            person.setLastName(attr.get(ATTRIBUTE_LASTNAME).get(0));
        }
        if (attr.containsKey(ATTRIBUTE_PRIMARYEMAILADDRESS)) {
            person.setPrimaryEmailAddress(attr.get(ATTRIBUTE_PRIMARYEMAILADDRESS).get(0));
        }

        return person;
    }

    private PersonAttributesResult convertAttributesSingleValued(final Map<String, String> attr) {
        final PersonAttributesResult person = new PersonAttributesResult();

        if (attr.containsKey(ATTRIBUTE_SCHOOLID)) {
            person.setSchoolId(attr.get(ATTRIBUTE_SCHOOLID));
        }
        if (attr.containsKey(ATTRIBUTE_FIRSTNAME)) {
            person.setFirstName(attr.get(ATTRIBUTE_FIRSTNAME));
        }
        if (attr.containsKey(ATTRIBUTE_LASTNAME)) {
            person.setLastName(attr.get(ATTRIBUTE_LASTNAME));
        }
        if (attr.containsKey(ATTRIBUTE_PRIMARYEMAILADDRESS)) {
            person.setPrimaryEmailAddress(attr.get(ATTRIBUTE_PRIMARYEMAILADDRESS));
        }

        return person;
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

}