/******************************************************************************* * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * * This product includes a number of subcomponents with * separate copyright notices and license terms. Your use of these * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ package org.cloudfoundry.identity.uaa.login; import java.util.Arrays; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.authentication.AccountNotVerifiedException; import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.oauth.UaaOauth2ErrorHandler; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.security.web.savedrequest.SavedRequest; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; /** * An authentication manager that can be used to login to a remote UAA service * with username and password credentials, * without the local server needing to know anything about the user accounts. * The request is handled by the UAA's * RemoteAuhenticationEndpoint and success or failure is determined by the * response code. * * @author Dave Syer * @author Luke Taylor * */ public class RemoteUaaAuthenticationManager implements AuthenticationManager { protected final Log logger = LogFactory.getLog(getClass()); private RestOperations restTemplate = new RestTemplate(); private static String DEFAULT_LOGIN_URL = "http://uaa.cloudfoundry.com/authenticate"; private String loginUrl = DEFAULT_LOGIN_URL; public void setAccountCreationService(AccountCreationService accountCreationService) { this.accountCreationService = accountCreationService; } private AccountCreationService accountCreationService; /** * @param loginUrl the login url to set */ public void setLoginUrl(String loginUrl) { this.loginUrl = loginUrl; } public String getLoginUrl() { return loginUrl; } /** * @param restTemplate a rest template to use */ public void setRestTemplate(RestOperations restTemplate) { this.restTemplate = restTemplate; if (restTemplate instanceof RestTemplate) { initRestTemplateErrorHandler((RestTemplate)restTemplate); } } public RestOperations getRestTemplate() { return restTemplate; } public RemoteUaaAuthenticationManager() { setRestTemplate(new RestTemplate()); // The default java.net client doesn't allow you to handle 4xx responses } private void initRestTemplateErrorHandler(RestTemplate restTemplate) { restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory()); if (restTemplate instanceof OAuth2RestTemplate) { OAuth2RestTemplate oAuth2RestTemplate = (OAuth2RestTemplate)restTemplate; oAuth2RestTemplate.setErrorHandler(new UaaOauth2ErrorHandler(oAuth2RestTemplate.getResource(), HttpStatus.Series.SERVER_ERROR)); } else { restTemplate.setErrorHandler(new DefaultResponseErrorHandler() { @Override protected boolean hasError(HttpStatus statusCode) { return statusCode.series() == HttpStatus.Series.SERVER_ERROR; } }); } } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = (String) authentication.getCredentials(); MultiValueMap<String, Object> parameters = getParameters(username, password); checkAndAddParameter("source", "login", parameters); if (authentication.isAuthenticated() && authentication.getPrincipal() instanceof UaaPrincipal) { UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); checkAndAddParameter(Origin.ORIGIN, principal.getOrigin(), parameters); checkAndAddParameter("email", principal.getEmail(), parameters); checkAndAddParameter(UaaAuthenticationDetails.ADD_NEW, Boolean.TRUE.toString(), parameters); } HttpHeaders headers = getHeaders(); @SuppressWarnings("rawtypes") ResponseEntity<Map> response = restTemplate.exchange(loginUrl, HttpMethod.POST, new HttpEntity<>(parameters, headers), Map.class); if (response.getStatusCode() == HttpStatus.OK || response.getStatusCode() == HttpStatus.CREATED) { Authentication auth = evaluateResponse(authentication, response); if (auth!=null) { logger.info("Successful authentication request for " + authentication.getName()); return auth; } } else if (response.getStatusCode() == HttpStatus.UNAUTHORIZED) { logger.info("Failed authentication request"); throw new BadCredentialsException("Authentication failed"); } else if (response.getStatusCode() == HttpStatus.FORBIDDEN) { if (authentication.getDetails() instanceof SavedRequestAwareAuthenticationDetails) { SavedRequestAwareAuthenticationDetails details = (SavedRequestAwareAuthenticationDetails) authentication.getDetails(); SavedRequest savedRequest = (SavedRequest) details.getSavedRequest(); String clientId = "login"; if (savedRequest != null && savedRequest.getParameterValues("client_id") != null) { clientId = savedRequest.getParameterValues("client_id")[0]; } // Assumes username is the same as email accountCreationService.resendVerificationCode(username, clientId); } logger.info("Account not verified - verification code resent"); throw new AccountNotVerifiedException("Account not verified"); } else if (response.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) { logger.info("Internal error from UAA. Please Check the UAA logs."); } else { logger.error("Unexpected status code " + response.getStatusCode() + " from the UAA." + " Is a compatible version running?"); } throw new RuntimeException("Could not authenticate with remote server"); } protected void checkAndAddParameter(String name, String value, MultiValueMap<String, Object> map) { if (StringUtils.hasText(value)) { map.add(name, value); } } protected Authentication evaluateResponse(Authentication authentication, ResponseEntity<Map> response) { String userFromUaa = (String) response.getBody().get("username"); String userId = (String)response.getBody().get("user_id"); String email = (String)response.getBody().get("email"); String origin = (String)response.getBody().get(Origin.ORIGIN); if (userFromUaa.equalsIgnoreCase(authentication.getName())) { if (StringUtils.hasText(userId) && StringUtils.hasText(origin)) { UaaPrincipal principal = new UaaPrincipal(userId, userFromUaa, email, origin, null); return new UsernamePasswordAuthenticationToken(principal, null, UaaAuthority.USER_AUTHORITIES); } else { return new UsernamePasswordAuthenticationToken(userFromUaa, null, UaaAuthority.USER_AUTHORITIES); } } else { logger.debug("Authentication username mismatch:"+userFromUaa); return null; } } protected MultiValueMap<String, Object> getParameters(String username, String password) { MultiValueMap<String, Object> parameters = new LinkedMaskingMultiValueMap<String, Object>("password"); parameters.set("username", username); parameters.set("password", password); return parameters; } protected HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); return headers; } }