/** * ============================================================================= * * 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.net.URLDecoder; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; import org.orcid.core.constants.OrcidOauth2Constants; import org.orcid.core.manager.ClientDetailsEntityCacheManager; import org.orcid.core.manager.ProfileEntityCacheManager; import org.orcid.core.oauth.service.OrcidAuthorizationEndpoint; import org.orcid.core.oauth.service.OrcidOAuth2RequestValidator; import org.orcid.jaxb.model.clientgroup.ClientType; import org.orcid.jaxb.model.message.ScopePathType; import org.orcid.persistence.jpa.entities.ClientDetailsEntity; import org.orcid.persistence.jpa.entities.ProfileEntity; import org.orcid.persistence.jpa.entities.RecordNameEntity; import org.orcid.pojo.ajaxForm.OauthAuthorizeForm; import org.orcid.pojo.ajaxForm.PojoUtil; import org.orcid.pojo.ajaxForm.RequestInfoForm; import org.orcid.pojo.ajaxForm.ScopeInfoForm; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.NoSuchMessageException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.web.authentication.WebAuthenticationDetails; import org.springframework.web.bind.annotation.ResponseBody; public class OauthControllerBase extends BaseController { private static final Logger LOGGER = LoggerFactory.getLogger(OauthControllerBase.class); protected Pattern clientIdPattern = Pattern.compile("client_id=([^&]*)"); protected Pattern scopesPattern = Pattern.compile("scope=([^&]*)"); private Pattern redirectUriPattern = Pattern.compile("redirect_uri=([^&]*)"); private Pattern responseTypePattern = Pattern.compile("response_type=([^&]*)"); private Pattern stateParamPattern = Pattern.compile("state=([^&]*)"); private Pattern orcidPattern = Pattern.compile("(&|\\?)orcid=([^&]*)"); protected static String PUBLIC_MEMBER_NAME = "PubApp"; protected static String REDIRECT_URI_ERROR = "/oauth/error/redirect-uri-mismatch?client_id={0}"; protected static String REQUEST_INFO_FORM = "requestInfoForm"; @Resource protected ClientDetailsEntityCacheManager clientDetailsEntityCacheManager; @Resource protected OrcidOAuth2RequestValidator orcidOAuth2RequestValidator; @Resource protected ProfileEntityCacheManager profileEntityCacheManager; @Resource protected AuthenticationManager authenticationManager; @Resource protected OrcidAuthorizationEndpoint authorizationEndpoint; public AuthenticationManager getAuthenticationManager() { return authenticationManager; } public void setAuthenticationManager(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } public OrcidAuthorizationEndpoint getAuthorizationEndpoint() { return authorizationEndpoint; } public void setAuthorizationEndpoint(OrcidAuthorizationEndpoint authorizationEndpoint) { this.authorizationEndpoint = authorizationEndpoint; } protected @ResponseBody RequestInfoForm generateRequestInfoForm(HttpServletRequest request) throws UnsupportedEncodingException { String clientId = request.getParameter("client_id"); String scopesString = request.getParameter("scope"); String redirectUri = request.getParameter("redirect_uri"); String responseType = request.getParameter("response_type"); String stateParam = request.getParameter("state"); String email = request.getParameter("email"); String orcid = request.getParameter("orcid"); String givenNames = request.getParameter("given_names"); String familyNames = request.getParameter("family_names"); return generateRequestInfoForm(clientId, scopesString, redirectUri, responseType, stateParam, email, orcid, givenNames, familyNames); } protected @ResponseBody RequestInfoForm generateRequestInfoForm(String requestUrl) throws UnsupportedEncodingException { String clientId = ""; String scopesString = ""; String redirectUri = ""; String responseType = ""; String stateParam = ""; String email = ""; String orcid = ""; String givenNames = ""; String familyNames = ""; if (!PojoUtil.isEmpty(requestUrl)) { Matcher matcher = clientIdPattern.matcher(requestUrl); if (matcher.find()) { clientId = matcher.group(1); } Matcher scopeMatcher = scopesPattern.matcher(requestUrl); if (scopeMatcher.find()) { String scopes = scopeMatcher.group(1); scopesString = URLDecoder.decode(scopes, "UTF-8").trim(); scopesString = scopesString.replaceAll(" +", " "); } Matcher redirectUriMatcher = redirectUriPattern.matcher(requestUrl); if (redirectUriMatcher.find()) { try { redirectUri = URLDecoder.decode(redirectUriMatcher.group(1), "UTF-8").trim(); } catch (UnsupportedEncodingException e) { } } Matcher responseTypeMatcher = responseTypePattern.matcher(requestUrl); if (responseTypeMatcher.find()) { try { responseType = URLDecoder.decode(responseTypeMatcher.group(1), "UTF-8").trim(); } catch (UnsupportedEncodingException e) { } } Matcher stateParamMatcher = stateParamPattern.matcher(requestUrl); if (stateParamMatcher.find()) { try { stateParam = URLDecoder.decode(stateParamMatcher.group(1), "UTF-8").trim(); } catch (UnsupportedEncodingException e) {} } Matcher emailMatcher = RegistrationController.emailPattern.matcher(requestUrl); if (emailMatcher.find()) { String tempEmail = emailMatcher.group(1); try { tempEmail = URLDecoder.decode(tempEmail, "UTF-8").trim(); } catch (UnsupportedEncodingException e) { } if (!PojoUtil.isEmpty(tempEmail)) { email = tempEmail; } } Matcher orcidMatcher = orcidPattern.matcher(requestUrl); if (orcidMatcher.find()) { String tempOrcid = orcidMatcher.group(2); try { tempOrcid = URLDecoder.decode(tempOrcid, "UTF-8").trim(); } catch (UnsupportedEncodingException e) { } if (orcidProfileManager.exists(tempOrcid)) { orcid = tempOrcid; } } Matcher givenNamesMatcher = RegistrationController.givenNamesPattern.matcher(requestUrl); if(givenNamesMatcher.find()) { givenNames = URLDecoder.decode(givenNamesMatcher.group(1), "UTF-8").trim(); } Matcher familyNamesMatcher = RegistrationController.familyNamesPattern.matcher(requestUrl); if(familyNamesMatcher.find()) { familyNames = URLDecoder.decode(familyNamesMatcher.group(1), "UTF-8").trim(); } } return generateRequestInfoForm(clientId, scopesString, redirectUri, responseType, stateParam, email, orcid, givenNames, familyNames); } private RequestInfoForm generateRequestInfoForm(String clientId, String scopesString, String redirectUri, String responseType, String stateParam, String email, String orcid, String givenNames, String familyNames) throws UnsupportedEncodingException { RequestInfoForm infoForm = new RequestInfoForm(); //If the user is logged in String loggedUserOrcid = getEffectiveUserOrcid(); if(!PojoUtil.isEmpty(loggedUserOrcid)) { infoForm.setUserOrcid(loggedUserOrcid); ProfileEntity profile = profileEntityCacheManager.retrieve(loggedUserOrcid); String creditName = ""; RecordNameEntity recordName = profile.getRecordNameEntity(); if(recordName != null) { if (!PojoUtil.isEmpty(profile.getRecordNameEntity().getCreditName())) { creditName = profile.getRecordNameEntity().getCreditName(); } else { creditName = PojoUtil.isEmpty(profile.getRecordNameEntity().getGivenNames()) ? profile.getRecordNameEntity().getFamilyName() : profile.getRecordNameEntity().getGivenNames() + " " + profile.getRecordNameEntity().getFamilyName(); } } if(!PojoUtil.isEmpty(creditName)) { infoForm.setUserName(URLDecoder.decode(creditName, "UTF-8").trim()); } } Set<ScopePathType> scopes = new HashSet<ScopePathType>(); if (!PojoUtil.isEmpty(clientId) && !PojoUtil.isEmpty(scopesString)) { scopesString = URLDecoder.decode(scopesString, "UTF-8").trim(); scopesString = scopesString.replaceAll(" +", " "); scopes = ScopePathType.getScopesFromSpaceSeparatedString(scopesString); } else { throw new InvalidRequestException("Unable to find parameters"); } for (ScopePathType theScope : scopes) { ScopeInfoForm scopeInfoForm = new ScopeInfoForm(); scopeInfoForm.setValue(theScope.value()); scopeInfoForm.setName(theScope.name()); try { scopeInfoForm.setDescription(getMessage(ScopePathType.class.getName() + '.' + theScope.name())); scopeInfoForm.setLongDescription(getMessage(ScopePathType.class.getName() + '.' + theScope.name() + ".longDesc")); } catch(NoSuchMessageException e) { LOGGER.warn("Unable to find key message for scope: " + theScope.name() + " " + theScope.value()); } infoForm.getScopes().add(scopeInfoForm); } // Check if the client has persistent tokens enabled ClientDetailsEntity clientDetails = clientDetailsEntityCacheManager.retrieve(clientId); if (clientDetails.isPersistentTokensEnabled()) { infoForm.setClientHavePersistentTokens(true); } // If client details is ok, continue String clientName = clientDetails.getClientName() == null ? "" : clientDetails.getClientName(); String clientEmailRequestReason = clientDetails.getEmailAccessReason() == null ? "" : clientDetails.getEmailAccessReason(); String clientDescription = clientDetails.getClientDescription() == null ? "" : clientDetails.getClientDescription(); String memberName = ""; // If client type is null it means it is a public client if (ClientType.PUBLIC_CLIENT.equals(clientDetails.getClientType())) { memberName = PUBLIC_MEMBER_NAME; } else if (!PojoUtil.isEmpty(clientDetails.getGroupProfileId())) { ProfileEntity groupProfile = profileEntityCacheManager.retrieve(clientDetails.getGroupProfileId()); if(groupProfile.getRecordNameEntity() != null) { memberName = groupProfile.getRecordNameEntity().getCreditName(); } } // If the group name is empty, use the same as the client // name, since it should be a SSO user if (StringUtils.isBlank(memberName)) { memberName = clientName; } if(!PojoUtil.isEmpty(email) || !PojoUtil.isEmpty(orcid)) { // Check if orcid exists, if so, show login screen if(!PojoUtil.isEmpty(orcid)) { orcid = orcid.trim(); if(orcidProfileManager.exists(orcid)) { infoForm.setUserId(orcid); } } else { // Check if email exists, if so, show login screen if(!PojoUtil.isEmpty(email)) { email = email.trim(); if(emailManager.emailExists(email)) { infoForm.setUserId(email); } } } } infoForm.setUserEmail(email); if(PojoUtil.isEmpty(loggedUserOrcid)) infoForm.setUserOrcid(orcid); infoForm.setUserGivenNames(givenNames); infoForm.setUserFamilyNames(familyNames); infoForm.setClientId(clientId); infoForm.setClientDescription(clientDescription); infoForm.setClientName(clientName); infoForm.setClientEmailRequestReason(clientEmailRequestReason); infoForm.setMemberName(memberName); infoForm.setRedirectUrl(redirectUri); infoForm.setStateParam(stateParam); infoForm.setResponseType(responseType); return infoForm; } protected void fillOauthParams(RequestInfoForm requestInfoForm, Map<String, String> params, Map<String, String> approvalParams, boolean userEnabledPersistentTokens, boolean allowEmailAccess) { if (requestInfoForm.containsEmailReadPrivateScope() && !allowEmailAccess) { requestInfoForm.removeEmailReadPrivateScope(); } if (!PojoUtil.isEmpty(requestInfoForm.getScopesAsString())) { params.put(OrcidOauth2Constants.SCOPE_PARAM, requestInfoForm.getScopesAsString()); } params.put(OrcidOauth2Constants.TOKEN_VERSION, OrcidOauth2Constants.PERSISTENT_TOKEN); params.put(OrcidOauth2Constants.CLIENT_ID_PARAM, requestInfoForm.getClientId()); // Redirect URI if (!PojoUtil.isEmpty(requestInfoForm.getRedirectUrl())) { params.put(OrcidOauth2Constants.REDIRECT_URI_PARAM, requestInfoForm.getRedirectUrl()); } else { params.put(OrcidOauth2Constants.REDIRECT_URI_PARAM, new String()); } // Response type if (!PojoUtil.isEmpty(requestInfoForm.getResponseType())) { params.put(OrcidOauth2Constants.RESPONSE_TYPE_PARAM, requestInfoForm.getResponseType()); } // State param if (!PojoUtil.isEmpty(requestInfoForm.getStateParam())) { params.put(OrcidOauth2Constants.STATE_PARAM, requestInfoForm.getStateParam()); } // Set approval params params.put(OAuth2Utils.USER_OAUTH_APPROVAL, "true"); approvalParams.put(OAuth2Utils.USER_OAUTH_APPROVAL, "true"); // Set persistent token flag if(requestInfoForm.getClientHavePersistentTokens() && userEnabledPersistentTokens) { params.put(OrcidOauth2Constants.GRANT_PERSISTENT_TOKEN, "true"); } else { params.put(OrcidOauth2Constants.GRANT_PERSISTENT_TOKEN, "false"); } } /** * Builds the redirect uri string to use when the user deny the request * * @param redirectUri * Redirect uri * @return the redirect uri string with the deny params */ protected String buildDenyRedirectUri(String redirectUri, String stateParam) { if (!PojoUtil.isEmpty(redirectUri)) { if (redirectUri.contains("?")) { redirectUri = redirectUri.concat("&error=access_denied&error_description=User denied access"); } else { redirectUri = redirectUri.concat("?error=access_denied&error_description=User denied access"); } } if (!PojoUtil.isEmpty(stateParam)) redirectUri += "&state=" + stateParam; return redirectUri; } protected void copy(Map<String, String[]> savedParams, Map<String, String> params) { if (savedParams != null && !savedParams.isEmpty()) { for (String key : savedParams.keySet()) { String[] values = savedParams.get(key); if (values != null && values.length > 0) params.put(key, values[0]); } } } /***************************** * Authenticate user methods ****************************/ protected Authentication authenticateUser(HttpServletRequest request, OauthAuthorizeForm form) throws AuthenticationException { UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(form.getUserName().getValue(), form.getPassword().getValue()); token.setDetails(new WebAuthenticationDetails(request)); return authenticateUser(token); } protected Authentication authenticateUser(HttpServletRequest request, String email, String password) { UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(email, password); token.setDetails(new WebAuthenticationDetails(request)); return authenticateUser(token); } protected Authentication authenticateUser(UsernamePasswordAuthenticationToken token) { Authentication authentication = authenticationManager.authenticate(token); SecurityContextHolder.getContext().setAuthentication(authentication); return authentication; } /** * Checks if the client has the persistent tokens enabled * * @return true if the persistent tokens are enabled for that client * @throws IllegalArgumentException */ protected boolean hasPersistenTokensEnabled(String clientId) throws IllegalArgumentException { ClientDetailsEntity clientDetails = clientDetailsEntityCacheManager.retrieve(clientId); if (clientDetails == null) throw new IllegalArgumentException(getMessage("web.orcid.oauth_invalid_client.exception")); return clientDetails.isPersistentTokensEnabled(); } }