/** * ============================================================================= * * ORCID (R) Open Source * http://orcid.org * * Copyright (c) 2012-2014 ORCID, Inc. * Licensed under an MIT-Style License (MIT) * http://orcid.org/open-source-license * * This copyright and license information (including a link to the full license) * shall be included in its entirety in all copies or substantial portion of * the software. * * ============================================================================= */ package org.orcid.frontend.web.controllers; import java.io.UnsupportedEncodingException; import java.util.Collections; import java.util.Date; import java.util.Map; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.orcid.core.manager.IdentityProviderManager; import org.orcid.core.manager.InstitutionalSignInManager; import org.orcid.core.manager.UserConnectionManager; import org.orcid.core.utils.JsonUtils; import org.orcid.frontend.web.exception.FeatureDisabledException; import org.orcid.persistence.jpa.entities.UserConnectionStatus; import org.orcid.persistence.jpa.entities.UserconnectionEntity; import org.orcid.pojo.HeaderCheckResult; import org.orcid.pojo.RemoteUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetails; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; /** * * @author Will Simpson * */ @Controller @RequestMapping("/shibboleth") public class ShibbolethController extends BaseController { private static final Logger LOGGER = LoggerFactory.getLogger(ShibbolethController.class); @Resource private UserConnectionManager userConnectionManager; @Resource private AuthenticationManager authenticationManager; @Resource private IdentityProviderManager identityProviderManager; @Resource private InstitutionalSignInManager institutionalSignInManager; @RequestMapping(value = { "/signin" }, method = RequestMethod.GET) public ModelAndView signinHandler(HttpServletRequest request, HttpServletResponse response, @RequestHeader Map<String, String> headers, ModelAndView mav) { LOGGER.info("Headers for shibboleth sign in: {}", headers); checkEnabled(); mav.setViewName("social_link_signin"); String shibIdentityProvider = headers.get(InstitutionalSignInManager.SHIB_IDENTITY_PROVIDER_HEADER); mav.addObject("providerId", shibIdentityProvider); String displayName = institutionalSignInManager.retrieveDisplayName(headers); mav.addObject("accountId", displayName); RemoteUser remoteUser = institutionalSignInManager.retrieveRemoteUser(headers); if (remoteUser == null) { LOGGER.info("Failed federated log in for {}", shibIdentityProvider); identityProviderManager.incrementFailedCount(shibIdentityProvider); mav.addObject("unsupportedInstitution", true); mav.addObject("institutionContactEmail", identityProviderManager.retrieveContactEmailByProviderid(shibIdentityProvider)); return mav; } // Check if the Shibboleth user is already linked to an ORCID account. // If so sign them in automatically. UserconnectionEntity userConnectionEntity = userConnectionManager.findByProviderIdAndProviderUserIdAndIdType(remoteUser.getUserId(), shibIdentityProvider, remoteUser.getIdType()); if (userConnectionEntity != null) { LOGGER.info("Found existing user connection: {}", userConnectionEntity); HeaderCheckResult checkHeadersResult = institutionalSignInManager.checkHeaders(parseOriginalHeaders(userConnectionEntity.getHeadersJson()), headers); if (!checkHeadersResult.isSuccess()) { mav.addObject("headerCheckFailed", true); return mav; } try { // Check if the user has been notified if (!UserConnectionStatus.NOTIFIED.equals(userConnectionEntity.getConnectionSatus())) { try { institutionalSignInManager.sendNotification(userConnectionEntity.getOrcid(), shibIdentityProvider); userConnectionEntity.setConnectionSatus(UserConnectionStatus.NOTIFIED); } catch (UnsupportedEncodingException e) { LOGGER.error("Unable to send institutional sign in notification to user " + userConnectionEntity.getOrcid(), e); } } PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(userConnectionEntity.getOrcid(), remoteUser.getUserId()); token.setDetails(new WebAuthenticationDetails(request)); Authentication authentication = authenticationManager.authenticate(token); SecurityContextHolder.getContext().setAuthentication(authentication); userConnectionEntity.setLastLogin(new Date()); userConnectionManager.update(userConnectionEntity); } catch (AuthenticationException e) { // this should never happen SecurityContextHolder.getContext().setAuthentication(null); LOGGER.warn("User {0} should have been logged-in via Shibboleth, but was unable to due to a problem", remoteUser, e); } return new ModelAndView("redirect:" + calculateRedirectUrl(request, response)); } else { // To avoid confusion, force the user to login to ORCID again mav.addObject("linkType", "shibboleth"); mav.addObject("firstName", (headers.get(InstitutionalSignInManager.GIVEN_NAME_HEADER) == null) ? "" : headers.get(InstitutionalSignInManager.GIVEN_NAME_HEADER)); mav.addObject("lastName", (headers.get(InstitutionalSignInManager.SN_HEADER) == null) ? "" : headers.get(InstitutionalSignInManager.SN_HEADER)); } return mav; } private Map<String, String> parseOriginalHeaders(String originalHeadersJson) { @SuppressWarnings("unchecked") Map<String, String> originalHeaders = originalHeadersJson != null ? JsonUtils.readObjectFromJsonString(originalHeadersJson, Map.class) : Collections.<String, String> emptyMap(); return originalHeaders; } private void checkEnabled() { if (!isShibbolethEnabled()) { throw new FeatureDisabledException(); } } }