package org.pac4j.oauth.profile.facebook;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.github.scribejava.core.model.OAuth2AccessToken;
import org.pac4j.core.exception.HttpAction;
import org.pac4j.core.exception.TechnicalException;
import org.pac4j.core.profile.converter.Converters;
import org.pac4j.core.profile.converter.DateConverter;
import org.pac4j.core.util.CommonHelper;
import org.pac4j.oauth.client.FacebookClient;
import org.pac4j.oauth.config.OAuth20Configuration;
import org.pac4j.oauth.profile.JsonHelper;
import org.pac4j.oauth.profile.converter.JsonConverter;
import org.pac4j.oauth.profile.definition.OAuth20ProfileDefinition;
import org.pac4j.oauth.profile.facebook.converter.FacebookRelationshipStatusConverter;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Arrays;
import java.util.List;
/**
* This class is the Facebook profile definition.
*
* @author Jerome Leleu
* @since 1.1.0
*/
public class FacebookProfileDefinition extends OAuth20ProfileDefinition<FacebookProfile> {
public static final String NAME = "name";
public static final String MIDDLE_NAME = "middle_name";
public static final String LAST_NAME = "last_name";
public static final String LANGUAGES = "languages";
public static final String LINK = "link";
public static final String THIRD_PARTY_ID = "third_party_id";
public static final String TIMEZONE = "timezone";
public static final String UPDATED_TIME = "updated_time";
public static final String VERIFIED = "verified";
public static final String ABOUT = "about";
public static final String BIRTHDAY = "birthday";
public static final String EDUCATION = "education";
public static final String HOMETOWN = "hometown";
public static final String INTERESTED_IN = "interested_in";
public static final String POLITICAL = "political";
public static final String FAVORITE_ATHLETES = "favorite_athletes";
public static final String FAVORITE_TEAMS = "favorite_teams";
public static final String QUOTES = "quotes";
public static final String RELATIONSHIP_STATUS = "relationship_status";
public static final String RELIGION = "religion";
public static final String SIGNIFICANT_OTHER = "significant_other";
public static final String WEBSITE = "website";
public static final String WORK = "work";
public static final String FRIENDS = "friends";
public static final String MOVIES = "movies";
public static final String MUSIC = "music";
public static final String BOOKS = "books";
public static final String LIKES = "likes";
public static final String ALBUMS = "albums";
public static final String EVENTS = "events";
public static final String GROUPS = "groups";
public static final String MUSIC_LISTENS = "music.listens";
public static final String PICTURE = "picture";
public static final int DEFAULT_LIMIT = 0;
protected static final String BASE_URL = "https://graph.facebook.com/v2.8/me";
protected static final String APPSECRET_PARAMETER = "appsecret_proof";
public FacebookProfileDefinition() {
super(x -> new FacebookProfile());
Arrays.stream(new String[] {
NAME, MIDDLE_NAME, LAST_NAME, THIRD_PARTY_ID, ABOUT, POLITICAL, QUOTES, RELIGION, WEBSITE
}).forEach(a -> primary(a, Converters.STRING));
primary(TIMEZONE, Converters.INTEGER);
primary(VERIFIED, Converters.BOOLEAN);
primary(LINK, Converters.URL);
final JsonConverter<FacebookObject> objectConverter = new JsonConverter<>(FacebookObject.class);
final JsonConverter multiObjectConverter = new JsonConverter(List.class, new TypeReference<List<FacebookObject>>() {});
final JsonConverter multiInfoConverter = new JsonConverter(List.class, new TypeReference<List<FacebookInfo>>() {});
primary(UPDATED_TIME, Converters.DATE_TZ_GENERAL);
primary(BIRTHDAY, new DateConverter("MM/dd/yyyy"));
primary(RELATIONSHIP_STATUS, new FacebookRelationshipStatusConverter());
primary(LANGUAGES, multiObjectConverter);
primary(EDUCATION, new JsonConverter(List.class, new TypeReference<List<FacebookEducation>>() {}));
primary(HOMETOWN, objectConverter);
primary(INTERESTED_IN, new JsonConverter(List.class, new TypeReference<List<String>>() {}));
primary(LOCATION, objectConverter);
primary(FAVORITE_ATHLETES, multiObjectConverter);
primary(FAVORITE_TEAMS, multiObjectConverter);
primary(SIGNIFICANT_OTHER, objectConverter);
primary(WORK, new JsonConverter(List.class, new TypeReference<List<FacebookWork>>() {}));
secondary(FRIENDS, multiObjectConverter);
secondary(MOVIES, multiInfoConverter);
secondary(MUSIC, multiInfoConverter);
secondary(BOOKS, multiInfoConverter);
secondary(LIKES, multiInfoConverter);
secondary(ALBUMS, new JsonConverter(List.class, new TypeReference<List<FacebookPhoto>>() {}));
secondary(EVENTS, new JsonConverter(List.class, new TypeReference<List<FacebookEvent>>() {}));
secondary(GROUPS, new JsonConverter(List.class, new TypeReference<List<FacebookGroup>>() {}));
secondary(MUSIC_LISTENS, new JsonConverter(List.class, new TypeReference<List<FacebookMusicListen>>() {}));
secondary(PICTURE, new JsonConverter<>(FacebookPicture.class));
}
@Override
public String getProfileUrl(final OAuth2AccessToken accessToken, final OAuth20Configuration configuration) {
final FacebookClient client = (FacebookClient) configuration.getClient();
String url = BASE_URL + "?fields=" + client.getFields();
if (client.getLimit() > DEFAULT_LIMIT) {
url += "&limit=" + client.getLimit();
}
// possibly include the appsecret_proof parameter
if (client.getUseAppSecretProof()) {
url = computeAppSecretProof(url, accessToken, configuration);
}
return url;
}
/**
* The code in this method is based on this blog post: https://www.sammyk.me/the-single-most-important-way-to-make-your-facebook-app-more-secure
* and this answer: https://stackoverflow.com/questions/7124735/hmac-sha256-algorithm-for-signature-calculation
*
* @param url the URL to which we're adding the proof
* @param token the application token we pass back and forth
* @param configuration the current configuration
* @return URL with the appsecret_proof parameter added
*/
public String computeAppSecretProof(final String url, final OAuth2AccessToken token, final OAuth20Configuration configuration) {
try {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(configuration.getSecret().getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
String proof = org.apache.commons.codec.binary.Hex.encodeHexString(sha256_HMAC.doFinal(token.getAccessToken().getBytes("UTF-8")));
final String computedUrl = CommonHelper.addParameter(url, APPSECRET_PARAMETER, proof);
return computedUrl;
} catch (final Exception e) {
throw new TechnicalException("Unable to compute appsecret_proof", e);
}
}
@Override
public FacebookProfile extractUserProfile(final String body) throws HttpAction {
final FacebookProfile profile = newProfile();
final JsonNode json = JsonHelper.getFirstNode(body);
if (json != null) {
profile.setId(JsonHelper.getElement(json, "id"));
for (final String attribute : getPrimaryAttributes()) {
convertAndAdd(profile, attribute, JsonHelper.getElement(json, attribute));
}
extractData(profile, json, FacebookProfileDefinition.FRIENDS);
extractData(profile, json, FacebookProfileDefinition.MOVIES);
extractData(profile, json, FacebookProfileDefinition.MUSIC);
extractData(profile, json, FacebookProfileDefinition.BOOKS);
extractData(profile, json, FacebookProfileDefinition.LIKES);
extractData(profile, json, FacebookProfileDefinition.ALBUMS);
extractData(profile, json, FacebookProfileDefinition.EVENTS);
extractData(profile, json, FacebookProfileDefinition.GROUPS);
extractData(profile, json, FacebookProfileDefinition.MUSIC_LISTENS);
extractData(profile, json, FacebookProfileDefinition.PICTURE);
}
return profile;
}
protected void extractData(final FacebookProfile profile, final JsonNode json, final String name) {
final JsonNode data = (JsonNode) JsonHelper.getElement(json, name);
if (data != null) {
convertAndAdd(profile, name, JsonHelper.getElement(data, "data"));
}
}
}