/*******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2016] 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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication;
import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal;
import org.cloudfoundry.identity.uaa.codestore.ExpiringCode;
import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore;
import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants;
import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.provider.AbstractXOAuthIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.provider.IdentityProvider;
import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning;
import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.provider.oauth.XOAuthProviderConfigurator;
import org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken;
import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator;
import org.cloudfoundry.identity.uaa.provider.saml.SamlRedirectUtils;
import org.cloudfoundry.identity.uaa.util.ColorHash;
import org.cloudfoundry.identity.uaa.util.DomainFilter;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.util.MapCollector;
import org.cloudfoundry.identity.uaa.util.UaaStringUtils;
import org.cloudfoundry.identity.uaa.util.UaaUrlUtils;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.cloudfoundry.identity.uaa.zone.Links;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
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.bind.annotation.ResponseStatus;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptyMap;
import static java.util.Objects.isNull;
import static java.util.Optional.ofNullable;
import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA;
import static org.cloudfoundry.identity.uaa.util.UaaUrlUtils.addSubdomainToUrl;
import static org.cloudfoundry.identity.uaa.web.UaaSavedRequestAwareAuthenticationSuccessHandler.SAVED_REQUEST_SESSION_ATTRIBUTE;
import static org.springframework.util.StringUtils.hasText;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
/**
* Controller that sends login info (e.g. prompts) to clients wishing to
* authenticate.
*
* @author Dave Syer
*/
@Controller
public class LoginInfoEndpoint {
private static Log logger = LogFactory.getLog(LoginInfoEndpoint.class);
public static final String NotANumber = OriginKeys.NotANumber;
public static final String CREATE_ACCOUNT_LINK = "createAccountLink";
public static final String FORGOT_PASSWORD_LINK = "forgotPasswordLink";
public static final String LINK_CREATE_ACCOUNT_SHOW = "linkCreateAccountShow";
public static final String FIELD_USERNAME_SHOW = "fieldUsernameShow";
public static final List<String> UI_ONLY_ATTRIBUTES =
Collections.unmodifiableList(
Arrays.asList(CREATE_ACCOUNT_LINK, FORGOT_PASSWORD_LINK, LINK_CREATE_ACCOUNT_SHOW, FIELD_USERNAME_SHOW)
);
public static final String PASSCODE = "passcode";
public static final String SHOW_LOGIN_LINKS = "showLoginLinks";
public static final String LINKS = "links";
public static final String ZONE_NAME = "zone_name";
public static final String ENTITY_ID = "entityID";
public static final String IDP_DEFINITIONS = "idpDefinitions";
public static final String OAUTH_LINKS = "oauthLinks";
private Properties gitProperties = new Properties();
private Properties buildProperties = new Properties();
private String baseUrl;
private String externalLoginUrl;
private String samlSPBaseUrl;
private String uaaHost;
private SamlIdentityProviderConfigurator idpDefinitions;
private long codeExpirationMillis = 5 * 60 * 1000;
private AuthenticationManager authenticationManager;
private ExpiringCodeStore expiringCodeStore;
private ClientDetailsService clientDetailsService;
private IdentityProviderProvisioning providerProvisioning;
private MapCollector<IdentityProvider, String, AbstractXOAuthIdentityProviderDefinition> idpsMapCollector =
new MapCollector<>(
idp -> idp.getOriginKey(),
idp -> (AbstractXOAuthIdentityProviderDefinition) idp.getConfig()
);
private XOAuthProviderConfigurator xoAuthProviderConfigurator;
private Links globalLinks = new Links().setSelfService(new Links.SelfService().setPasswd(null).setSignup(null));
public void setGlobalLinks(Links globalLinks) {
this.globalLinks = globalLinks;
}
public LoginInfoEndpoint setXoAuthProviderConfigurator(XOAuthProviderConfigurator xoAuthProviderConfigurator) {
this.xoAuthProviderConfigurator = xoAuthProviderConfigurator;
return this;
}
public void setExpiringCodeStore(ExpiringCodeStore expiringCodeStore) {
this.expiringCodeStore = expiringCodeStore;
}
public long getCodeExpirationMillis() {
return codeExpirationMillis;
}
public void setCodeExpirationMillis(long codeExpirationMillis) {
this.codeExpirationMillis = codeExpirationMillis;
}
public void setIdpDefinitions(SamlIdentityProviderConfigurator idpDefinitions) {
this.idpDefinitions = idpDefinitions;
}
public AuthenticationManager getAuthenticationManager() {
return authenticationManager;
}
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
private String entityID = "";
public void setEntityID(String entityID) {
this.entityID = entityID;
}
public LoginInfoEndpoint() {
try {
gitProperties = PropertiesLoaderUtils.loadAllProperties("git.properties");
} catch (IOException e) {
// Ignore
}
try {
buildProperties = PropertiesLoaderUtils.loadAllProperties("build.properties");
} catch (IOException e) {
// Ignore
}
}
@RequestMapping(value = {"/login"}, headers = "Accept=application/json")
public String loginForJson(Model model, Principal principal, HttpServletRequest request) {
return login(model, principal, Collections.emptyList(), true, request);
}
@RequestMapping(value = {"/info"}, headers = "Accept=application/json")
public String infoForJson(Model model, Principal principal, HttpServletRequest request) {
return login(model, principal, Collections.emptyList(), true, request);
}
@RequestMapping(value = {"/info"}, headers = "Accept=text/html, */*")
public String infoForHtml(Model model, Principal principal, HttpServletRequest request) {
return login(model, principal, Arrays.asList(PASSCODE), false, request);
}
static class SavedAccountOptionModel extends SavedAccountOption {
public int red, green, blue;
public void assignColors(Color color) {
red = color.getRed();
blue = color.getBlue();
green = color.getGreen();
}
}
@RequestMapping(value = {"/login"}, headers = "Accept=text/html, */*")
public String loginForHtml(Model model, Principal principal, HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
List<SavedAccountOptionModel> savedAccounts = getSavedAccounts(cookies, SavedAccountOptionModel.class);
savedAccounts.forEach(account -> {
Color color = ColorHash.getColor(account.getUserId());
account.assignColors(color);
});
model.addAttribute("savedAccounts", savedAccounts);
return login(model, principal, Arrays.asList(PASSCODE), false, request);
}
private static <T extends SavedAccountOption> List<T> getSavedAccounts(Cookie[] cookies, Class<T> clazz) {
return Arrays.asList(ofNullable(cookies).orElse(new Cookie[]{}))
.stream()
.filter(c -> c.getName().startsWith("Saved-Account"))
.map(c -> JsonUtils.readValue(decodeCookieValue(c.getValue()), clazz))
.collect(Collectors.toList());
}
private static String decodeCookieValue(String inValue) {
String out = null;
try {
out = URLDecoder.decode(inValue, UTF_8.name());
} catch (Exception e) {
logger.debug("URLDecoder.decode failed for " + inValue, e);
return "";
}
return out;
}
@RequestMapping(value = {"/invalid_request"})
public String invalidRequest(HttpServletRequest request) {
return "invalid_request";
}
protected String getZonifiedEntityId() {
return SamlRedirectUtils.getZonifiedEntityId(entityID);
}
private String login(Model model, Principal principal, List<String> excludedPrompts, boolean jsonResponse, HttpServletRequest request) {
if(principal instanceof UaaAuthentication && ((UaaAuthentication)principal).isAuthenticated()) { return "redirect:/home"; }
HttpSession session = request != null ? request.getSession(false) : null;
List<String> allowedIdps = null;
String clientName = null;
Map<String,Object> clientInfo;
if((clientInfo = getClientInfo(session)) != null) {
allowedIdps = (List<String>) clientInfo.get(ClientConstants.ALLOWED_PROVIDERS);
clientName = (String) clientInfo.get(ClientConstants.CLIENT_NAME);
}
Map<String, SamlIdentityProviderDefinition> samlIdps = getSamlIdentityProviderDefinitions(allowedIdps);
Map<String, AbstractXOAuthIdentityProviderDefinition> oauthIdentityProviderDefinitions = getOauthIdentityProviderDefinitions(allowedIdps);
Map<String, AbstractIdentityProviderDefinition> combinedIdps = new HashMap<>();
combinedIdps.putAll(samlIdps);
combinedIdps.putAll(oauthIdentityProviderDefinitions);
boolean fieldUsernameShow = true;
boolean returnLoginPrompts = true;
IdentityProvider ldapIdentityProvider = null;
try {
ldapIdentityProvider = providerProvisioning.retrieveByOrigin(OriginKeys.LDAP, IdentityZoneHolder.get().getId());
} catch (EmptyResultDataAccessException e) {
}
IdentityProvider uaaIdentityProvider = providerProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId());
//ldap and uaa disabled
if (!uaaIdentityProvider.isActive()) {
if (ldapIdentityProvider == null || !ldapIdentityProvider.isActive()) {
fieldUsernameShow = false;
returnLoginPrompts = false;
}
}
//ldap or uaa not part of allowedIdps
if (allowedIdps != null) {
if ((!allowedIdps.contains(OriginKeys.LDAP) &&
!allowedIdps.contains(OriginKeys.UAA) &&
!allowedIdps.contains(OriginKeys.KEYSTONE))) {
fieldUsernameShow = false;
}
}
Map.Entry<String, AbstractIdentityProviderDefinition> idpForRedirect = null;
Optional<String> loginHintParam =
ofNullable(session)
.flatMap(s -> ofNullable((SavedRequest) s.getAttribute(SAVED_REQUEST_SESSION_ATTRIBUTE)))
.flatMap(sr -> ofNullable(sr.getParameterValues("login_hint")))
.flatMap(lhValues -> Arrays.asList(lhValues).stream().findFirst());
if(loginHintParam.isPresent()) {
String loginHint = loginHintParam.get();
List<Map.Entry<String, AbstractIdentityProviderDefinition>> matchingIdps = combinedIdps.entrySet().stream().filter(idp -> idp.getValue().getEmailDomain().contains(loginHint)).collect(Collectors.toList());
if(matchingIdps.size() > 1) {
throw new IllegalStateException("There is a misconfiguration with the identity provider(s). Please contact your system administrator.");
}
if(matchingIdps.size() == 1) {
idpForRedirect = matchingIdps.get(0);
}
}
if(idpForRedirect == null && !jsonResponse && !fieldUsernameShow && combinedIdps.size() == 1) {
idpForRedirect = combinedIdps.entrySet().stream().findAny().get();
}
String externalRedirect;
if (idpForRedirect != null && (externalRedirect = redirectToExternalProvider(idpForRedirect.getValue(), idpForRedirect.getKey(), request)) != null) {
return externalRedirect;
}
boolean linkCreateAccountShow = fieldUsernameShow;
if (fieldUsernameShow && (allowedIdps != null && !allowedIdps.contains(OriginKeys.UAA))) {
linkCreateAccountShow = false;
}
String zonifiedEntityID = getZonifiedEntityId();
Map links = getLinksInfo();
if (jsonResponse) {
for (String attribute : UI_ONLY_ATTRIBUTES) {
links.remove(attribute);
}
Map<String, String> idpDefinitionsForJson = new HashMap<>();
if (samlIdps != null) {
for (SamlIdentityProviderDefinition def : samlIdps.values()) {
String idpUrl = links.get("login") +
String.format("/saml/discovery?returnIDParam=idp&entityID=%s&idp=%s&isPassive=true",
zonifiedEntityID,
def.getIdpEntityAlias());
idpDefinitionsForJson.put(def.getIdpEntityAlias(), idpUrl);
}
model.addAttribute(IDP_DEFINITIONS, idpDefinitionsForJson);
}
} else {
model.addAttribute(LINK_CREATE_ACCOUNT_SHOW, linkCreateAccountShow);
model.addAttribute(FIELD_USERNAME_SHOW, fieldUsernameShow);
model.addAttribute(IDP_DEFINITIONS, samlIdps.values());
Map<String, String> oauthLinks = new HashMap<>();
ofNullable(oauthIdentityProviderDefinitions).orElse(emptyMap()).entrySet().stream()
.filter(e -> e.getValue().isShowLinkText() == true)
.forEach(e ->
oauthLinks.put(
xoAuthProviderConfigurator.getCompleteAuthorizationURI(
e.getKey(),
UaaUrlUtils.getBaseURL(request),
e.getValue()),
e.getValue().getLinkText()
)
);
model.addAttribute(OAUTH_LINKS, oauthLinks);
model.addAttribute("clientName", clientName);
}
model.addAttribute(LINKS, links);
setCommitInfo(model);
model.addAttribute(ZONE_NAME, IdentityZoneHolder.get().getName());
// Entity ID to start the discovery
model.addAttribute(ENTITY_ID, zonifiedEntityID);
boolean noIdpsPresent = true;
for (SamlIdentityProviderDefinition idp : samlIdps.values()) {
if (idp.isShowSamlLink()) {
model.addAttribute(SHOW_LOGIN_LINKS, true);
noIdpsPresent = false;
break;
}
}
for (AbstractXOAuthIdentityProviderDefinition oauthIdp : oauthIdentityProviderDefinitions.values()) {
if (oauthIdp.isShowLinkText()) {
model.addAttribute(SHOW_LOGIN_LINKS, true);
noIdpsPresent = false;
break;
}
}
//make the list writeable
excludedPrompts = new LinkedList<>(excludedPrompts);
if (noIdpsPresent) {
excludedPrompts.add(PASSCODE);
}
if(!returnLoginPrompts){
excludedPrompts.add("username");
excludedPrompts.add("password");
}
populatePrompts(model, excludedPrompts, jsonResponse);
if (principal == null) {
boolean accountChooserNeeded = IdentityZoneHolder.get().getConfig().isIdpDiscoveryEnabled()
&& IdentityZoneHolder.get().getConfig().isAccountChooserEnabled()
&& request != null && !(Boolean.parseBoolean(request.getParameter("otherAccountSignIn")) || getSavedAccounts(request.getCookies(), SavedAccountOption.class).isEmpty());
if(accountChooserNeeded) {
return "idp_discovery/account_chooser";
}
boolean discoveryNeeded = IdentityZoneHolder.get().getConfig().isIdpDiscoveryEnabled()
&& (request == null || !Boolean.parseBoolean(request.getParameter("discoveryPerformed")));
if(discoveryNeeded) {
return "idp_discovery/email";
}
return "login";
}
return "home";
}
@RequestMapping(value = {"/delete_saved_account"})
public String deleteSavedAccount(HttpServletRequest request, HttpServletResponse response, String userId) {
Cookie cookie = new Cookie("Saved-Account-" + userId, "");
cookie.setMaxAge(0);
cookie.setPath(request.getContextPath() + "/login");
response.addCookie(cookie);
return "redirect:/login";
}
private String redirectToExternalProvider(AbstractIdentityProviderDefinition idpForRedirect, String alias, HttpServletRequest request) {
if(idpForRedirect != null) {
if (idpForRedirect instanceof SamlIdentityProviderDefinition) {
String url = SamlRedirectUtils.getIdpRedirectUrl((SamlIdentityProviderDefinition) idpForRedirect, entityID);
return "redirect:/" + url;
} else if (idpForRedirect instanceof AbstractXOAuthIdentityProviderDefinition) {
try {
String redirectUrl = getRedirectUrlForXOAuthIDP(request, alias, (AbstractXOAuthIdentityProviderDefinition) idpForRedirect);
return "redirect:" + redirectUrl;
} catch (UnsupportedEncodingException e) {
}
}
}
return null;
}
private String getRedirectUrlForXOAuthIDP(HttpServletRequest request, String alias, AbstractXOAuthIdentityProviderDefinition definition) throws UnsupportedEncodingException {
return xoAuthProviderConfigurator.getCompleteAuthorizationURI(alias, UaaUrlUtils.getBaseURL(request), definition);
}
protected Map<String, SamlIdentityProviderDefinition> getSamlIdentityProviderDefinitions(List<String> allowedIdps) {
List<SamlIdentityProviderDefinition> filteredIdps = idpDefinitions.getIdentityProviderDefinitions(allowedIdps, IdentityZoneHolder.get());
return filteredIdps.stream().collect(new MapCollector<>(SamlIdentityProviderDefinition::getUniqueAlias, idp -> idp));
}
protected Map<String, AbstractXOAuthIdentityProviderDefinition> getOauthIdentityProviderDefinitions(List<String> allowedIdps) {
List<IdentityProvider> identityProviders =
xoAuthProviderConfigurator.retrieveAll(true, IdentityZoneHolder.get().getId());
Map<String, AbstractXOAuthIdentityProviderDefinition> identityProviderDefinitions = identityProviders.stream()
.filter(p -> allowedIdps==null || allowedIdps.contains(p.getOriginKey()))
.collect(idpsMapCollector);
return identityProviderDefinitions;
}
protected boolean hasSavedOauthAuthorizeRequest(HttpSession session) {
if (session == null || session.getAttribute(SAVED_REQUEST_SESSION_ATTRIBUTE) == null) {
return false;
}
SavedRequest savedRequest = (SavedRequest) session.getAttribute(SAVED_REQUEST_SESSION_ATTRIBUTE);
String redirectUrl = savedRequest.getRedirectUrl();
String[] client_ids = savedRequest.getParameterValues("client_id");
if (redirectUrl != null && redirectUrl.contains("/oauth/authorize") && client_ids != null && client_ids.length != 0) {
return true;
}
return false;
}
public Map<String, Object> getClientInfo(HttpSession session) {
if (!hasSavedOauthAuthorizeRequest(session)) {
return null;
}
SavedRequest savedRequest = (SavedRequest) session.getAttribute(SAVED_REQUEST_SESSION_ATTRIBUTE);
String[] client_ids = savedRequest.getParameterValues("client_id");
try {
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(client_ids[0]);
return clientDetails.getAdditionalInformation();
} catch (NoSuchClientException x) {
return null;
}
}
private void setCommitInfo(Model model) {
model.addAttribute("commit_id", gitProperties.getProperty("git.commit.id.abbrev", "UNKNOWN"));
model.addAttribute(
"timestamp",
gitProperties.getProperty("git.commit.time",
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date())));
model.addAttribute("app", UaaStringUtils.getMapFromProperties(buildProperties, "build."));
}
public void populatePrompts(Model model, List<String> exclude, boolean jsonResponse) {
IdentityZoneConfiguration zoneConfiguration = IdentityZoneHolder.get().getConfig();
if (isNull(zoneConfiguration)) {
zoneConfiguration = new IdentityZoneConfiguration();
}
Map<String, String[]> map = new LinkedHashMap<>();
for (Prompt prompt : zoneConfiguration.getPrompts()) {
if (!exclude.contains(prompt.getName())) {
String[] details = prompt.getDetails();
if (PASSCODE.equals(prompt.getName()) && !IdentityZoneHolder.isUaa()) {
String urlInPasscode = extractUrlFromString(prompt.getDetails()[1]);
if (hasText(urlInPasscode)) {
String[] newDetails = new String[details.length];
System.arraycopy(details, 0, newDetails, 0, details.length);
newDetails[1] = newDetails[1].replace(urlInPasscode, addSubdomainToUrl(urlInPasscode));
details = newDetails;
}
}
map.put(prompt.getName(), details);
}
}
model.addAttribute("prompts", map);
}
//http://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string
// Pattern for recognizing a URL, based off RFC 3986
private static final Pattern urlPattern = Pattern.compile(
"((https?|ftp|gopher|telnet|file):((//)|(\\\\))+[\\w\\d:#@%/;$()~_?\\+-=\\\\\\.&]*)",
Pattern.CASE_INSENSITIVE );
public String extractUrlFromString(String s) {
Matcher matcher = urlPattern.matcher(s);
if (matcher.find()) {
int matchStart = matcher.start(0);
int matchEnd = matcher.end(0);
// now you have the offsets of a URL match
return s.substring(matchStart, matchEnd);
}
return null;
}
@RequestMapping(value = "/login/idp_discovery", method = RequestMethod.POST)
public String discoverIdentityProvider(@RequestParam String email, Model model, HttpSession session, HttpServletRequest request) {
ClientDetails clientDetails = null;
if (hasSavedOauthAuthorizeRequest(session)) {
SavedRequest savedRequest = (SavedRequest) session.getAttribute(SAVED_REQUEST_SESSION_ATTRIBUTE);
String[] client_ids = savedRequest.getParameterValues("client_id");
try {
clientDetails = clientDetailsService.loadClientByClientId(client_ids[0]);
} catch (NoSuchClientException e) {
}
}
List<IdentityProvider> identityProviders = DomainFilter.filter(providerProvisioning.retrieveActive(IdentityZoneHolder.get().getId()), clientDetails, email);
if (identityProviders.size() == 1) {
IdentityProvider matchedIdp = identityProviders.get(0);
if (matchedIdp.getType().equals(UAA)) {
return goToPasswordPage(email, model);
} else {
String redirectUrl;
if ((redirectUrl = redirectToExternalProvider(matchedIdp.getConfig(), matchedIdp.getOriginKey(), request)) != null) {
return redirectUrl;
}
}
}
return "redirect:/login?discoveryPerformed=true";
}
private String goToPasswordPage(String email, Model model) {
model.addAttribute(ZONE_NAME, IdentityZoneHolder.get().getName());
model.addAttribute("email", email);
String forgotPasswordLink;
if ((forgotPasswordLink = getSelfServiceLinks().get(FORGOT_PASSWORD_LINK)) != null) {
model.addAttribute(FORGOT_PASSWORD_LINK, forgotPasswordLink);
}
return "idp_discovery/password";
}
@RequestMapping(value = "/autologin", method = RequestMethod.POST)
@ResponseBody
public AutologinResponse generateAutologinCode(@RequestBody AutologinRequest request,
@RequestHeader(value = "Authorization", required = false) String auth) throws Exception {
if (auth == null || (!auth.startsWith("Basic"))) {
throw new BadCredentialsException("No basic authorization client information in request");
}
String username = request.getUsername();
if (username == null) {
throw new BadCredentialsException("No username in request");
}
Authentication userAuthentication = null;
if (authenticationManager != null) {
String password = request.getPassword();
if (!hasText(password)) {
throw new BadCredentialsException("No password in request");
}
userAuthentication = authenticationManager.authenticate(new AuthzAuthenticationRequest(username, password, null));
}
String base64Credentials = auth.substring("Basic".length()).trim();
String credentials = new String(new Base64().decode(base64Credentials.getBytes()), UTF_8.name());
// credentials = username:password
final String[] values = credentials.split(":", 2);
if (values == null || values.length == 0) {
throw new BadCredentialsException("Invalid authorization header.");
}
String clientId = values[0];
Map<String, String> codeData = new HashMap<>();
codeData.put("client_id", clientId);
codeData.put("username", username);
if (userAuthentication!=null && userAuthentication.getPrincipal() instanceof UaaPrincipal) {
UaaPrincipal p = (UaaPrincipal)userAuthentication.getPrincipal();
if (p!=null) {
codeData.put("user_id", p.getId());
codeData.put(OriginKeys.ORIGIN, p.getOrigin());
}
}
ExpiringCode expiringCode = expiringCodeStore.generateCode(JsonUtils.writeValueAsString(codeData), new Timestamp(System.currentTimeMillis() + 5 * 60 * 1000), ExpiringCodeType.AUTOLOGIN.name());
return new AutologinResponse(expiringCode.getCode());
}
@RequestMapping(value = "/autologin", method = GET)
public String performAutologin(HttpSession session) {
String redirectLocation = "home";
SavedRequest savedRequest = (SavedRequest) session.getAttribute(SAVED_REQUEST_SESSION_ATTRIBUTE);
if (savedRequest != null && savedRequest.getRedirectUrl() != null) {
redirectLocation = savedRequest.getRedirectUrl();
}
return "redirect:" + redirectLocation;
}
@RequestMapping(value = "/login_implicit", method = GET)
public String captureImplicitValuesUsingJavascript() {
return "login_implicit";
}
@RequestMapping(value = "/login/callback/{origin}")
public String handleXOAuthCallback(HttpSession session) {
String redirectLocation = "/home";
SavedRequest savedRequest = (SavedRequest) session.getAttribute(SAVED_REQUEST_SESSION_ATTRIBUTE);
if (savedRequest != null && savedRequest.getRedirectUrl() != null) {
redirectLocation = savedRequest.getRedirectUrl();
}
return "redirect:" + redirectLocation;
}
@RequestMapping(value = { "/passcode" }, method = GET)
public String generatePasscode(Map<String, Object> model, Principal principal)
throws NoSuchAlgorithmException, IOException {
String username, origin, userId = NotANumber;
Map<String, Object> authorizationParameters = null;
if (principal instanceof UaaPrincipal) {
UaaPrincipal uaaPrincipal = (UaaPrincipal)principal;
username = uaaPrincipal.getName();
origin = uaaPrincipal.getOrigin();
userId = uaaPrincipal.getId();
} else if (principal instanceof UaaAuthentication) {
UaaPrincipal uaaPrincipal = ((UaaAuthentication)principal).getPrincipal();
username = uaaPrincipal.getName();
origin = uaaPrincipal.getOrigin();
userId = uaaPrincipal.getId();
} else if (principal instanceof LoginSamlAuthenticationToken) {
username = principal.getName();
origin = ((LoginSamlAuthenticationToken) principal).getUaaPrincipal().getOrigin();
userId = ((LoginSamlAuthenticationToken) principal).getUaaPrincipal().getId();
} else if (principal instanceof Authentication && ((Authentication)principal).getPrincipal() instanceof UaaPrincipal) {
UaaPrincipal uaaPrincipal = (UaaPrincipal)((Authentication)principal).getPrincipal();
username = uaaPrincipal.getName();
origin = uaaPrincipal.getOrigin();
userId = uaaPrincipal.getId();
} else {
throw new UnknownPrincipalException();
}
PasscodeInformation pi = new PasscodeInformation(userId, username, null, origin, authorizationParameters);
String intent = ExpiringCodeType.PASSCODE + " " + pi.getUserId();
expiringCodeStore.expireByIntent(intent);
ExpiringCode code = expiringCodeStore.generateCode(
JsonUtils.writeValueAsString(pi),
new Timestamp(System.currentTimeMillis() + (getCodeExpirationMillis())),
intent);
model.put(PASSCODE, code.getCode());
return PASSCODE;
}
protected Map<String, ?> getLinksInfo() {
Map<String, Object> model = new HashMap<>();
model.put(OriginKeys.UAA, addSubdomainToUrl(getUaaBaseUrl()));
if (getBaseUrl().contains("localhost:")) {
model.put("login", addSubdomainToUrl(getUaaBaseUrl()));
} else if (hasText(getExternalLoginUrl())){
model.put("login", getExternalLoginUrl());
} else {
model.put("login", addSubdomainToUrl(getUaaBaseUrl().replaceAll(OriginKeys.UAA, "login")));
}
model.putAll(getSelfServiceLinks());
return model;
}
protected Map<String,String> getSelfServiceLinks() {
Map<String, String> selfServiceLinks = new HashMap<>();
IdentityZone zone = IdentityZoneHolder.get();
IdentityProvider<UaaIdentityProviderDefinition> uaaIdp = providerProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId());
boolean disableInternalUserManagement = (uaaIdp.getConfig()!=null) ? uaaIdp.getConfig().isDisableInternalUserManagement() : false;
boolean selfServiceLinksEnabled = (zone.getConfig()!=null) ? zone.getConfig().getLinks().getSelfService().isSelfServiceLinksEnabled() : true;
final String defaultSignup = "/create_account";
final String defaultPasswd = "/forgot_password";
Links.SelfService service = zone.getConfig()!=null ? zone.getConfig().getLinks().getSelfService() : null;
String signup = UaaStringUtils.nonNull(
service!=null ? service.getSignup() : null,
globalLinks.getSelfService().getSignup(),
defaultSignup);
String passwd = UaaStringUtils.nonNull(
service!=null ? service.getPasswd() : null,
globalLinks.getSelfService().getPasswd(),
defaultPasswd);
if (selfServiceLinksEnabled && !disableInternalUserManagement) {
if (hasText(signup)) {
signup = UaaStringUtils.replaceZoneVariables(signup, IdentityZoneHolder.get());
selfServiceLinks.put(CREATE_ACCOUNT_LINK, signup);
selfServiceLinks.put("register", signup);
}
if (hasText(passwd)) {
passwd = UaaStringUtils.replaceZoneVariables(passwd, IdentityZoneHolder.get());
selfServiceLinks.put(FORGOT_PASSWORD_LINK, passwd);
selfServiceLinks.put("passwd", passwd);
}
}
return selfServiceLinks;
}
public void setUaaBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
try {
URI uri = new URI(baseUrl);
setUaaHost(uri.getHost());
if (uri.getPort()!=443 && uri.getPort()!=80 && uri.getPort()>0) {
//append non standard ports to the hostname
setUaaHost(getUaaHost()+":"+uri.getPort());
}
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Could not extract host from URI: " + baseUrl);
}
}
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
protected String getUaaBaseUrl() {
return baseUrl;
}
public String getUaaHost() {
return uaaHost;
}
public void setUaaHost(String uaaHost) {
this.uaaHost = uaaHost;
}
public void setExternalLoginUrl(String baseUrl) {
this.externalLoginUrl = baseUrl;
}
public String getExternalLoginUrl() {
return externalLoginUrl;
}
public String getSamlSPBaseUrl() {
return samlSPBaseUrl;
}
public void setSamlSPBaseUrl(String samlSPBaseUrl) {
this.samlSPBaseUrl = samlSPBaseUrl;
}
protected String extractPath(HttpServletRequest request) {
String query = request.getQueryString();
try {
query = query == null ? "" : "?" + URLDecoder.decode(query, UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Cannot decode query string: " + query);
}
String path = request.getRequestURI() + query;
String context = request.getContextPath();
path = path.substring(context.length());
if (path.startsWith("/")) {
// In the root context we have to remove this as well
path = path.substring(1);
}
return path;
}
public void setClientDetailsService(ClientDetailsService clientDetailsService) {
this.clientDetailsService = clientDetailsService;
}
public IdentityProviderProvisioning getProviderProvisioning() {
return providerProvisioning;
}
public void setProviderProvisioning(IdentityProviderProvisioning providerProvisioning) {
this.providerProvisioning = providerProvisioning;
}
@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "Unknown authentication token type, unable to derive user ID.")
public static final class UnknownPrincipalException extends RuntimeException {}
}