/*******************************************************************************
* 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 static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.http.MediaType.TEXT_HTML_VALUE;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.client.SocialClientUserDetails;
import org.cloudfoundry.identity.uaa.codestore.ExpiringCode;
import org.cloudfoundry.identity.uaa.login.saml.IdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.login.saml.LoginSamlAuthenticationToken;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.providers.ExpiringUsernameAuthenticationToken;
import org.springframework.util.MultiValueMap;
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.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
public class SamlRemoteUaaController extends RemoteUaaController {
private static final Log logger = LogFactory.getLog(SamlRemoteUaaController.class);
public static final String NotANumber = "NaN";
private final ObjectMapper mapper = new ObjectMapper();
public void setIdpDefinitions(List<IdentityProviderDefinition> idpDefinitions) {
this.idpDefinitions = idpDefinitions;
}
private List<IdentityProviderDefinition> idpDefinitions;
@Value("${login.entityID}")
public String entityID = "";
public SamlRemoteUaaController(Environment environment, RestTemplate restTemplate) {
super(environment, restTemplate);
}
@Override
@RequestMapping(value = { "/info", "/login" }, method = RequestMethod.GET, produces = APPLICATION_JSON_VALUE, headers = "Accept=application/json")
public String prompts(HttpServletRequest request, @RequestHeader HttpHeaders headers, Map<String, Object> model,
Principal principal) throws Exception {
// Entity ID to start the discovery
model.put("entityID", entityID);
model.put("idpDefinitions", idpDefinitions);
for (IdentityProviderDefinition idp : idpDefinitions) {
if(idp.isShowSamlLink()) {
model.put("showSamlLoginLinks", true);
break;
}
}
return super.prompts(request, headers, model, principal);
}
@RequestMapping(value = { "/info", "/login" }, method = RequestMethod.GET, produces = TEXT_HTML_VALUE, headers = "Accept=text/html, */*")
public String samlUiPrompts(HttpServletRequest request, @RequestHeader HttpHeaders headers, Map<String, Object> model,
Principal principal) throws Exception {
String logicalViewName = prompts(request, headers, model, principal);
Map<String,Object> prompts = new LinkedHashMap<String, Object>((Map<String, Object>) model.get("prompts"));
prompts.remove("passcode");
// Entity ID to start the discovery
model.put("entityID", entityID);
model.put("idpDefinitions", idpDefinitions);
model.put("prompts", prompts);
return logicalViewName;
}
@Override
protected Map<String, String> getLoginCredentials(Principal principal) {
Map<String, String> login = super.getLoginCredentials(principal);
Collection<? extends GrantedAuthority> authorities = null;
if (principal instanceof LoginSamlAuthenticationToken) {
appendField(login, UaaAuthenticationDetails.ADD_NEW, "true");
LoginSamlAuthenticationToken et = (LoginSamlAuthenticationToken)principal;
appendField(login, Origin.ORIGIN, et.getIdpAlias());
if (et.getPrincipal() instanceof String ) {
appendField(login, "username", et.getPrincipal());
authorities = et.getAuthorities();
} else if (et.getPrincipal() instanceof UaaPrincipal) {
appendField(login, "username", ((UaaPrincipal)et.getPrincipal()).getName());
appendField(login, "email", ((UaaPrincipal)et.getPrincipal()).getEmail());
appendField(login, "external_id", ((UaaPrincipal)et.getPrincipal()).getExternalId());
authorities = et.getAuthorities();
} else {
appendField(login, "username",
((SamlUserDetails) (((ExpiringUsernameAuthenticationToken) principal).getPrincipal()))
.getUsername());
authorities = ((SamlUserDetails) (((ExpiringUsernameAuthenticationToken) principal).getPrincipal()))
.getAuthorities();
}
}
if (principal instanceof Authentication) {
Object details = ((Authentication) principal).getPrincipal();
if (details instanceof SocialClientUserDetails) {
SocialClientUserDetails user = (SocialClientUserDetails) details;
appendField(login, "name", user.getName());
appendField(login, "external_id", user.getExternalId());
appendField(login, "email", user.getEmail());
}
if (((Authentication) principal).getAuthorities() instanceof Collection<?>) {
authorities = ((Authentication) principal).getAuthorities();
}
}
if (authorities != null && authorities.size() > 0) {
Map<String, String> externalGroupMap = new HashMap<String, String>();
int i = 0;
for (GrantedAuthority authority : authorities) {
externalGroupMap.put("externalGroups." + i, authority.getAuthority());
i++;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
mapper.writeValue(baos, externalGroupMap);
appendField(login, "authorities", new String(baos.toByteArray()));
} catch (Throwable t) {
logger.error("Unable to convert external groups to be sent for authorization ", t);
}
}
return login;
}
@RequestMapping(value = { "/passcode" }, method = RequestMethod.GET)
public String generatePasscode(@RequestHeader HttpHeaders headers, Map<String, Object> model, Principal principal)
throws NoSuchAlgorithmException, IOException, JsonMappingException {
String username = null, origin = null;
Map<String, Object> authorizationParameters = null;
if (principal instanceof LoginSamlAuthenticationToken) {
username = principal.getName();
origin = ((LoginSamlAuthenticationToken)principal).getIdpAlias();
//TODO collect authorities here?
} else if (principal instanceof ExpiringUsernameAuthenticationToken) {
username = ((SamlUserDetails) ((ExpiringUsernameAuthenticationToken) principal).getPrincipal()).getUsername();
origin = "login-saml";
Collection<GrantedAuthority> authorities = ((SamlUserDetails) (((ExpiringUsernameAuthenticationToken) principal)
.getPrincipal())).getAuthorities();
if (authorities != null) {
authorizationParameters = new LinkedHashMap<>();
authorizationParameters.put("authorities", authorities);
}
} else {
username = principal.getName();
origin = "passcode";
}
PasscodeInformation pi = new PasscodeInformation(NotANumber, username, null, origin, authorizationParameters);
ResponseEntity<ExpiringCode> response = doGenerateCode(pi);
model.put("passcode", response.getBody().getCode());
return "passcode";
}
private String getClientId(byte[] clientInfoResponse) {
try {
BaseClientDetails clientInfo = mapper.readValue(clientInfoResponse,
new TypeReference<BaseClientDetails>() {
});
return clientInfo.getClientId();
} catch (IOException e) {
logger.warn("Unknown format of tokenRequest: " + new String(clientInfoResponse) + ". Ignoring.");
return null;
}
}
}