org.cloudfoundry.identity.uaa.client.SocialClientUserDetailsSource Maven / Gradle / Ivy
/*******************************************************************************
* 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.client;
import java.util.List;
import java.util.Map;
import org.cloudfoundry.identity.uaa.client.SocialClientUserDetails.Source;
import org.cloudfoundry.identity.uaa.user.UaaAuthority;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.util.Assert;
import org.springframework.web.client.RestOperations;
/**
*
* Helper class to extract user details from a remote endpoint, triggering
* authentication and approval if necessary by throwing an appropriate
* exception. The exception and how it should be handled depends on the
* authorization protocol in use (a RestTemplate implementation is usually
* paired with a client-side filter that handles redirects to the provider).
*
*
*
* Tested with Facebook, Github, Cloudfoundry, Twitter, LinkedIn. Should work
* with other providers if they use the same conventions in their user info
* endpoint. Note that the amount of information contained in the resulting
* {@link SocialClientUserDetails} depends on the provider and also on the
* user's preferences with the provider (e.g. he may have chosen not to reveal
* his email in github, in which case the user details will not have an email
* address).
*
*
* @author Dave Syer
*
*/
public class SocialClientUserDetailsSource implements InitializingBean, PreAuthenticatedPrincipalSource {
private RestOperations restTemplate;
private String userInfoUrl;
/**
* A rest template to be used to contact the remote user info endpoint.
* Normally would be an instance of {@link OAuth2RestTemplate}, but there is
* no need for that dependency to be explicit, and there are advantages in
* making it implicit (e.g. for testing purposes).
*
* @param restTemplate a rest template
*/
public void setRestTemplate(RestOperations restTemplate) {
this.restTemplate = restTemplate;
}
/**
* The remote URL of the /userinfo
endpoint or equivalent. This
* should be a resource on the remote
* server that provides user profile data.
*
* @param userInfoUrl
*/
public void setUserInfoUrl(String userInfoUrl) {
this.userInfoUrl = userInfoUrl;
}
@Override
public void afterPropertiesSet() {
Assert.state(userInfoUrl != null, "User info URL must be provided");
Assert.state(restTemplate != null, "RestTemplate URL must be provided");
}
/**
* Get as much generic information as possible about the current user from
* the remote endpoint. The aim is to
* collect as much of the properties of a {@link SocialClientUserDetails} as
* possible but not to fail if there is an
* authenticated user. If one of the tested remote providers is used at
* least a user id will be available, asserting
* that someone has authenticated and permitted us to see some public
* information.
*
* @return some user details
*/
@Override
public Authentication getPrincipal() {
@SuppressWarnings("unchecked")
Map map = restTemplate.getForObject(userInfoUrl, Map.class);
String userName = getUserName(map);
String email = null;
if (map.containsKey("email")) {
email = map.get("email");
}
if (userName == null && email != null) {
userName = email;
}
if (userName == null) {
userName = map.get("id"); // no user-friendly identifier for linked
// in and google
}
List authorities = UaaAuthority.USER_AUTHORITIES;
SocialClientUserDetails user = new SocialClientUserDetails(userName, authorities);
user.setSource(Source.classify(userInfoUrl));
user.setExternalId(getUserId(map));
String fullName = getFullName(map);
if (fullName != null) {
user.setFullName(fullName);
}
if (email != null) {
user.setEmail(email);
}
return user;
}
private String getFullName(Map map) {
if (map.containsKey("name")) {
return map.get("name");
}
if (map.containsKey("formattedName")) {
return map.get("formattedName");
}
if (map.containsKey("fullName")) {
return map.get("fullName");
}
String firstName = null;
if (map.containsKey("firstName")) {
firstName = map.get("firstName");
}
if (map.containsKey("givenName")) {
firstName = map.get("givenName");
}
String lastName = null;
if (map.containsKey("lastName")) {
lastName = map.get("lastName");
}
if (map.containsKey("familyName")) {
lastName = map.get("familyName");
}
if (firstName != null) {
if (lastName != null) {
return firstName + " " + lastName;
}
}
return null;
}
private Object getUserId(Map map) {
String key = "id";
if (userInfoUrl.contains("run.pivotal.io")) {
key = "user_id";
}
return map.get(key);
}
private String getUserName(Map map) {
String key = "username";
if (map.containsKey(key)) {
return map.get(key);
}
if (userInfoUrl.contains("run.pivotal.io") || userInfoUrl.endsWith("/uaa/userinfo")) {
key = "user_name";
}
if (userInfoUrl.contains("github.com")) {
key = "login";
}
if (userInfoUrl.contains("twitter.com")) {
key = "screen_name";
}
String value = map.get(key);
return value;
}
}