FileDocCategorySizeDatePackage
FacebookSignatureUtil.javaAPI DocFacebook API11110Mon Jul 16 13:08:38 BST 2007com.facebook.api

FacebookSignatureUtil.java

/*
  +---------------------------------------------------------------------------+
  | Facebook Development Platform Java Client                                 |
  +---------------------------------------------------------------------------+
  | Copyright (c) 2007 Facebook, Inc.                                         |
  | All rights reserved.                                                      |
  |                                                                           |
  | Redistribution and use in source and binary forms, with or without        |
  | modification, are permitted provided that the following conditions        |
  | are met:                                                                  |
  |                                                                           |
  | 1. Redistributions of source code must retain the above copyright         |
  |    notice, this list of conditions and the following disclaimer.          |
  | 2. Redistributions in binary form must reproduce the above copyright      |
  |    notice, this list of conditions and the following disclaimer in the    |
  |    documentation and/or other materials provided with the distribution.   |
  |                                                                           |
  | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR      |
  | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
  | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.   |
  | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,          |
  | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT  |
  | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
  | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY     |
  | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT       |
  | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF  |
  | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.         |
  +---------------------------------------------------------------------------+
  | For help with this library, contact developers-help@facebook.com          |
  +---------------------------------------------------------------------------+
 */
package com.facebook.api;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public final class FacebookSignatureUtil {
  private FacebookSignatureUtil() {
  }

  /**
   * Out of the passed in <code>reqParams</code>, extracts the parameters that
   * are in the FacebookParam namespace and returns them.
   * @param reqParams A map of request parameters to their values. Values
   *                  are arrays of strings, as returned by
   *                  ServletRequest.getParameterMap(). Only the first element
   *                  in a given array is significant.
   * @return a boolean indicating whether the calculated signature matched the
   *   expected signature
   */
  public static Map<String, CharSequence> extractFacebookParamsFromArray(Map<CharSequence, CharSequence[]> reqParams) {
    if (null == reqParams)
      return null;
    Map<String,CharSequence> result = new HashMap<String,CharSequence>(reqParams.size());
    for (Map.Entry<CharSequence,CharSequence[]> entry : reqParams.entrySet()) {
      String key = entry.getKey().toString();
      if (FacebookParam.isInNamespace(key)) {
        CharSequence[] value = entry.getValue();
        if (value.length > 0)
          result.put(key, value[0]);
      }
    }
    return result;
  }

  /**
   * Out of the passed in <code>reqParams</code>, extracts the parameters that
   * are in the FacebookParam namespace and returns them.
   * @param reqParams a map of request parameters to their values
   * @return a boolean indicating whether the calculated signature matched the
   *   expected signature
   */
  public static Map<String, CharSequence> extractFacebookNamespaceParams(Map<CharSequence, CharSequence> reqParams) {
    if (null == reqParams)
      return null;
    Map<String,CharSequence> result = new HashMap<String,CharSequence>(reqParams.size());
    for (Map.Entry<CharSequence,CharSequence> entry : reqParams.entrySet()) {
      String key = entry.getKey().toString();
      if (FacebookParam.isInNamespace(key))
        result.put(key, entry.getValue());
    }
    return result;
  }

  /**
   * Out of the passed in <code>reqParams</code>, extracts the parameters that
   * are known FacebookParams and returns them.
   * @param reqParams a map of request parameters to their values
   * @return a map suitable for being passed to verify signature
   */
  public static EnumMap<FacebookParam, CharSequence> extractFacebookParams(Map<CharSequence, CharSequence> reqParams) {
    if (null == reqParams)
      return null;

    EnumMap<FacebookParam, CharSequence> result =
      new EnumMap<FacebookParam, CharSequence>(FacebookParam.class);
    for (Map.Entry<CharSequence, CharSequence> entry: reqParams.entrySet()) {
      FacebookParam matchingFacebookParam = FacebookParam.get(entry.getKey().toString());
      if (null != matchingFacebookParam) {
        result.put(matchingFacebookParam, entry.getValue());
      }
    }
    return result;
  }

  /**
   * Verifies that a signature received matches the expected value.
   * Removes FacebookParam.SIGNATURE from params if present.
   * @param params a map of parameters and their values, such as one
   *   obtained from extractFacebookParams; expected to the expected signature 
   *   as the FacebookParam.SIGNATURE parameter
   * @param secret
   * @return a boolean indicating whether the calculated signature matched the
   *   expected signature
   */
  public static boolean verifySignature(EnumMap<FacebookParam, CharSequence> params, String secret) {
    if (null == params || params.isEmpty() )
      return false;
    CharSequence sigParam = params.remove(FacebookParam.SIGNATURE);
    return (null == sigParam) ? false : verifySignature(params, secret, sigParam.toString()); 
  }

  /**
   * Verifies that a signature received matches the expected value.
   * @param params a map of parameters and their values, such as one
   *   obtained from extractFacebookParams
   * @return a boolean indicating whether the calculated signature matched the
   *   expected signature
   */
  public static boolean verifySignature(EnumMap<FacebookParam, CharSequence> params, String secret,
                                        String expected) {
    assert !(null == secret || "".equals(secret));
    if (null == params || params.isEmpty() )
      return false;
    if (null == expected || "".equals(expected)) {
      return false;
    }
    params.remove(FacebookParam.SIGNATURE);
    List<String> sigParams = convertFacebookParams(params.entrySet());
    return verifySignature(sigParams, secret, expected);
  }

  /**
   * Verifies that a signature received matches the expected value.
   * Removes FacebookParam.SIGNATURE from params if present.
   * @param params a map of parameters and their values, such as one
   *   obtained from extractFacebookNamespaceParams; expected to contain the 
   *   signature as the FacebookParam.SIGNATURE parameter
   * @param secret
   * @return a boolean indicating whether the calculated signature matched the
   *   expected signature
   */
  public static boolean verifySignature(Map<String, CharSequence> params, String secret) {
    if (null == params || params.isEmpty() )
      return false;
    CharSequence sigParam = params.remove(FacebookParam.SIGNATURE.toString());
    return (null == sigParam) ? false : verifySignature(params, secret, sigParam.toString()); 
  }

  /**
   * Verifies that a signature received matches the expected value.
   * @param params a map of parameters and their values, such as one
   *   obtained from extractFacebookNamespaceParams
   * @return a boolean indicating whether the calculated signature matched the
   *   expected signature
   */
  public static boolean verifySignature(Map<String, CharSequence> params, String secret,
                                        String expected) {
    assert !(null == secret || "".equals(secret));
    if (null == params || params.isEmpty() )
      return false;
    if (null == expected || "".equals(expected)) {
      return false;
    }
    params.remove(FacebookParam.SIGNATURE.toString());
    List<String> sigParams = convert(params.entrySet());
    return verifySignature(sigParams, secret, expected);
  }


  private static boolean verifySignature(List<String> sigParams, String secret, String expected) {
    if (null == expected || "".equals(expected))
      return false;
    String signature = generateSignature(sigParams, secret);
    return expected.equals(signature);    
  }

  /**
   * Converts a Map of key-value pairs into the form expected by generateSignature
   * @param entries a collection of Map.Entry's, such as can be obtained using
   *   myMap.entrySet()
   * @return a List suitable for being passed to generateSignature
   */
  public static List<String> convert(Collection<Map.Entry<String, CharSequence>> entries) {
    List<String> result = new ArrayList<String>(entries.size());
    for (Map.Entry<String, CharSequence> entry: entries)
      result.add(FacebookParam.stripSignaturePrefix(entry.getKey()) + "=" + entry.getValue());
    return result;
  }

  /**
   * Converts a Map of key-value pairs into the form expected by generateSignature
   * @param entries a collection of Map.Entry's, such as can be obtained using
   *   myMap.entrySet()
   * @return a List suitable for being passed to generateSignature
   */
  public static List<String> convertFacebookParams(Collection<Map.Entry<FacebookParam, CharSequence>> entries) {
    List<String> result = new ArrayList<String>(entries.size());
    for (Map.Entry<FacebookParam, CharSequence> entry: entries)
      result.add(entry.getKey().getSignatureName() + "=" + entry.getValue());
    return result;
  }

  /**
   * Calculates the signature for the given set of params using the supplied secret
   * @param params Strings of the form "key=value"
   * @param secret
   * @return the signature
   */
  public static String generateSignature(List<String> params, String secret) {
    StringBuffer buffer = new StringBuffer();
    Collections.sort(params);
    for (String param: params) {
      buffer.append(param);
    }

    buffer.append(secret);
    try {
      java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
      StringBuffer result = new StringBuffer();
      for (byte b: md.digest(buffer.toString().getBytes())) {
        result.append(Integer.toHexString((b & 0xf0) >>> 4));
        result.append(Integer.toHexString(b & 0x0f));
      }
      return result.toString();
    }
    catch (java.security.NoSuchAlgorithmException ex) {
      System.err.println("MD5 does not appear to be supported" + ex);
      return "";
    }
  }
}