How to handle incoming message to Alexa skill from external app? - c#

I have an Alexa skill and a winform on a windows 10 device. I'm sending a message from the winform to Alexa using the Skill Messaging API. I've got the access token, sent the message and received a 202 status code to say the 'message has been successfully accepted, and will be sent to the skill' so I believe everything on the winform side is okay.
The code for it;
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (requestMessage, certificate, chain, policyErrors) => true;
using (var httpClient = new HttpClient(handler))
{
// Obtain skill messaging token
using (var requestToken = new HttpRequestMessage(new HttpMethod("POST"), "https://api.amazon.com/auth/O2/token"))
{
requestToken.Content = new StringContent("grant_type=client_credentials&scope=alexa:skill_messaging&client_id=amzn1.application-oa2-client.************&client_secret=************");
requestToken.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded");
var responseToken = httpClient.SendAsync(requestToken);
Response r = JsonConvert.DeserializeObject<Response>(responseToken.Result.Content.ReadAsStringAsync().Result);
// Send message
using (var requestMessage = new HttpRequestMessage(new HttpMethod("POST"), "https://api.eu.amazonalexa.com/v1/skillmessages/users/" + strUserId))
{
requestMessage.Headers.TryAddWithoutValidation("Authorization", "Bearer " + r.access_token);
requestMessage.Content = new StringContent("{ \"data\" : { \"message\" : \"Hi pickle\" }}");
requestMessage.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
var responseMessage = httpClient.SendAsync(requestMessage);
MessageBox.Show(responseMessage.Result.ToString());
}
}
}
How do I capture the incoming message event on the skill though?
Looking at the docs I need to handle an incoming request of type Messaging.MessageReceived? Is that correct?
I tried something like that in the skills FunctionHandler but didn't have any luck.
public SkillResponse FunctionHandler(SkillRequest input, ILambdaContext context)
{
// Initialise response
skillResponse = new SkillResponse
{
Version = "1.0",
Response = new ResponseBody()
};
ssmlResponse = new SsmlOutputSpeech();
if (input.GetRequestType() == typeof(LaunchRequest))
{
LaunchRequestHandler(input, context);
}
else if (input.GetRequestType() == typeof(IntentRequest))
{
IntentRequestHandler(input, context);
}
else if (input.GetRequestType() == typeof(SessionEndedRequest))
{
SessionEndedRequestHandler(input, context);
}
else if(input.GetRequestType().Equals("Messaging.MessageReceived"))
{
ssmlResponse.Ssml = "<speak>" + input.Request.Type + "</speak>";
}
skillResponse.Response.OutputSpeech = ssmlResponse;
return skillResponse;
}
How do I react to the message? Is it permissions I need to set up? Does the incoming message not trigger the functionhandler the same way the echo device does?
Thanks.

Related

Xero API Disconnect Issue ,Not able to see login screen after disconnecting

I am disconnecting with xero as per the xero documentation that is revocation of the connection and deleting the organization which are connected and both the apis are working fine. But when I disconnect and click on connect again it still remembers the previous organization connected and without showing the xero page of organization it redirects me back to my application and I am connected now.
But I want the xero to show its page where I can choose the organization I want. For now I am deleting the XeroIdentity cookie from my browser. Is it a good approach as it shows me the login page now? if not please
do suggest me a way for this.
Here is my code to disconnect
public async Task<JsonResult> RemoveConnection(string token, string Id)
{
try
{
if (Id == null)
{
return Json("Please connect with Xero first");
}
else
{
var accessToken1 = "";
var tenantId1 = "";
var token1 = await _tokenStore.GetAccessTokenAsync(Id);
accessToken1 = token1.AccessToken;
var restclient1 = new RestClient("https://identity.xero.com/connect/revocation");
var request1 = new RestRequest(Method.POST);
string encoded = base64encode(Configuration["xero:clientid"] + ":" + Configuration["xero:clientsecret"]);
request1.AddHeader("authorization", string.Format("basic {0}", encoded));
request1.AddHeader("content-type", "application/x-www-form-urlencoded");
request1.AddParameter("application/json",string.Format("token={0}", accessToken1),ParameterType.RequestBody);
var response1 = restclient1.Execute(request1);
var connections1 = await _xeroClient.GetConnectionsAsync(token1);
foreach (var connection in connections1)
{
accessToken1 = token1.AccessToken;
tenantId1 = connection.id.ToString();
var restClient = new RestClient("https://api.xero.com/connections/" + tenantId1 + "");
var request = new RestRequest(Method.DELETE);
request.AddHeader("Authorization", string.Format("Bearer {0}", accessToken1));
var response = restClient.Execute(request);
var x = response.Content;
var connections12 = await _xeroClient.GetConnectionsAsync(token1);
}
var details = new { Result = "Disconnected", Status = "Ok" };
return Json(details);
}
}
catch (Exception ex)
{
return Json(ex.Message);
}
}

How to send PushNotification to iOS device with C# asp.net .netcore in webapi?

I'm trying to send a PushNotification Message to a list of iOS device.
My constraints are :
code is inside a webapi controller
web server is a Windows 2012 R2
using asp.net / .net core (actually 3.1)
First, I decided to use p8 file instead of p12 (it seems Apple prefere it).
I crawled many SO questions, trying about 5-6 solutions, but i still get same results :
An unhandled exception occurred while processing the request.
Win32Exception: Le message reçu était inattendu ou formaté de façon
incorrecte. Unknown location
AuthenticationException: Authentication failed, see inner exception.
System.Net.Security.SslStream.StartSendAuthResetSignal(ProtocolToken
message, AsyncProtocolRequest asyncRequest, ExceptionDispatchInfo
exception)
HttpRequestException: The SSL connection could not be established, see
inner exception.
System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Stream
stream, SslClientAuthenticationOptions sslOptions, CancellationToken
cancellationToken)
From french "Win32Exception: Le message reçu était inattendu ou formaté de façon incorrecte", in English would be "Win32Exception: The message received was unexpected or badly formatted"
To format the JWT, i've used the solution here from Bourne Koloh.
I've also tried PushSharp and CoreSharp with same error.
When I extract data from the p8 file, i have all the file without first and last line, and without linebreak.
When i use the Push Notifications Tester application, it works, the message is delivered to the device.
By the way, i've added the both p12 certificate (dev and prod) to the Windows 2012 R2 server
I probably miss something important but still don't know what.
Since the message say it's a problem of authentication, I guess it's around the JWT and the the p8 file.
How I extract the p8 file data :
var data = System.IO.File.ReadAllText("AuthKey.p8");
var list = data.Split('\n').ToList();
var prk = list.Where((s, i) => i != 0 && i != list.Count - 1).Aggregate((agg, s) => agg + s);
var key = new ECDsaCng(CngKey.Import(Convert.FromBase64String(prk), CngKeyBlobFormat.Pkcs8PrivateBlob));
Il also tried this way (with BouncyCastle ):
using (var reader = System.IO.File.OpenText("AuthKey.p8"))
{
var ecPrivateKeyParameters = (ECPrivateKeyParameters)new PemReader(reader).ReadObject();
var x = ecPrivateKeyParameters.Parameters.G.AffineXCoord.GetEncoded();
var y = ecPrivateKeyParameters.Parameters.G.AffineYCoord.GetEncoded();
var d = ecPrivateKeyParameters.D.ToByteArrayUnsigned();
var key = ECDsaCng(EccKey.New(x, y, d));
}
And now how I build the JWT :
try #1 : using Jose package.
private string GetProviderToken(ECDsaCng key)
{
var epochNow = (int)DateTime.UtcNow.Subtract(new DateTime(1970,1,1)).TotalSeconds;
var payload = new Dictionary<string, object>()
{
{ "iss", "THETEAMID" },
{ "iat", epochNow }
};
var extraHeaders = new Dictionary<string, object>()
{
{ "kid", "THEKEYID"}
};
return JWT.Encode(payload, key, JwsAlgorithm.ES256, extraHeaders);
}
var token = GetProviderToken(key);
try #2 :
private string CreateToken(ECDsa key, string keyID, string teamID)
{
var securityKey = new ECDsaSecurityKey(key) { KeyId = keyID };
var credentials = new SigningCredentials(securityKey, "ES256");
var descriptor = new SecurityTokenDescriptor
{
IssuedAt = DateTime.Now,
Issuer = teamID,
SigningCredentials = credentials
};
descriptor.Expires = null;
descriptor.NotBefore = null;
var handler = new JwtSecurityTokenHandler();
var encodedToken = handler.CreateEncodedJwt(descriptor);
return encodedToken;
}
var token = CreateToken(key, "THEKEYID", "THETEAMID");
try #3
private string SignES256(string privateKey, string header, string payload)
{
CngKey key = CngKey.Import(Convert.FromBase64String(privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob);
using (ECDsaCng dsa = new ECDsaCng(key))
{
dsa.HashAlgorithm = CngAlgorithm.Sha256;
var unsignedJwtData = Microsoft.AspNetCore.WebUtilities.WebEncoders.Base64UrlEncode(System.Text.Encoding.UTF8.GetBytes(header)) + "." + Microsoft.AspNetCore.WebUtilities.WebEncoders.Base64UrlEncode(System.Text.Encoding.UTF8.GetBytes(payload));
var signature = dsa.SignData(System.Text.Encoding.UTF8.GetBytes(unsignedJwtData));
return unsignedJwtData + "." + Microsoft.AspNetCore.WebUtilities.WebEncoders.Base64UrlEncode(signature);
}
}
var token = SignES256(prk, "{\"alg\":\"ES256\" ,\"kid\":\"THEKEYID\"}", "{ \"iss\": \"THETEAMID\",\"iat\":" + (int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds + "\" }");
If I'm not using a library (CorePush, etc) for sending the http message, i do this way :
var url = string.Format("https://api.sandbox.push.apple.com/3/device/{0}", deviceToken);
var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Headers.TryAddWithoutValidation("apns-push-type", "alert"); // or background
request.Headers.TryAddWithoutValidation("apns-id", Guid.NewGuid().ToString("D"));
request.Headers.TryAddWithoutValidation("apns-expiration", Convert.ToString(0));
request.Headers.TryAddWithoutValidation("apns-priority", Convert.ToString(10));
request.Headers.TryAddWithoutValidation("apns-topic", "com.company.project");
request.Content = new StringContent("{\"aps\":{\"alert\":\"Hello\"},\"yourCustomKey\":\"1\"}");
// also tried without yourcustomkey
request.Version = new Version(2, 0); // tried directly with System.Net.HttpVersion.Version20;
var handler = new HttpClientHandler();
handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls; // Tried with only Tls12
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
using (HttpClient client = new HttpClient(handler))
{
HttpResponseMessage resp = await client.SendAsync(request).ContinueWith(responseTask =>
{
return responseTask.Result; // line of error
});
if (resp != null)
{
string apnsResponseString = await resp.Content.ReadAsStringAsync();
handler.Dispose();
//ALL GOOD ....
return Ok(apnsResponseString);
}
handler.Dispose();
}

Error 401 'INVALID_KEY_TYPE' with FireBase and c#

I am implementing c # with FireBase, it sends me error 401 'INVALID_KEY_TYPE'
private static Uri FireBasePushNotificationsURL = new Uri("https://fcm.googleapis.com/fcm/send");
private static string ServerKey = "AIzaSyA9fL8lPyxcrngIDDsDeUbq9sPTkavXXXX";
public static async Task<bool> SendPushNotification(string deviceTokens, string title, string body, object data)
{
bool sent = false;
if (deviceTokens.Count() > 0)
{
//Object creation
var messageInformation = new
{
to = "fZ0EyxU-tsk:APA91bE3-qo4DwL9phteDJC8pG6iLdr-YSSl-N_2SJne3U6eyUhmEuZNQhJi0YM-XXXXXX",
priority = "high",
content_available = true,
notification = new
{
body = "Test",
title = "Test miguel",
badge = 1
},
};
//Object to JSON STRUCTURE => using Newtonsoft.Json;
string jsonMessage = JsonConvert.SerializeObject(messageInformation);
//Create request to Firebase API
var request = new HttpRequestMessage(HttpMethod.Post, FireBasePushNotificationsURL);
request.Headers.TryAddWithoutValidation("Authorization", "key=" + ServerKey);
request.Content = new StringContent(jsonMessage, Encoding.UTF8, "application/json");
HttpResponseMessage result;
using (var client = new HttpClient())
{
result = await client.SendAsync(request);
sent = sent && result.IsSuccessStatusCode;
}
}
return sent;
}
I have the same problem. The problem is that you take the wrong server key.
The right firebase server key is Project > Settings > Cloud Messaging > Server Key

.NET Core 2.1 Apple Push Notifications

I have to send push notifications to specific iOS devices with my .Net Core WebAPI that will be executed on a Windows 2008 Server R2. The server itself should not be the problem because it is working with a node.js library. But I want it to work with an WepAPI in ASP .Net Core 2.1 which is self hosted with the inbuilt Kestrel Server. Maybe you've got an idea how to solve this problem.
My Code:
// This will encode the jason web token apns needs for the authorization
// get the base64 private key of the .p8 file from apple
string p8File = System.IO.File.ReadAllText(Settings.Apn.PrivateKey);
p8File = p8File.Replace("-----BEGIN PRIVATE KEY-----", string.Empty);
p8File = p8File.Replace("-----END PRIVATE KEY-----", string.Empty);
p8File = p8File.Replace(" ", string.Empty);
byte[] keyData = Convert.FromBase64String(p8File);
ECDsa key = new ECDsaCng(CngKey.Import(keyData, CngKeyBlobFormat.Pkcs8PrivateBlob));
ECDsaSecurityKey securityKey = new ECDsaSecurityKey(key) { KeyId = Settings.Apn.KeyId };
SigningCredentials credentials = new SigningCredentials(securityKey, "ES256");
SecurityTokenDescriptor descriptor =
new SecurityTokenDescriptor
{
IssuedAt = DateTime.Now,
Issuer = Settings.Apn.TeamId,
SigningCredentials = credentials
};
JwtSecurityTokenHandler jwtHandler = new JwtSecurityTokenHandler();
string encodedToken = jwtHandler.CreateEncodedJwt(descriptor);
this.log?.LogInformation($"Created JWT: {encodedToken}");
// The hostname is: https://api.development.push.apple.com:443
HttpClient client = new HttpClient { BaseAddress = new Uri(Settings.Apn.Hostname) };
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
this.log?.LogInformation("Initialized new HttpClient.");
// payload content for the apns
JObject payloadData = new JObject
{
new JProperty("alert", data.Message),
new JProperty("badge", 2),
new JProperty("sound", "default")
};
JObject payload = new JObject
{
new JProperty("aps", payloadData)
};
this.log?.LogInformation($"Setup payload: {payload}");
// HttpRequestMessage that should be send
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Post,
$"{Settings.Apn.Hostname}/3/device/{data.DeviceId}")
{
Content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json")
};
this.log?.LogInformation("Setup HttpRequestMessage.");
// Setup the header
request.Headers.Add("Authorization", $"Bearer {encodedToken}");
request.Headers.Add("apns-id", Guid.NewGuid().ToString());
request.Headers.Add("apns-expiration", DateTime.Now.AddDays(1).ToString(CultureInfo.InvariantCulture));
request.Headers.Add("apns-priority", "10");
request.Headers.Add("apns-topic", "de.gefasoft-engineering.FabChat");
// Debug logging
this.log.LogDebug(request.ToString());
this.log.LogDebug(await request.Content.ReadAsStringAsync());
this.log.LogDebug(request.RequestUri.Host + request.RequestUri.Port);
// Send request
var result = await client.SendAsync(request);
this.log?.LogInformation("Sent request.");
this.log?.LogInformation(await result.Content.ReadAsStringAsync());
I always get following Exception thrown:
System.Net.Http.HttpRequestException: The SSL connection could not be
established, see inner exception. --->
System.Security.Authentication.AuthenticationException: Authentication
failed, see inner exception. --->
System.ComponentModel.Win32Exception: The message received was
unexpected or badly formatted --- End of inner exception stack
trace ---
Use CorePush lib
It's very lightweight. I use it across all my projects to send Firebase Android/WebPush and Apple iOS push notifications. Useful links:
NuGet package
Documentation
The interface is very simple and minimalistic:
Send APN message:
var apn = new ApnSender(settings, httpClient);
await apn.SendAsync(notification, deviceToken);
It can also send Android FCM message if needed:
var fcm = new FcmSender(settings, httpClient);
await fcm.SendAsync(deviceToken, notification);
can you try adding version information to your request after the apns-topic line as below? It ran to completion and I got a "bad device token" error for the first time after adding the following line.
request.Version = new Version(2, 0);
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
I saw the version setting command at the post below.
How to implement apple token based push notifications (using p8 file) in C#?
I've already commented on the answer from #civilator. But I think, that some people read over it, so I'm posting it again.
This is the code that worked for me. Sorry for the late answer!
private readonly string hostname = "gateway.sandbox.push.apple.com";
private readonly int port = 2195;
public async Task<RestResult<JObject>> SendPushNotification(string deviceToken, string message)
{
this.log?.LogInformation("Trying to send push notification.");
X509Certificate2Collection certificatesCollection;
// Setup and read the certificate
// NOTE: You should get the certificate from your apple developer account.
try
{
string certificatePath = Settings.Apn.Certificate;
X509Certificate2 clientCertificate = new X509Certificate2(
File.ReadAllBytes(certificatePath),
Settings.Apn.Password);
certificatesCollection = new X509Certificate2Collection(clientCertificate);
this.log?.LogInformation("Setup certificates.");
}
catch (Exception e)
{
this.log?.LogError(e.ToString());
return new RestResult<JObject> { Result = "exception", Message = "Failed to setup certificates." };
}
// Setup a tcp connection to the apns
TcpClient client = new TcpClient(AddressFamily.InterNetwork);
this.log?.LogInformation("Created new TcpClient.");
try
{
IPHostEntry host = Dns.GetHostEntry(this.hostname);
await client.ConnectAsync(host.AddressList[0], this.port);
this.log?.LogInformation($"Opened connection to {this.hostname}:{this.port}.");
}
catch (Exception e)
{
this.log?.LogError("Failed to open tcp connection to the apns.");
this.log?.LogError(e.ToString());
}
// Validate the Certificate you get from the APN (for more information read the documentation:
// https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html#//apple_ref/doc/uid/TP40008194-CH8-SW1).
SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(this.ValidateServerCertificate),
null);
try
{
await sslStream.AuthenticateAsClientAsync(this.hostname, certificatesCollection, SslProtocols.Tls, false);
MemoryStream memoryStream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(memoryStream);
writer.Write((byte)0);
writer.Write((byte)0);
writer.Write((byte)32);
writer.Write(HexStringToByteArray(deviceToken.ToUpper()));
// Creating an payload object to send key values to the apns
JObject aps = new JObject
{
new JProperty("alert", message),
new JProperty("badge", 0),
new JProperty("sound", "default")
};
JObject payload = new JObject
{
new JProperty("aps", aps)
};
string payloadString = JsonConvert.SerializeObject(payload);
writer.Write((byte)0);
writer.Write((byte)payloadString.Length);
byte[] b1 = System.Text.Encoding.UTF8.GetBytes(payloadString);
writer.Write(b1);
writer.Flush();
byte[] array = memoryStream.ToArray();
sslStream.Write(array);
sslStream.Flush();
client.Dispose();
}
catch (AuthenticationException ex)
{
this.log?.LogError(ex.ToString());
client.Dispose();
return new RestResult<JObject> { Result = "exception", Message = "Authentication Exception." };
}
catch (Exception e)
{
this.log?.LogError(e.ToString());
client.Dispose();
return new RestResult<JObject> { Result = "exception", Message = "Exception was thrown." };
}
this.log?.LogInformation("Notification sent.");
return new RestResult<JObject> { Result = "success", Message = "Notification sent. Check your device." };
}
#region Helper methods
private static byte[] HexStringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
// The following method is invoked by the RemoteCertificateValidationDelegate.
private bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
this.log?.LogInformation("Server Certificate validated.");
return true;
}
this.log?.LogError($"Server Certificate error: {sslPolicyErrors}");
// Do not allow this client to communicate with unauthenticated servers.
return false;
}
#endregion

Upload image to Skype BOT

I have a bot develop with Microsoft Bot Framework, and in Debug run correctly
After the install on Skype, after the upload the image I have a link like this
https://df-apis.skype.com/v2/attachments/0-eus-d4-7e19a097c62f5fc21dd53eabfa19d85e/views/original
The code is very simply and run without skype
if ((activity.Attachments != null) && (activity.Attachments.Count > 0))
{
analysisResult = await AnalyzeUrl(activity.Attachments[0].ContentUrl);
}
........
How do I find the picture that I sent?
According to this comment, to fetch an attachment, the GET request should contain JwtToken of the bot as authorization header:
var attachment = activity.Attachments?.FirstOrDefault();
if (attachment?.ContentUrl != null)
{
using (var connectorClient = new ConnectorClient(new Uri(activity.ServiceUrl)))
{
var token = await (connectorClient.Credentials as MicrosoftAppCredentials).GetTokenAsync();
var uri = new Uri(attachment.ContentUrl);
using (var httpClient = new HttpClient())
{
if (uri.Host.EndsWith("skype.com") && uri.Scheme == Uri.UriSchemeHttps)
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream"));
}
else
{
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(attachment.ContentType));
}
var attachmentData = await httpClient.GetByteArrayAsync(uri);
analysisResult = await AnalyzeUrl(attachmentData);
}
}
}
That means you have to change the AnalyzeUrl to accept the image data instead of URL. I believe CognitiveServices, you are using, are able to accept the image data.

Categories