Verifying SendGrid's Signed Event Webhook? - c#

I'm trying to implement the validation from this link.
https://sendgrid.com/docs/for-developers/tracking-events/getting-started-event-webhook-security-features/#the-signed-event-webhook
I don't want the OAuth part of this, but to use the Public key and the headers to verify the data. I've seen many posts related to SendGrid's Event Webhook, but nothing to verify it.
This is what I've attempted, but something is clearly wrong. I'm really not sure how to translate the code from the documentation to C#. I'm not sure where each piece of data from the Request should go. Does anyone have any insight on this?
public async Task<IHttpActionResult> EventsHook()
{
IEnumerable<string> signatureFromHeader = Request.Headers.GetValues("X-Twilio-Email-Event-Webhook-Signature");
byte[] timeStampBytes = Encoding.ASCII.GetBytes(Request.Headers.GetValues("X-Twilio-Email-Event-Webhook-Timestamp").First());
byte[] body = await Request.Content.ReadAsByteArrayAsync();
byte[] payloadHash = timeStampBytes.Concat(body).ToArray();
byte[] signatureByteArray = Convert.FromBase64String(signatureFromHeader.First());
var publicKey = "{myPublicKey}";
byte[] publicKeyBytes = Convert.FromBase64String(publicKey);
var ecdsaParams = new ECParameters
{
D = publicKeyBytes
};
using (var ecdsa = ECDsa.Create(ecdsaParams))
{
if (ecdsa.VerifyData(payloadHash, signatureByteArray, HashAlgorithmName.SHA256))
{
return Ok();
}
return StatusCode(System.Net.HttpStatusCode.Forbidden);
}
}

I just implemented this, and I can help you with this:
1- I used the library starkbank-ecdsa to verify the signature.
2- I declared this interface
public interface IEmailWebHookRequestValidator
{
bool IsValidRequest(HttpRequest request);
}
3- the implementation of this interface is this:
public class SendGridWebHookRequestValidator : IEmailWebHookRequestValidator
{
private const string SIGNATURE_HEADER = "X-Twilio-Email-Event-Webhook-Signature";
private const string TIMESTAMP_HEADER = "X-Twilio-Email-Event-Webhook-Timestamp";
private readonly string _sendGridWebHookPublicKey;
public SendGridWebHookRequestValidator(ISettingsProvider settingsProvider)
{
_sendGridWebHookPublicKey = settingsProvider.SendGridWebHookPublicKey //"public key that you can get from SendGrid website when you enable Event Webhook's signature";
}
public bool IsValidRequest(HttpRequest request)
{
request.Headers.TryGetValue(SIGNATURE_HEADER, out var signatureHeaderValues);
request.Headers.TryGetValue(TIMESTAMP_HEADER, out var timestampHeaderValues);
var signature = signatureHeaderValues.FirstOrDefault();
var timestamp = timestampHeaderValues.FirstOrDefault();
if (string.IsNullOrWhiteSpace(signature) || string.IsNullOrWhiteSpace(timestamp))
{
return false;
}
var body = ReadStream(request.Body);
return VerifySignature(_sendGridWebHookPublicKey, body, signature, timestamp);
}
public bool VerifySignature(string verificationKey, string payload, string signature, string timestamp)
{
var publicKey = PublicKey.fromPem(verificationKey);
var timestampedPayload = timestamp + payload;
var decodedSignature = Signature.fromBase64(signature);
return Ecdsa.verify(timestampedPayload, decodedSignature, publicKey);
}
public static string ReadStream(Stream stream)
{
using var ms = new MemoryStream();
stream.CopyTo(ms);
return Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length);
}
}
ISettingsProvider is just an interface that helps you to retrieve the public key from the settings.
The above code can be easily used when your webhook receives the httprequest by just passing it to the IsValidRequest method.

I have this problem when was testing using a payload copied from Webhook.site to Postman.
The ONE thing I was not doing was an ENTER (new line) on postman payload. This was changing the expected payload from signature.

Related

populate array(list) to send a call from c# to php

So I'm passing parameters from unity to a PHP API and all are fine but now I want to pass it as an array, so in this method I have declared my lists and trying to send the requests with the correct parameters
tokenID, address, and value (the last 3) need to be populated from the lists declared, but I'm stuck on how to do that.
any help appreciated
public List<string> recipientAdvancedAddress = new List<string>();
public List<string> tokenAdvancedSend = new List<string>();
public List<string> valueAdvancedSend = new List<string>();
public void Send()
{
StartCoroutine(SendAsync(userSession, senderAdvancedID, tokenAdvancedSend, recipientAdvancedAddress, valueAdvancedSend));
}
IEnumerator SendAsync(string session, int senderID, List<string> tokenID, List<string> addresses, List<string> value)
{
CryptoFilter cryptoFilter = new CryptoFilter();
cryptoFilter.AddParam("action", "advancedSendFT");
cryptoFilter.AddParam("session", session);
cryptoFilter.AddParam("senderID", $"{senderID}");
cryptoFilter.AddParam("tokenID", tokenID);
cryptoFilter.AddParam("addresses", addresses);
cryptoFilter.AddParam("value", value);
using (UnityWebRequest webRequest = UnityWebRequest.Post($"http://{SERVER_ADDR}/tsd/api.php", cryptoFilter.GetRequestPayload()))
{
yield return webRequest.SendWebRequest();
var result = webRequest.downloadHandler.text;
// Decrypt the response from the server.
result = cryptoFilter.GetResponsePayload(result);
sendItem data = JsonConvert.DeserializeObject<sendItem>(result);
if (!data.error)
txID = data.id;
}
}
this is the error: https://i.ibb.co/frNR5tF/error.png
and this is the method in the CryptoFilter.cs
class CryptoFilter
{
// Encrypt & Decrypt Engine
AES256 aes = new AES256();
// Request parameters
Dictionary<string, string> request = new Dictionary<string, string>();
/*
*
* Add a parameter.
*
*/
public void AddParam(string name, string value)
{
if (value != "")
request.Add(name, value);
}
/*
*
* Get the encrypted request string.
*
*/
public string GetRequestPayload()
{
string plain = JsonConvert.SerializeObject(request);
return aes.EncryptString(plain);
}
/*
*
* Get the decrypted response string.
*
*/
public string GetResponsePayload(string encrypted)
{
return aes.DecryptString(encrypted);
}
}
Btw, couldn't I do a foreach in the main script?
this is how the php is sent btw:
CreateEnjinRequest(
identity_id: YOUR_IDENTITY_ID,
type: ADVANCED_SEND,
advanced_send_token_data: {transfers: [
{tokenID: "xxxxxxxxxxxxxxxx", addresses: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", value:"1" },
{tokenID: "xxxxxxxxxxxxxxxx", addresses: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", value:"1" }
]})
so if I have 1 tokenID it's fine, but when I need to send 2 tokenID's to 2 different addresses is what I'm trying to achieve
How about an overload for the AddParam method:
AddParam(List<string>)
Or a generic version:
AddParam(List<T>)
Or even:
AddParam(Collection<T>)
I think I don't really understand what you're asking but within this method, you could maybe just loop through the items and do with them whatever you're already doing.
Edit: Looks like you need a json in the end. You can use Unity's built-in serialization for this. First make a class that represent the DTO (data transfer object) and then serialize it so it's a json string.
[Serializable]
public class CryptoFilter
{
public string action;
// other string fields
public List<string> addresses;
// other string list fields
public CryptoFilter(string action, List<string> addresses)
{
this.action = action;
this.addresses = addresses;
}
}
Then use:
CryptoFilter cryptoFilter = new CryptoFilter(...);
string json = JsonUtility.ToJson(cryptoFilter);
Edit: A string to string dictionary as request won't work for lists. Use a serializable class like the one I posted for this. Just call it Request or so instead of CryptoFilter. Then create the object and set it to the CryptoFilter once instead of calling AddParam multiple times.

How do you invalidate a cached AWS API Gateway endpoint in ASPNETCORE?

[Note: I've already worked out an answer to this but struggled to find anything online so I'm adding it here]
I need to invalidate the cache for an individual AWS API Gateway endpoint using ASPNETCORE.
The docs say to send a signed request. How do you do this in .NET?
I'm answering my own question as I couldn't find much information online and it took a bit of time to get working. Hopefully it'll help someone.
I've added code here: https://gist.github.com/secretorange/905b4811300d7c96c71fa9c6d115ee24
CacheInvalidationRequestBuilder.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
namespace Aws
{
public static class CacheInvalidationRequestBuilder
{
private const string ServiceName = "execute-api";
private const string Algorithm = "AWS4-HMAC-SHA256";
private const string ContentType = "application/json";
private const string DateTimeFormat = "yyyyMMddTHHmmssZ";
private const string DateFormat = "yyyyMMdd";
public static WebRequest Build(CacheInvalidationRequestModel request)
{
string hashedRequestPayload = CreateRequestPayload(String.Empty);
string authorization = Sign(request, hashedRequestPayload, "GET", request.AbsolutePath, request.QueryString);
string requestDate = DateTime.UtcNow.ToString(DateTimeFormat);
var webRequest = WebRequest.Create($"https://{request.Host}{request.AbsolutePath}");
webRequest.Method = "GET";
webRequest.ContentType = ContentType;
webRequest.Headers.Add("Cache-Control", "max-age=0");
webRequest.Headers.Add("Host", request.Host);
webRequest.Headers.Add("X-Amz-Date", requestDate);
webRequest.Headers.Add("Authorization", authorization);
return webRequest;
}
private static string CreateRequestPayload(string jsonString)
{
return HexEncode(Hash(ToBytes(jsonString)));
}
private static string Sign(CacheInvalidationRequestModel request, string hashedRequestPayload, string requestMethod, string canonicalUri, string canonicalQueryString)
{
var currentDateTime = DateTime.UtcNow;
var dateStamp = currentDateTime.ToString(DateFormat);
var requestDate = currentDateTime.ToString(DateTimeFormat);
var credentialScope = $"{dateStamp}/{request.Region}/{ServiceName}/aws4_request";
var headers = new SortedDictionary<string, string> {
{ "cache-control", "max-age=0" },
{ "content-type", ContentType },
{ "host", request.Host },
{ "x-amz-date", requestDate }
};
var canonicalHeaders = string.Join("\n", headers.Select(x => x.Key.ToLowerInvariant() + ":" + x.Value.Trim())) + "\n";
// Task 1: Create a Canonical Request For Signature Version 4
var SignedHeaders = String.Join(';', headers.Select(x => x.Key.ToLowerInvariant()));
var canonicalRequest = $"{requestMethod}\n{canonicalUri}\n{canonicalQueryString}\n{canonicalHeaders}\n{SignedHeaders}\n{hashedRequestPayload}";
var hashedCanonicalRequest = HexEncode(Hash(ToBytes(canonicalRequest)));
// Task 2: Create a String to Sign for Signature Version 4
var stringToSign = $"{Algorithm}\n{requestDate}\n{credentialScope}\n{hashedCanonicalRequest}";
// Task 3: Calculate the AWS Signature Version 4
var signingKey = GetSignatureKey(request.SecretKey, dateStamp, request.Region, ServiceName);
var signature = HexEncode(HmacSha256(stringToSign, signingKey));
// Task 4: Prepare a signed request
// Authorization: algorithm Credential=access key ID/credential scope, SignedHeadaers=SignedHeaders, Signature=signature
var authorization = $"{Algorithm} Credential={request.AccessKey}/{dateStamp}/{request.Region}/{ServiceName}/aws4_request, SignedHeaders={SignedHeaders}, Signature={signature}";
return authorization;
}
private static byte[] GetSignatureKey(string key, string dateStamp, string regionName, string serviceName)
{
var kDate = HmacSha256(dateStamp, ToBytes("AWS4" + key));
var kRegion = HmacSha256(regionName, kDate);
var kService = HmacSha256(serviceName, kRegion);
return HmacSha256("aws4_request", kService);
}
private static byte[] ToBytes(string str)
{
return Encoding.UTF8.GetBytes(str.ToCharArray());
}
private static string HexEncode(byte[] bytes)
{
return BitConverter.ToString(bytes).Replace("-", string.Empty).ToLowerInvariant();
}
private static byte[] Hash(byte[] bytes)
{
return SHA256.Create().ComputeHash(bytes);
}
private static byte[] HmacSha256(string data, byte[] key)
{
return new HMACSHA256(key).ComputeHash(ToBytes(data));
}
}
}
CacheInvalidationRequestModel.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Aws
{
public class CacheInvalidationRequestModel
{
public string Region { get; set; }
public string Host { get; set; }
public string AbsolutePath { get; set; }
public string QueryString { get; set; }
public string AccessKey { get; set; }
public string SecretKey { get; set; }
}
}
How to use the code
To make a request, use code similar to:
var url = $"/myendpoint";
var model = GetCacheInvalidationRequestModel(url);
var request = CacheInvalidationRequestBuilder.Build(model);
try
{
// Hit the endpoint
using (var response = request.GetResponse())
{
// Not currently doing anything with the response
}
}
catch(Exception ex)
{
Logger.LogError(ex, "Problem invalidating cache for url: " + url);
}
The GetCacheInvalidationRequestModel method might look something like this (I pass in the model properties as IOptions):
private CacheInvalidationRequestModel GetCacheInvalidationRequestModel(string absolutePath)
{
return new CacheInvalidationRequestModel()
{
Region = Options.Region,
Host = Options.Host,
AccessKey = Options.InvalidatorKey,
SecretKey = Options.InvalidatorSecret,
AbsolutePath = absolutePath
};
}
AWS Information
AWS docs for building signed requests are here: https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
Your AWS user will need an attached policy, as shown here: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-caching.html
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"execute-api:InvalidateCache"
],
"Resource": [
"arn:aws:execute-api:region:account-id:api-id/stage-name/GET/resource-path-specifier"
]
}
]
}
NOTE: You can use wildcards if you like.

How to upload any file on Slack via Slack-App in c#

I need help with uploading files to Slack.
I have a Slack-App that is working with my code(below) so far. But all I can do is post messages. I can not attach images to the messages - because I do not understand how to use the so called "methods" and the syntax Slack is "showing" on their API-page.
This creates my "content" and below its just a Stream for reading a file I could upload:
public class PostMessage
{
public FormUrlEncodedContent Content(string message, string file)
{
var values = new Dictionary<string, string>
{
{"token", "xoxp-myToken"},
{ "username", "X"},
{ "channel", "myChannel"},
{ "as_user", "false"},
{"text", message},
{ "content", file},
{ "attachments","[{ \"fallback\":\"dummy\", \"text\":\"this is a waste of time\"}]"}
};
var content = new FormUrlEncodedContent(values);
return content;
}
}
public class PostFile
{
String path = #"C:\Users\f.held\Desktop\Held-Docs\dagged.jpg";
public string ReadImageFile()
{
FileInfo fileInfo = new FileInfo(path);
long imageFileLength = fileInfo.Length;
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
BinaryReader br = new BinaryReader(fs);
byte[] imageData = br.ReadBytes((int)imageFileLength);
var str = Encoding.Default.GetString(imageData);
return str;
}
}
}
The client that communicates:
public class SlackClient
{
private readonly Uri _webhookUrl;
private readonly HttpClient _httpClient = new HttpClient {};
public SlackClient(Uri webhookUrl)
{
_webhookUrl = webhookUrl;
}
public async Task<HttpResponseMessage> SendMessageAsync(FormUrlEncodedContent content)
{
var response = await _httpClient.PostAsync(_webhookUrl, content);
return response;
}
}
}
The Main:
public static void Main(string[] args)
{
Task.WaitAll(IntegrateWithSlackAsync());
}
private static async Task IntegrateWithSlackAsync()
{
var webhookUrl = new Uri("https://slack.com/api/files.upload");
var slackClient = new SlackClient(webhookUrl);
PostMessage PM = new PostMessage();
PostFile PF = new PostFile();
while (true)
{
Console.Write("Type a message: ");
var message = Console.ReadLine();
var testFile = PF.ReadImageFile();
FormUrlEncodedContent payload = PM.Content(message, testFile);
var response = await slackClient.SendMessageAsync(payload);
var isValid = response.IsSuccessStatusCode ? "valid" : "invalid";
Console.WriteLine($"Received {isValid} response.");
Console.WriteLine(response);
response.Dispose();
}
}
}
}
If somebody has an example on what a upload has to look like. Or even better,
if somebody could really explain the syntax these Slack-Messages have to have.
That would be great! I still do not know where and HOW I should put the so called
"Accepted content types: multipart/form-data, application/x-www-form-urlencoded" to my upload. I just can not find examples on this...
Edit:
What confuses me needlesly is that Slack states they have an extra method called file.upload - but we shouldn't use it anymore, we should use just postMessage.
But how would I "pack" a file in a message? My syntax always seems to be off. Especially when it comes to "content"...
I just can not figure out what the c#-code has to look like. Where do I declare the aforementioned "content type"?
Another problem is, it always sends my messages through - means I get a 200-response from the server. But it never shows the file (which probably means the syntax is off) Or I get the 200-response but the message never shows in Slack.
Images in message
If you want to include an image in your message (along with some text) you can do so by adding images as message attachment to a normal message send with chat.postMessage.
For that you need a public URL of your image and that link with the image_url property to an attachment. That attachment can also contain text, and you can add multiple attachments to your message.
This is how it looks like:
And here is how this message looks in JSON:
{
"channel": "test",
"text": "This is a message example with images in the attachment",
"attachments": [
{
"fallback": "game over",
"text": "This is some text in the attachement",
"image_url": "https://i.imgur.com/jO9N3eJ.jpg"
}
]
}
Uploading images
The image URL needs to be publicly accessible on the Internet. So you need to host your image file on a public webserver or upload it to a image cloud service (e.g. imgur.com).
You can also use Slack as cloud service for your images. Here is how that works:
Upload to Slack: Upload your image to your Slack workspace with files.upload
Get public URL: Get a public URL for your image file with files.sharedPublicURL. Normally all files on Slack are private, but you can only use public URLs for message attachments.
Send message: Include your image as attachment in a message: Use the permalink_public property of your image file as value for image_url
Example code
Here is a full working example in C# for first uploading an image to Slack and then using it in a message.
Note: This example requires Newtonsoft.Json.
using System;
using System.Net;
using System.Collections.Specialized;
using System.Text;
using Newtonsoft.Json;
public class SlackExample
{
// classes for converting JSON respones from API method into objects
// note that only those properties are defind that are needed for this example
// reponse from file methods
class SlackFileResponse
{
public bool ok { get; set; }
public String error { get; set; }
public SlackFile file { get; set; }
}
// a slack file
class SlackFile
{
public String id { get; set; }
public String name { get; set; }
public String permalink_public { get; set; }
}
// reponse from message methods
class SlackMessageResponse
{
public bool ok { get; set; }
public String error { get; set; }
public String channel { get; set; }
public String ts { get; set; }
}
// a slack message attachment
class SlackAttachment
{
public String fallback { get; set; }
public String text { get; set; }
public String image_url { get; set; }
}
// main method with logic
public static void Main()
{
String token = "xoxp-YOUR-TOKEN";
/////////////////////
// Step 1: Upload file to Slack
var parameters = new NameValueCollection();
// put your token here
parameters["token"] = token;
var client1 = new WebClient();
client1.QueryString = parameters;
byte[] responseBytes1 = client1.UploadFile(
"https://slack.com/api/files.upload",
"C:\\Temp\\Stratios_down.jpg"
);
String responseString1 = Encoding.UTF8.GetString(responseBytes1);
SlackFileResponse fileResponse1 =
JsonConvert.DeserializeObject<SlackFileResponse>(responseString1);
String fileId = fileResponse1.file.id;
/////////////////////
// Step 2: Make file public and get the URL
var parameters2 = new NameValueCollection();
parameters2["token"] = token;
parameters2["file"] = fileId;
var client2 = new WebClient();
byte[] responseBytes2 = client2.UploadValues("https://slack.com/api/files.sharedPublicURL", "POST", parameters2);
String responseString2 = Encoding.UTF8.GetString(responseBytes2);
SlackFileResponse fileResponse2 =
JsonConvert.DeserializeObject<SlackFileResponse>(responseString2);
String imageUrl = fileResponse2.file.permalink_public;
/////////////////////
// Step 3: Send message including freshly uploaded image as attachment
var parameters3 = new NameValueCollection();
parameters3["token"] = token;
parameters3["channel"] = "test_new";
parameters3["text"] = "test message 2";
// create attachment
SlackAttachment attachment = new SlackAttachment();
attachment.fallback = "this did not work";
attachment.text = "this is anattachment";
attachment.image_url = imageUrl;
SlackAttachment[] attachments = { attachment };
parameters3["attachments"] = JsonConvert.SerializeObject(attachments);
var client3 = new WebClient();
byte[] responseBytes3 = client3.UploadValues("https://slack.com/api/chat.postMessage", "POST", parameters3);
String responseString3 = Encoding.UTF8.GetString(responseBytes3);
SlackMessageResponse messageResponse =
JsonConvert.DeserializeObject<SlackMessageResponse>(responseString3);
}
}
Here is a shorter working example showing how to just upload any file to Slack with C# only. The example will also automatically share the file the given channel.
I have included the logic to convert the API response from JSON, which will always be needed to determine if the API call was successful.
Note: This example requires Newtonsoft.Json
using System;
using System.Net;
using System.Collections.Specialized;
using System.Text;
using Newtonsoft.Json;
public class SlackExample
{
// classes for converting JSON respones from API method into objects
// note that only those properties are defind that are needed for this example
// reponse from file methods
class SlackFileResponse
{
public bool ok { get; set; }
public String error { get; set; }
public SlackFile file { get; set; }
}
// a slack file
class SlackFile
{
public String id { get; set; }
public String name { get; set; }
}
// main method with logic
public static void Main()
{
var parameters = new NameValueCollection();
// put your token here
parameters["token"] = "xoxp-YOUR-TOKEN";
parameters["channels"] = "test";
var client = new WebClient();
client.QueryString = parameters;
byte[] responseBytes = client.UploadFile(
"https://slack.com/api/files.upload",
"D:\\temp\\Stratios_down.jpg"
);
String responseString = Encoding.UTF8.GetString(responseBytes);
SlackFileResponse fileResponse =
JsonConvert.DeserializeObject<SlackFileResponse>(responseString);
}
}
About content types: Those are part of the header of a HTTP request and can be set manually in the WebClient object (see also this answer). However, for our case you can ignore it, because the default content types that WebClient is using for the POST request will work just fine.
Also see this answer on how to upload files with the WebClient class.
Here is another complete example for uploading a file to Slack, this time using the async approach with HttpClient.
Note: This example requires Newtonsoft.Json.
using Newtonsoft.Json;
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace SlackExample
{
class UploadFileExample
{
private static readonly HttpClient client = new HttpClient();
// classes for converting JSON respones from API method into objects
// note that only those properties are defind that are needed for this example
// reponse from file methods
class SlackFileResponse
{
public bool ok { get; set; }
public String error { get; set; }
public SlackFile file { get; set; }
}
// a slack file
class SlackFile
{
public String id { get; set; }
public String name { get; set; }
}
// sends a slack message asynchronous
// throws exception if message can not be sent
public static async Task UploadFileAsync(string token, string path, string channels)
{
// we need to send a request with multipart/form-data
var multiForm = new MultipartFormDataContent();
// add API method parameters
multiForm.Add(new StringContent(token), "token");
multiForm.Add(new StringContent(channels), "channels");
// add file and directly upload it
FileStream fs = File.OpenRead(path);
multiForm.Add(new StreamContent(fs), "file", Path.GetFileName(path));
// send request to API
var url = "https://slack.com/api/files.upload";
var response = await client.PostAsync(url, multiForm);
// fetch response from API
var responseJson = await response.Content.ReadAsStringAsync();
// convert JSON response to object
SlackFileResponse fileResponse =
JsonConvert.DeserializeObject<SlackFileResponse>(responseJson);
// throw exception if sending failed
if (fileResponse.ok == false)
{
throw new Exception(
"failed to upload message: " + fileResponse.error
);
}
else
{
Console.WriteLine(
"Uploaded new file with id: " + fileResponse.file.id
);
}
}
static void Main(string[] args)
{
// upload this file and wait for completion
UploadFileAsync(
"xoxp-YOUR-TOKEN",
"C:\\temp\\Stratios_down.jpg",
"test"
).Wait();
Console.ReadKey();
}
}
}

Validating Recaptcha 2 (No CAPTCHA reCAPTCHA) in ASP.NET's server side

The new Recaptcha 2 looks promising, but i didn't find a way to validate it in ASP.NET's server side,
if(Page.IsValid) in This answer, is valid for the old Recaptcha, but not the new one,
How to validate the new reCAPTCHA in server side?
After reading many resources, I ended up with writing this class to handle the validation of the new ReCaptcha :
As mentioned Here : When a reCAPTCHA is solved by end user, a new field (g-recaptcha-response) will be populated in HTML.
We need to read this value and pass it to the class below to validate it:
In C#:
In the code behind of your page :
string EncodedResponse = Request.Form["g-Recaptcha-Response"];
bool IsCaptchaValid = (ReCaptchaClass.Validate(EncodedResponse) == "true" ? true : false);
if (IsCaptchaValid) {
//Valid Request
}
The Class:
using Newtonsoft.Json;
public class ReCaptchaClass
{
public static string Validate(string EncodedResponse)
{
var client = new System.Net.WebClient();
string PrivateKey = "6LcH-v8SerfgAPlLLffghrITSL9xM7XLrz8aeory";
var GoogleReply = client.DownloadString(string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", PrivateKey, EncodedResponse));
var captchaResponse = Newtonsoft.Json.JsonConvert.DeserializeObject<ReCaptchaClass>(GoogleReply);
return captchaResponse.Success.ToLower();
}
[JsonProperty("success")]
public string Success
{
get { return m_Success; }
set { m_Success = value; }
}
private string m_Success;
[JsonProperty("error-codes")]
public List<string> ErrorCodes
{
get { return m_ErrorCodes; }
set { m_ErrorCodes = value; }
}
private List<string> m_ErrorCodes;
}
In VB.NET:
In the code behind of your page :
Dim EncodedResponse As String = Request.Form("g-Recaptcha-Response")
Dim IsCaptchaValid As Boolean = IIf(ReCaptchaClass.Validate(EncodedResponse) = "True", True, False)
If IsCaptchaValid Then
'Valid Request
End If
The Class:
Imports Newtonsoft.Json
Public Class ReCaptchaClass
Public Shared Function Validate(ByVal EncodedResponse As String) As String
Dim client = New System.Net.WebClient()
Dim PrivateKey As String = "6dsfH-v8SerfgAPlLLffghrITSL9xM7XLrz8aeory"
Dim GoogleReply = client.DownloadString(String.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", PrivateKey, EncodedResponse))
Dim captchaResponse = Newtonsoft.Json.JsonConvert.DeserializeObject(Of ReCaptchaClass)(GoogleReply)
Return captchaResponse.Success
End Function
<JsonProperty("success")> _
Public Property Success() As String
Get
Return m_Success
End Get
Set(value As String)
m_Success = value
End Set
End Property
Private m_Success As String
<JsonProperty("error-codes")> _
Public Property ErrorCodes() As List(Of String)
Get
Return m_ErrorCodes
End Get
Set(value As List(Of String))
m_ErrorCodes = value
End Set
End Property
Private m_ErrorCodes As List(Of String)
End Class
Here's a version that uses the JavaScriptSerializer. Thanks Ala for the basis for this code.
WebConfig App Setting -
I've added the secret key to the Web.Config in my case to allow transforms between environments. It can also be easily encrypted here if required.
<add key="Google.ReCaptcha.Secret" value="123456789012345678901234567890" />
The ReCaptcha Class - A simple class to post the response parameter along with your secret to Google and validate it. The response is deserialized using the .Net JavaScriptSerializer class and from that true or false returned.
using System.Collections.Generic;
using System.Configuration;
public class ReCaptcha
{
public bool Success { get; set; }
public List<string> ErrorCodes { get; set; }
public static bool Validate(string encodedResponse)
{
if (string.IsNullOrEmpty(encodedResponse)) return false;
var client = new System.Net.WebClient();
var secret = ConfigurationManager.AppSettings["Google.ReCaptcha.Secret"];
if (string.IsNullOrEmpty(secret)) return false;
var googleReply = client.DownloadString(string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", secret, encodedResponse));
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var reCaptcha = serializer.Deserialize<ReCaptcha>(googleReply);
return reCaptcha.Success;
}
}
Validate The Response - Check the validity of the g-Recaptcha-Response form parameter in your Controller (or code behind for a web form) and take appropriate action.
var encodedResponse = Request.Form["g-Recaptcha-Response"];
var isCaptchaValid = ReCaptcha.Validate(encodedResponse);
if (!isCaptchaValid)
{
// E.g. Return to view or set an error message to visible
}
Most of these answers seem more complex than needed. They also dont specify the IP which will help prevent a interception attack (https://security.stackexchange.com/questions/81865/is-there-any-reason-to-include-the-remote-ip-when-using-recaptcha). Here's what I settled on
public bool CheckCaptcha(string captchaResponse, string ipAddress)
{
using (var client = new WebClient())
{
var response = client.DownloadString($"https://www.google.com/recaptcha/api/siteverify?secret={ ConfigurationManager.AppSettings["Google.ReCaptcha.Secret"] }&response={ captchaResponse }&remoteIp={ ipAddress }");
return (bool)JObject.Parse(response)["success"];
}
}
You can use "IsValidCaptcha()" method to validate your google recaptcha on server side. Replace your secret key with "YourRecaptchaSecretkey" in the following method.
Public bool IsValidCaptcha()
{
string resp = Request["g-recaptcha-response"];
var req = (HttpWebRequest)WebRequest.Create
(https://www.google.com/recaptcha/api/siteverify?secret=+ YourRecaptchaSecretkey + "&response=" + resp);
using (WebResponse wResponse = req.GetResponse())
{
using (StreamReader readStream = new StreamReader(wResponse.GetResponseStream()))
{
string jsonResponse = readStream.ReadToEnd();
JavaScriptSerializer js = new JavaScriptSerializer();
// Deserialize Json
CaptchaResult data = js.Deserialize<CaptchaResult>(jsonResponse);
if (Convert.ToBoolean(data.success))
{
return true;
}
}
}
return false;
}
Also create following class as well.
public class CaptchaResult
{
public string success { get; set; }
}
According to the doc you just post your secret key and user's answer to API and read returned "success" property
SHORT ANSWER:
var webClient = new WebClient();
string verification = webClient.DownloadString(string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", secretKey, userResponse));
if (JObject.Parse(verification)["success"].Value<bool>())
{
// SUCCESS!!!
FULL EXAMPLE:
Suppose, you implement this page in IamNotARobotLogin.cshtml.
<head>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head>
<body>
<form action="Login" method="POST">
<div class="g-recaptcha" data-sitekey="your_site_key"></div><br/>
<input type="submit" value="Log In">
</form>
</body>
And suppose you wish the controller saved, let's say, "I_AM_NOT_ROBOT" flag in the session if the verification succeeded:
public ActionResult IamNotARobotLogin()
{
return View();
}
[HttpPost]
public ActionResult Login()
{
const string secretKey = "6LcH-v8SerfgAPlLLffghrITSL9xM7XLrz8aeory";
string userResponse = Request.Form["g-Recaptcha-Response"];
var webClient = new System.Net.WebClient();
string verification = webClient.DownloadString(string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", secretKey, userResponse));
var verificationJson = Newtonsoft.Json.Linq.JObject.Parse(verification);
if (verificationJson["success"].Value<bool>())
{
Session["I_AM_NOT_A_ROBOT"] = "true";
return RedirectToAction("Index", "Demo");
}
// try again:
return RedirectToAction("IamNotARobotLogin");
}
Here's my fork of Ala's solution in order to:
send paramter in POST
to sanitize the form input
include the requester IP address
store the secret in Web.Config:
In the controller:
bool isCaptchaValid = await ReCaptchaClass.Validate(this.Request);
if (!isCaptchaValid)
{
ModelState.AddModelError("", "Invalid captcha");
return View(model);
}
The utility class:
public class ReCaptchaClass
{
private static ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private static string SecretKey = System.Configuration.ConfigurationManager.AppSettings["Google.ReCaptcha.Secret"];
[JsonProperty("success")]
public bool Success { get; set; }
[JsonProperty("error-codes")]
public List<string> ErrorCodes { get; set; }
public static async Task<bool> Validate(HttpRequestBase Request)
{
string encodedResponse = Request.Form["g-Recaptcha-Response"];
string remoteIp = Request.UserHostAddress;
using (var client = new HttpClient())
{
var values = new Dictionary<string, string>
{
{"secret", SecretKey},
{"remoteIp", remoteIp},
{"response", encodedResponse}
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync("https://www.google.com/recaptcha/api/siteverify", content);
var responseString = await response.Content.ReadAsStringAsync();
var captchaResponse = Newtonsoft.Json.JsonConvert.DeserializeObject<ReCaptchaClass>(responseString);
if ((captchaResponse.ErrorCodes?.Count ?? 0) != 0)
{
log.Warn("ReCaptcha errors: " + string.Join("\n", captchaResponse.ErrorCodes));
}
return captchaResponse.Success;
}
}
}
This article give clear step by step explication on how to implement a ReCaptcha validation attribute on your model.
First, create the Recaptcha validation attribute.
namespace Sample.Validation
{
public class GoogleReCaptchaValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Lazy<ValidationResult> errorResult = new Lazy<ValidationResult>(() => new ValidationResult("Google reCAPTCHA validation failed", new String[] { validationContext.MemberName }));
if (value == null || String.IsNullOrWhiteSpace( value.ToString()))
{
return errorResult.Value;
}
IConfiguration configuration = (IConfiguration)validationContext.GetService(typeof(IConfiguration));
String reCaptchResponse = value.ToString();
String reCaptchaSecret = configuration.GetValue<String>("GoogleReCaptcha:SecretKey");
HttpClient httpClient = new HttpClient();
var httpResponse = httpClient.GetAsync($"https://www.google.com/recaptcha/api/siteverify?secret={reCaptchaSecret}&response={reCaptchResponse}").Result;
if (httpResponse.StatusCode != HttpStatusCode.OK)
{
return errorResult.Value;
}
String jsonResponse = httpResponse.Content.ReadAsStringAsync().Result;
dynamic jsonData = JObject.Parse(jsonResponse);
if (jsonData.success != true.ToString().ToLower())
{
return errorResult.Value;
}
return ValidationResult.Success;
}
}
}
Then add the validation attribute on your model.
namespace Sample.Models
{
public class XModel
{
// ...
[Required]
[GoogleReCaptchaValidation]
public String GoogleReCaptchaResponse { get; set; }
}
}
Finally, you have just to call the ModelState.IsValid method
namespace Sample.Api.Controllers
{
[ApiController]
public class XController : ControllerBase
{
[HttpPost]
public IActionResult Post(XModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// ...
}
}
}
Et voilĂ  ! :)
Another example is posted here:
RecaptchaV2.NET (Github)
It also implements the secure token option of Recaptcha 2.0 (look at full source code for that bit, I have stripped out relevant pieces of code ONLY for validating a result).
This one doesn't rely on newtonsoft's json parser and instead uses the built in .NET one.
Here is the relevant snippet of code from the RecaptchaV2.NET library (from recaptcha.cs):
namespace RecaptchaV2.NET
{
/// <summary>
/// Helper Methods for the Google Recaptcha V2 Library
/// </summary>
public class Recaptcha
{
public string SiteKey { get; set; }
public string SecretKey { get; set; }
public Guid SessionId { get; set; }
/// <summary>
/// Validates a Recaptcha V2 response.
/// </summary>
/// <param name="recaptchaResponse">g-recaptcha-response form response variable (HttpContext.Current.Request.Form["g-recaptcha-response"])</param>
/// <returns>RecaptchaValidationResult</returns>
public RecaptchaValidationResult Validate(string recaptchaResponse)
{
RecaptchaValidationResult result = new RecaptchaValidationResult();
HttpWebRequest req = (HttpWebRequest)WebRequest.Create("https://www.google.com/recaptcha/api/siteverify?secret=" + SecretKey + "&response="
+ recaptchaResponse + "&remoteip=" + GetClientIp());
//Google recaptcha Response
using (WebResponse wResponse = req.GetResponse())
{
using (StreamReader readStream = new StreamReader(wResponse.GetResponseStream()))
{
string jsonResponse = readStream.ReadToEnd();
JavaScriptSerializer js = new JavaScriptSerializer();
result = js.Deserialize<RecaptchaValidationResult>(jsonResponse.Replace("error-codes", "ErrorMessages").Replace("success", "Succeeded"));// Deserialize Json
}
}
return result;
}
private string GetClientIp()
{
// Look for a proxy address first
String _ip = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
// If there is no proxy, get the standard remote address
if (string.IsNullOrWhiteSpace(_ip) || _ip.ToLower() == "unknown")
_ip = HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
return _ip;
}
}
public class RecaptchaValidationResult
{
public RecaptchaValidationResult()
{
ErrorMessages = new List<string>();
Succeeded = false;
}
public List<string> ErrorMessages { get; set; }
public bool Succeeded { get; set; }
public string GetErrorMessagesString()
{
return string.Join("<br/>", ErrorMessages.ToArray());
}
}
}
Google's ReCaptcha API no longer accepts the payload as query string parameters in a GET request. Google always returned a "false" success response unless I sent the data via HTTP POST. Here is an update to Ala's (excellent!) class which POSTs the payload to the Google service endpoint:
using Newtonsoft.Json;
using System.Net;
using System.IO;
using System.Text;
public class RecaptchaHandler
{
public static string Validate(string EncodedResponse, string RemoteIP)
{
var client = new WebClient();
string PrivateKey = "PRIVATE KEY";
WebRequest req = WebRequest.Create("https://www.google.com/recaptcha/api/siteverify");
string postData = String.Format("secret={0}&response={1}&remoteip={2}",
PrivateKey,
EncodedResponse,
RemoteIP);
byte[] send = Encoding.Default.GetBytes(postData);
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.ContentLength = send.Length;
Stream sout = req.GetRequestStream();
sout.Write(send, 0, send.Length);
sout.Flush();
sout.Close();
WebResponse res = req.GetResponse();
StreamReader sr = new StreamReader(res.GetResponseStream());
string returnvalue = sr.ReadToEnd();
var captchaResponse = JsonConvert.DeserializeObject<RecaptchaHandler>(returnvalue);
return captchaResponse.Success;
}
[JsonProperty("success")]
public string Success
{
get { return m_Success; }
set { m_Success = value; }
}
private string m_Success;
[JsonProperty("error-codes")]
public List<string> ErrorCodes
{
get { return m_ErrorCodes; }
set { m_ErrorCodes = value; }
}
private List<string> m_ErrorCodes;
}
Using dynamic to validate recaptcha at server side
Calling Function
[HttpPost]
public ActionResult ClientOrderDetail(FormCollection collection, string EncodedResponse)
{
Boolean Validation = myFunction.ValidateRecaptcha(EncodedResponse);
return View();
}
Function Declaration
public static Boolean ValidateRecaptcha(string EncodedResponse)
{
string PrivateKey = "YourSiteKey";
var client = new System.Net.WebClient();
var GoogleReply = client.DownloadString(string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", PrivateKey, EncodedResponse));
var serializer = new JavaScriptSerializer();
dynamic data = serializer.Deserialize(GoogleReply, typeof(object));
Boolean Status = data["success"];
string challenge_ts = data["challenge_ts"];
string hostname = data["hostname"];
return Status;
}
the example I posted in this so post uses Newtonsoft.JSON to deserialize the full returned JSON, posts the data to Google(as opposed to using a querystring) stores the relevant variables in the web.config rather than hard coded.

JSON Numeric Keys

Sorry for asking this question again, but I just don't seem to understand the given answers :(
I need to read some JSON using JSON.net. some of the keys start with numbers, eg.
"24h_rate":22.65826595,"
When I put the JSON into http://json2csharp.com/ to make my classes, it makes it into __invalid_name__24h_total.
I am using the following to read and Deserialize the JSON
public class JsonWebClient
{
public async Task<System.IO.TextReader> DoRequestAsync(WebRequest req)
{
var task = Task.Factory.FromAsync((cb, o) => ((HttpWebRequest)o).BeginGetResponse(cb, o), res => ((HttpWebRequest)res.AsyncState).EndGetResponse(res), req);
var result = await task;
var resp = result;
var stream = resp.GetResponseStream();
var sr = new System.IO.StreamReader(stream);
return sr;
}
public async Task<T> getJsonAsync<T>(string url)
{
HttpWebRequest req = HttpWebRequest.CreateHttp(url);
req.AllowReadStreamBuffering = true;
var ret = await DoRequestAsync(req);
var response = await ret.ReadToEndAsync();
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(response);
}
}
What do I need to change to make this work?
Thanks very much.
Playing around with it a bit I found out that the deserializer you're using can't 'handle' key names that start with a number for some reason, even though it's perfectly valid JSON (checked with http://jsonlint.com/)
If you change it to this instead
{ "rate_24h":22.65826595 }
it works:
public class RootObject
{
public double rate_24h { get; set; }
}
Thanks to #Ulugbek Umirov:
Your rate_24h property should be attributed with [JsonProperty("24h_rate")] to be property deserialized by JSON.NET.

Categories

Resources