-package de.juplo.facebook.token;
-
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import org.apache.commons.codec.binary.Base64;
-import org.codehaus.jackson.JsonNode;
-import org.codehaus.jackson.map.ObjectMapper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
-import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
-import org.springframework.security.oauth2.client.token.AccessTokenRequest;
-import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
-import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
-import org.springframework.security.oauth2.common.OAuth2AccessToken;
-
-
-/**
- * This class extends {@link AuthorizationCodeAccessTokenProvider} and adds
- * support for signed requests, which are issued by Facebook, if the Canvas-
- * or Tab-Page of a Facebook-App is accessed for the first time.
- *
- * @author Kai Moritz
- */
-public class SignedRequestAwareAuthorizationCodeAccessTokenProvider
- extends AuthorizationCodeAccessTokenProvider
-{
- private final Logger log =
- LoggerFactory.getLogger(SignedRequestAwareAuthorizationCodeAccessTokenProvider.class);
- private final static Pattern pattern =
- Pattern.compile("([a-zA-Z0-9_-]+)\\.([a-zA-Z0-9_-]+)");
-
- public final static String PARAM_SIGNED_REQUEST = "signed_request";
-
-
- private String secret;
- private ObjectMapper objectMapper;
-
-
- @Override
- public OAuth2AccessToken obtainAccessToken(
- OAuth2ProtectedResourceDetails details,
- AccessTokenRequest parameters
- )
- {
- try
- {
- return super.obtainAccessToken(details, parameters);
- }
- catch (UserRedirectRequiredException redirect)
- {
- log.debug("no valid access-token available: checking for signed request");
-
- if (!parameters.containsKey(PARAM_SIGNED_REQUEST))
- {
- log.info(
- "parameter " + PARAM_SIGNED_REQUEST + " is not present"
- );
- throw redirect;
- }
-
- String signed_request = parameters.get(PARAM_SIGNED_REQUEST).get(0);
-
- Matcher matcher = pattern.matcher(signed_request);
- if (!matcher.matches())
- {
- log.error("invalid signed_request: {}", signed_request);
- throw redirect;
- }
-
- String signature = matcher.group(1);
- String rawdata = matcher.group(2);
-
- String data;
- try
- {
- data = new String(Base64.decodeBase64(rawdata), "UTF-8");
- log.debug("JSON-data: {}", data);
- }
- catch (UnsupportedEncodingException e)
- {
- log.error("error while decoding data: {}", e.getMessage());
- throw redirect;
- }
-
- JsonNode json;
- try
- {
- json = objectMapper.readTree(data);
- }
- catch (IOException e)
- {
- log.error("error \"{}\" while parsing JSON-data: {}", e, data);
- throw redirect;
- }
-
- String algorithm = "";
- try
- {
- algorithm = json.get("algorithm").asText();
- }
- catch (NullPointerException e) {}
- if (algorithm.isEmpty())
- {
- log.error("field \"algorithm\" is missing: {}", data);
- throw redirect;
- }
- algorithm = algorithm.replaceAll("-", "");
-
- String check;
- try
- {
- SecretKeySpec key = new SecretKeySpec(secret.getBytes("UTF-8"), algorithm);
- Mac mac = Mac.getInstance(algorithm);
- mac.init(key);
- byte[] hmacData = mac.doFinal(rawdata.getBytes("UTF-8"));
- check = new String(Base64.encodeBase64URLSafe(hmacData), "UTF-8");
- }
- catch (
- UnsupportedEncodingException |
- NoSuchAlgorithmException |
- InvalidKeyException |
- IllegalStateException e
- )
- {
- log.error("signature check failed!", e);
- throw redirect;
- }
- if (!check.equals(signature))
- {
- log.error("signature does not match!");
- throw redirect;
- }
-
- /**
- * Extract additional information and store it in the token
- * See:
- * https://developers.facebook.com/docs/reference/login/signed-request/
- * TODO:
- * - Attribute "code"
- */
- Map<String,Object> additionalInformation = new HashMap<>();
- try
- {
- additionalInformation.put(
- "issued_at",
- new Date(json.get("issued_at").getLongValue()*1000L)
- );
- Map<String,Object> user = new HashMap<>();
- user.put(
- "country",
- json.get("user").get("country").asText()
- );
- user.put(
- "locale",
- json.get("user").get("locale").asText()
- );
- user.put(
- "age_min",
- json.get("user").get("age").get("min").getNumberValue()
- );
- if (json.get("user") != null && json.get("user").get("max") != null)
- user.put(
- "age_max",
- json.get("user").get("age").get("max").getNumberValue()
- );
- additionalInformation.put("user", user);
- if (json.get("app_data") != null)
- additionalInformation.put("app_data", json.get("app_data").asText());
- if (json.get("page") != null)
- {
- Map<String,Object> page = new HashMap<>();
- page.put("id", json.get("page").get("id").asText());
- page.put("liked", json.get("page").get("liked").asBoolean());
- page.put("admin", json.get("page").get("admin").asBoolean());
- additionalInformation.put("page", page);
- }
- }
- catch (NullPointerException e)
- {
- log.warn("expected additional data is missing: {}", data);
- }
-
- DefaultOAuth2AccessToken token = null;
- try
- {
- String value = json.get("oauth_token").asText();
- if (value.isEmpty())
- {
- log.error("field \"oauth_token\" is missing: {}", data);
- throw redirect;
- }
- token = new DefaultOAuth2AccessToken(value);
- token.setExpiration(new Date(json.get("expires").getLongValue()*1000L));
-
- additionalInformation.put(
- "user_id",
- json.get("user_id").asText()
- );
-
- token.setAdditionalInformation(additionalInformation);
- }
- catch (NullPointerException e)
- {
- if (token == null)
- {
- log.error("field \"oauth_token\" is missing: {}", data);
- throw redirect;
- }
- else
- log.warn("expected additional data is missing: {}", data);
- }
-
- return token;
- }
- }
-
-
- public String getSecret()
- {
- return secret;
- }
-
- public void setSecret(String secret)
- {
- this.secret = secret;
- }
-
- public ObjectMapper getObjectMapper()
- {
- return objectMapper;
- }
-
- public void setObjectMapper(ObjectMapper objectMapper)
- {
- this.objectMapper = objectMapper;
- }
-}