模仿/测试Facebook取消POST授权

时间:2012-03-19 11:21:46

标签: facebook-graph-api

Facebook需要一个公共网址才能发布POST,以便为用户取消对您应用的授权。

我在防火墙后面并且不希望在我的生产环境中测试这个,因此有没有人设法在c#(或其他)集成测试中模仿这个POST?

这将涉及构建一个带有signed_request字段的表单并加密到base64并将其发布到我的本地Web应用程序来处理......

2 个答案:

答案 0 :(得分:0)

以下是我在PHP中的表现:

<?php

function generateSignedRequest($data, $secret) {

    $payload = base64UrlEncode(json_encode($data));
    $signature = base64UrlEncode(hash_hmac('sha256', $payload, $secret, $raw = true));

    return "$signature.$payload";
}

function base64UrlEncode($input) {
    $input = rtrim(base64_encode($input), '=');
    return strtr($input, '+/', '-_');
}

$app_secret = "YOUR_APP_SECRET_HERE";

$data = array(
    'algorithm' => 'HMAC-SHA256',
    'issued_at' => 1332162396,
    'user' => array(
        'country' => 'bg',
        'locale' => 'en_US'
    ),
    'user_id' => '100003387159594'
);

$signed_request = generateSignedRequest($data, $app_secret);

?>

<form action="deauthorize_callback.php" method="post">
    <input type="text" name="signed_request" value="<?php echo $signed_request?>" size="255" />
    <input type="submit" value="Send Request" />
</form>

如果您在本地环境中使用PHP,则可以尝试将表单提交到后端,看看它是否有效。您只需要在数据数组中替换您的应用程序密钥和用户ID。

希望这有帮助,到目前为止它对我有用。

答案 1 :(得分:0)

我设法找到了类似的东西来构建我已经转换的Ruby中的有效负载,这里是集成测试:

此类构建将作为request.form [“signed_request”]

POST的字符串
public class SignedRequest
  {
    public static string SignedRequestFor(string applicationSecret, string json)
    {
      // encode payload
      var payload = ToUrlBase64String(json);

      // create sig
      var Hmac = SignWithHmac(UTF8Encoding.UTF8.GetBytes(payload), UTF8Encoding.UTF8.GetBytes(applicationSecret));
      var HmacBase64 = ToUrlBase64String(Hmac);

      // concat and return
      return HmacBase64.Concatenate(".", payload);
    }

    private static byte[] SignWithHmac(byte[] dataToSign, byte[] keyBody)
    {
      using (var hmacAlgorithm = new HMACSHA256(keyBody))
      {
        hmacAlgorithm.ComputeHash(dataToSign);
        return hmacAlgorithm.Hash;
      }
    }

    private static string ToUrlBase64String(string input)
    {
      byte[] encodedBytes = Encoding.UTF8.GetBytes(input);
      return ToUrlBase64String(encodedBytes);
    }

    private static string ToUrlBase64String(byte[] input)
    {
        return Convert.ToBase64String(input).Replace("=", String.Empty)
                                            .Replace('+', '-')
                                            .Replace('/', '_');
    }
  }

测试使用的小值对象:

public class RequestEnvelope
  {
    public long user_id { get; set; }

    public string algorithm = "HMAC-SHA256";
  }

测试:

[Test]
public void CanDeauthorizeUser()
{
  var appSecret = ConfigurationManager.AppSettings["NewsletterSubscriptionFacebookAppSecret"];

  var envelope = new RequestEnvelope { user_id = 123456 };

  var signedRequest = SignedRequest.SignedRequestFor(appSecret, JsonConvert.SerializeObject(envelope));

  var postData = "signed_request=" + signedRequest;
  byte[] postBytes = Encoding.UTF8.GetBytes(postData);

  var request = WebRequest.Create(ConfigurationManager.AppSettings["AppWebUrl"] + "/SubscriptionHandler.ashx?a=deactivate");
  request.Method = "POST";
  request.ContentType = "application/x-www-form-urlencoded";
  request.ContentLength = postBytes.Length;
  using (var newStream = request.GetRequestStream())
  {
    // Send the data.
    newStream.Write(postBytes, 0, postBytes.Length);
    newStream.Close();
  }

  // Get the response.
  using (var response = request.GetResponse())
  {
    var status = ((HttpWebResponse) response).StatusDescription;

    // Get the stream containing all content returned by the requested server.
    using (var dataStream = response.GetResponseStream())
    {

      // Open the stream using a StreamReader for easy access.
      using (var reader = new StreamReader(dataStream))
      {

        // Read the content fully up to the end.
        string responseFromServer = reader.ReadToEnd();

        Console.WriteLine(responseFromServer);
        Console.WriteLine("status: " + status);
      }
    }
  }
}

我发布到处理程序的代码段:

string signedRequestValue = HttpContext.Current.Request.Form["signed_request"];
var items = SignedRequestParser.Parse(ConfigurationManager.AppSettings["NewsletterSubscriptionFacebookAppSecret"], signedRequestValue);

用于解析该代码的代码(从codeplex facebook API中获取/修改):

public class SignedRequestParser
  {
    /// <summary>
    /// Parses the signed request string.
    /// </summary>
    /// <param name="appSecret">The unique app hash known the provider and the developer</param>
    /// <param name="signedRequestValue">The encoded signed request value.</param>
    /// <returns>The valid signed request.</returns>
    public static Dictionary<string, string> Parse(string appSecret, string signedRequestValue)
    {
      Check.Require(!String.IsNullOrEmpty(signedRequestValue));
      Check.Require(signedRequestValue.Contains("."), "Invalid Signed Request");

      string[] parts = signedRequestValue.Split('.');
      var encodedValue = parts[0];
      if (String.IsNullOrEmpty(encodedValue))
      {
        throw new InvalidOperationException("Invalid Signed Request");
      }

      var sig = Base64UrlDecode(encodedValue);
      var payload = parts[1];

      using (var cryto = new System.Security.Cryptography.HMACSHA256(Encoding.UTF8.GetBytes(appSecret)))
      {
        var hash = Convert.ToBase64String(cryto.ComputeHash(Encoding.UTF8.GetBytes(payload)));
        var hashDecoded = Base64UrlDecode(hash);
        if (hashDecoded != sig)
        {
          throw new InvalidOperationException("Invalid Signed Request.");
        }
      }

      var payloadJson = Encoding.UTF8.GetString(Convert.FromBase64String(Base64UrlDecode(payload)));
      var data = JsonConvert.DeserializeObject<Dictionary<string, string>>(payloadJson);
      return data;
    }

    /// <summary>
    /// Converts the base 64 url encoded string to standard base 64 encoding.
    /// </summary>
    /// <param name="encodedValue">The encoded value.</param>
    /// <returns>The base 64 string.</returns>
    private static string Base64UrlDecode(string encodedValue)
    {
      Check.Require(!String.IsNullOrEmpty(encodedValue));

      encodedValue = encodedValue.Replace('+', '-').Replace('/', '_').Trim();
      int pad = encodedValue.Length % 4;
      if (pad > 0)
      {
        pad = 4 - pad;
      }

      encodedValue = encodedValue.PadRight(encodedValue.Length + pad, '=');
      return encodedValue;
    }
  }