Authentication information is not given in the correct format error when calling Azure Rest API in Microsoft Flow - rest

When I try to call this https://learn.microsoft.com/en-us/rest/api/storageservices/get-blob-metadata api in microsoft flow I always get this error with 400 bad request.
I edited my authorization header regarding this answer https://stackoverflow.com/a/22029178/10389562 but couldn't get what am I doing wrong.
Method: GET
Uri: https://myaccount.blob.core.windows.net/containername/blobname?comp=metadata
Headers :
{
"Authorization": "SharedKey storageaccountname: primary key in the storage
account properties",
"x-ms-date": "Thu, 21 Sep 2018 23:45:00 GMT",
"x-ms-version": "2018-03-28"
}
After call this API I got this output
<?xml version="1.0" encoding="utf-8"?><Error>
<Code>InvalidAuthenticationInfo</Code><Message>Authentication information is
not given in the correct format. Check the value of Authorization header.
RequestId:f3b3051b-601e-00a4-4b3c-51c58d000000
Time:2018-09-20T23:46:40.6659210Z</Message></Error>
Thanks for any help

Update
In Microsoft Flow, calling Rest Api against Azure Storage seems not a valid way. Authorization needs x-ms-* headers sent by the flow(like x-ms-tracking-id,x-ms-workflow-id,etc.) adding to stringStr, which is not under our control. What's more, signature is only valid for 15m since generated.
There's a built-in Get Blob Metadata action. And for Storage, other common actions are available as well.
To set blob metadata, I suggest to host the logic in Azure function.
Follow this tutorial to create Function app and a httptrigger function, remember to choose the Storage Account where we need to set blob metadata.
Replace the httptrigger sample with code below, and modify metadataName to what you need.
#r "Microsoft.WindowsAzure.Storage"
using System.Net;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
dynamic data = await req.Content.ReadAsAsync<object>();
if (data == null)
{
return req.CreateResponse(HttpStatusCode.BadRequest, "No request body posted");
}
else
{
string metadata = data.metadata;
string blobName = data.blobName;
string containerName = data.containerName;
try
{
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(Environment.GetEnvironmentVariable("AzureWebJobsStorage"));
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer blobContainer = blobClient.GetContainerReference(containerName);
CloudBlob blob = blobContainer.GetBlobReference(blobName);
blob.Metadata.Add("metadataName", metadata);
blob.SetMetadata();
}
catch (Exception e)
{
log.Error(e.ToString());
return req.CreateResponse(HttpStatusCode.InternalServerError, "Fail to set metadata");
}
return (string.IsNullOrEmpty(metadata) || string.IsNullOrEmpty(blobName) || string.IsNullOrEmpty(containerName))
? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass necessary parameters in the request body")
: req.CreateResponse(HttpStatusCode.OK, $"Metadata of {blobName} has been set");
}
}
In Microsoft Flow, create a Http action, post content below to the function url got in step 2.
{
"metadata": "test",
"blobName":"myblob",
"containerName":"mycontainer"
}

Related

Retrieving SendGrid Transactional Templates List

I have been trying to retrieve list of SendGrid transactional templates using API. I'm using correct API key and getting an empty array while there are about 5 transactional templates existing in my SendGrid account. Here is the response:
{
"templates": []
}
Any guesses what could be wrong?
Any guesses what could be wrong?
Yep, their documentation could be!
I also stuck with the problem and finally managed to solve it once I opened the devtools and saw how they request their own API from the UI. Long story short - one has to pass additional generations=dynamic query parameter. Here is the C# code I use:
var client = new SendGridClient("key");
var response = await client.RequestAsync(
SendGridClient.Method.GET,
urlPath: "/templates",
queryParams: "{\"generations\": \"dynamic\"}");
Using Api 7.3.0 PHP
require("../../sendgrid-php.php");
$apiKey = getenv('SENDGRID_API_KEY');
$sg = new \SendGrid($apiKey);
#Comma-delimited list specifying which generations of templates to return. Options are legacy, dynamic or legacy,dynamic
$query_params = json_decode('{"generations": "legacy,dynamic"}');
try {
#$response = $sg->client->templates()->get();
$response = $sg->client->templates()->get(null, $query_params);
echo $response->body();
exit;
} catch (Exception $e) {
echo '{"error":"Caught exception: '. $e->getMessage().'"}';
}
I had the same problem using the python wrapper provided by Sendgrid.
My code was similar to this:
response = SendGridAPIClient(<your api key>).client.templates.get({'generations': 'legacy,dynamic'})
This returned an empty array.
To fix you have to name the param or to pass None before the dict:
response = SendGridAPIClient(<your api key>).client.templates.get(None, {'generations': 'legacy,dynamic'})
or
response = SendGridAPIClient(<your api key>).client.templates.get(query_params={'generations': 'legacy,dynamic'})

How to throw custom error message from API Gateway custom authorizer

Here in the blue print says, API gateway will respond with 401: Unauthorized.
I wrote the same raise Exception('Unauthorized') in my lambda and was able to test it from Lambda Console. But in POSTMAN, I'm receiving status 500
with body:
{
message: null`
}
I want to add custom error messages such as "Invalid signature", "TokenExpired", etc., Any documentation or guidance would be appreciated.
This is totally possible but the docs are so bad and confusing.
Here's how you do it:
There is an object called $context.authorizer that you have access to in your gateway responses template. You can read more about it here: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
Here is an examample of populating this authorizer object from your authorizer lambda like so:
// A simple TOKEN authorizer example to demonstrate how to use an authorization token
// to allow or deny a request. In this example, the caller named 'user' is allowed to invoke
// a request if the client-supplied token value is 'allow'. The caller is not allowed to invoke
// the request if the token value is 'deny'. If the token value is 'Unauthorized', the function
// returns the 'Unauthorized' error with an HTTP status code of 401. For any other token value,
// the authorizer returns an 'Invalid token' error.
exports.handler = function(event, context, callback) {
var token = event.authorizationToken;
switch (token.toLowerCase()) {
case 'allow':
callback(null, generatePolicy('user', 'Allow', event.methodArn));
break;
case 'deny':
callback(null, generatePolicy('user', 'Deny', event.methodArn));
break;
case 'unauthorized':
callback("Unauthorized"); // Return a 401 Unauthorized response
break;
default:
callback("Error: Invalid token");
}
};
var generatePolicy = function(principalId, effect, resource) {
var authResponse = {};
authResponse.principalId = principalId;
if (effect && resource) {
var policyDocument = {};
policyDocument.Version = '2012-10-17';
policyDocument.Statement = [];
var statementOne = {};
statementOne.Action = 'execute-api:Invoke';
statementOne.Effect = effect;
statementOne.Resource = resource;
policyDocument.Statement[0] = statementOne;
authResponse.policyDocument = policyDocument;
}
// Optional output with custom properties of the String, Number or Boolean type.
authResponse.context = {
"stringKey": "stringval custom anything can go here",
"numberKey": 123,
"booleanKey": true,
};
return authResponse;
}
They key here is adding this part:
// Optional output with custom properties of the String, Number or Boolean type.
authResponse.context = {
"stringKey": "stringval custom anything can go here",
"numberKey": 123,
"booleanKey": true,
};
This will become available on $context.authorizer
I then set the body mapping template in gateway responses tab like this:
{"message":"$context.authorizer.stringKey"}
NOTE: it must be quoted!
finally - after sending a request in postman with Authorization token set to deny I now get back a payload from postman that looks like this:
{
"message": "stringval custom anything can go here"
}
I used #maxwell solution, using custom resource ResponseTemplates. For deny response see below:
{
"success":false,
"message":"Custom Deny Message"
}
You can check this out here: https://github.com/SeptiyanAndika/serverless-custom-authorizer
I'm not sure what is causing the 500 message: null response. Possibly misconfiguration of the Lambda function permissions.
To customize the Unauthorized error response, you'll set up a Gateway Response for the UNAUTHORIZED error type. You can configure response headers and payload here.
Maxwell is mostly correct. I tried his implementation and noticed that his message should go from :
{"message":"$context.authorizer.stringKey"}
to
{"message":"$context.authorizer.context.stringKey"}
As noted by Connor far as I can see, the answer to the specific question - which mentions 401 related errors - is NO.
You can produce a generic 401 Unauthorized but you cannot alter the error message.
That is you can customise the 403 Forbidden (DENY) messages but not the 401's.
Note that I've used the NodeJS Lambda custom authorizers but not the Python version referenced in the question.
With my testing what i observed is , You cannot customize message when you throw exception from the lambda,
You can have customized messages when you return DENY Policy message from the authorizer
Here is how i am returning custom message when i DENY from the Authorizer, it in the detail field of
authResponse.context returned from custom Authorizer
you can also update status code to 401 instead of 403 .
This can be easily achieved by using the context.fail() function.
Example:
const customAuthorizer: Handler = (event, context: Context, callback: Callback) => {
authenticate(event)
.then((res) => {
// result should be as described in AWS docs
callback(null, res);
})
.catch((err) => {
context.fail("Unauthorized");
});
}
This will return a 401 response with following body.
{
"message": "Unauthorized"
}
This can also be achieved by throwing an error:
throw new Error('Unauthorized');

How to call Windows Azure Storage RestFul Service without SDK?

I try to use Windows Azure like a Storage fom Salesforce.com.
I cheked the documentation and I only can see call the calls to azure rest api from SDK (Java, .Net, JS, etc) examples.
I need integrate Salesforce with Windows Azure Storage but, Azure don't have a SDK for Salesforce.com
From Salesforce.com is allow the calls to rest services but the process to call Azure Rest Services require one o more librarys.
Exameple:
Authentication for the Azure Storage Services require of:
Headers: Date Header and Authorization Header
The Authorization Header require two elments
SharedKey
Account Name
Authorization="[SharedKey|SharedKeyLite] :"
SharedKey and Account Name give a conversion:
HMAC-SHA256 conversion
over UTF-8 encoded
For this convertion the documentation referes to SDK Librarys in others words Java Class or .Net Class type helper that in Salesforce.com not exist.
Please, I need a example to call the authentification service without sdk
Sorry for my bad English.
Visit: https://learn.microsoft.com/en-us/rest/api/storageservices/fileservices/authentication-for-the-azure-storage-services
I need a example to call the authentification service without sdk
We could generate signature string and specify Authorization header for the request of performing Azure storage services without installing SDK. Here is a simple working sample to list the containers, you could refer to my generateAuthorizationHeader function and Authentication for the Azure Storage Services to construct the signature string.
string StorageAccount = "mystorageaccount";
string StorageKey = "my storage key";
string requestMethod = "GET";
string mxdate = "";
string storageServiceVersion = "2014-02-14";
protected void btnlist_Click(object sender, EventArgs e)
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(string.Format(CultureInfo.InvariantCulture,
"https://{0}.blob.core.windows.net/?comp=list",
StorageAccount
));
req.Method = requestMethod;
//specify request header
string AuthorizationHeader = generateAuthorizationHeader();
req.Headers.Add("Authorization", AuthorizationHeader);
req.Headers.Add("x-ms-date", mxdate);
req.Headers.Add("x-ms-version", storageServiceVersion);
using (HttpWebResponse response = (HttpWebResponse)req.GetResponse())
{
var stream = response.GetResponseStream();
StreamReader reader = new StreamReader(stream);
string content = reader.ReadToEnd();
StringReader theReader = new StringReader(content);
DataSet theDataSet = new DataSet();
theDataSet.ReadXml(theReader);
DataTable dt = theDataSet.Tables[2];
}
}
public string generateAuthorizationHeader()
{
mxdate = DateTime.UtcNow.ToString("R");
string canonicalizedHeaders = string.Format(
"x-ms-date:{0}\nx-ms-version:{1}",
mxdate,
storageServiceVersion);
string canonicalizedResource = string.Format("/{0}/\ncomp:list", StorageAccount);
string stringToSign = string.Format(
"{0}\n\n\n\n\n\n\n\n\n\n\n\n{1}\n{2}",
requestMethod,
canonicalizedHeaders,
canonicalizedResource);
HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(StorageKey));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
String authorization = String.Format("{0} {1}:{2}",
"SharedKey",
StorageAccount,
signature
);
return authorization;
}
Besides, please refer to Azure Storage Services REST API Reference to know more about programmatic access to Azure Storage Services via REST APIs.
I find a way to solve this.
You should use Shared Sing, here explain me:
Enter to Portal Azure
Open the Account Storage
In the General Information click on "Share sing access"
Enable all permissions that you need (In my case only Enable "File")
Enable all resources permission that you need (In my case onl Enable "Service, Container and Object")
Define and Start Date and End Date (This is the space of time that Shared Key will be valid)
Define protocol type (In my case use HTTPS)
Clic on "Generate SAS" button
After this process you will get a token like this:
?sv=2016-05-31&ss=f&srt=sco&sp=rwdlc&se=2017-11-28T04:29:49Z&st=2017-02-18T20:29:49Z&spr=https&sig=rt7Loxo1MHGJqp0F6ryLhYAmOdRreyiYT418ybDN2OI%3D
You have to use this Token like Autentication
Example Call Code List a Content:
public with sharing class CallAzureRestDemo {
public string token = '&sv=2016-05-31&ss=f&srt=sco&sp=rwdlc&se=2017-02-19T04:00:44Z&st=2017-02-18T20:00:44Z&spr=https&sig=GTWGQc5GOAvQ0BIMxMbwUpgag5AmUVjrfZc56nHkhjI%3D';
//public Integer batchSize;
public CallAzureRestDemo(){}
public void getlistcontent(String endpoint)
{
// Create HTTP GET request
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setEndpoint(endpoint+token);
Http http = new Http();
HTTPResponse res;
System.debug(LoggingLevel.INFO, '##RESPONSE: '+res);
// only do this if not running in a test method
if(!Test.isRunningTest())
{
System.debug(LoggingLevel.INFO, 'Sending the message to Azure');
res = http.send(req);
System.debug(LoggingLevel.INFO, 'http.send result status: ' + res.getStatus());
}
else
{
System.debug(LoggingLevel.INFO, 'Running in a test so not sending the message to Azure');
}
}
}
Example TestMethod:
#isTest
private class Test_CallAzureRestDemo {
static testMethod void myUnitTest() {
CallAzureRestDemo oRest = new CallAzureRestDemo();
try{
//Call the method and set endpoint
oRest.getlistcontent('https://accountstoragecomex.file.core.windows.net/?comp=list');
}catch(Exception e){
System.debug('##'+e);
}
}
}
Example to Response:
20:15:47.64 (79388244)|CALLOUT_REQUEST|[100]|System.HttpRequest[Endpoint=https://accountstoragecomex.file.core.windows.net/?comp=list&sv=2016-05-31&ss=f&srt=sco&sp=rwdlc&se=2017-02-19T04:00:44Z&st=2017-02-18T20:00:44Z&spr=https&sig=GTWGQc5GOAvQ0BIMxMbwUpgag5AmUVjrfZc56nHkhjI%3D, Method=GET]
20:15:47.64 (395755012)|CALLOUT_RESPONSE|[100]|System.HttpResponse[Status=OK, StatusCode=200]
Example Call Service "FILE - Get List Share"
Call To List Content
One more time, Sorry for my bad english.

SendGrid incoming mail webhook - how to save the JSON format email data into my application folder in C#

This is regarding Sendgrid incoming mail webhook, I have referred this URL SendGrid incoming mail webhook - how do I secure my endpoint, and got some idea how to go about this, but, as I am new to MVC / WebAPI, could anyone give me the controller method code snippet to catch the JSON format HTTP post and save to my application folder.
This is the solution I found after googling and with slight modifications:
[HttpPost, HttpGet]
[EnableCors(origins: "*", headers: "*", methods: "*")]
public async Task Post()
{
if (Request.Content.IsMimeMultipartContent("form-data"))
try
{
//To get complete post in a string use the below line, not used here
string strCompletePost = await Request.Content.ReadAsStringAsync();
HttpContext context = HttpContext.Current;
string strFrom = context.Request.Form.GetValues("from")[0];
string strEmailText = context.Request.Form.GetValues("email")[0];
string strSubject = context.Request.Form.GetValues("subject")[0];
//Not useful I guess, because it always return sendgrid IP
string strSenderIP = context.Request.Form.GetValues("sender_ip")[0];
}
catch (Exception ex)
{
}
}
I tried, retrieving the values as
String to = context.Request.Params["to"];
but, the value returned is not consistent, i.e. most of the times it is returning null and occasionally returns actual value stored in it.
If anyone have a better solution, please let me know.
Thank you
If for some reason ["to"] doesn't work for you, try to get ["envelope"] value,
context.Request.Form.GetValues("envelope")[0]
which looks like
{"to":["emailto#example.com"],"from":"emailfrom#example.com"}

Yammer API - Posting to External Networks

I've used the Yammer API extensively for accessing current users internal network. All API calls have been working correctly (GET's and POST's) with the original token extracted from;
"https://www.yammer.com/oauth2/access_token.json?client_id={App ID}&client_secret={App Secret}&code={Access Code}"
and using the headers; "Authorization : Bearer {Token}" and "Cookie : {Cookies Received from HTML request}.
I've gotten the tokens for all accessible networks using;
"https://www.yammer.com/api/v1/oauth/tokens.json".
Accessing external networks beyond this point has proved troublesome. I changed the header to "Authorization : Bearer {NetworkToken}". While I am able to GET details from external networks, I cannot POST to external networks. I always receive a '401 Unauthorized' response. The 'Unauthorized' requests include deleting messages and liking messages in external networks.
Is there another step between being able to read data from an external network and enabling POST methods?
If I could get any insight into this i'd be extremely grateful!
Cheers!
When accessing external networks, you need to set the authToken to the authToken for that external network.
Step 1 - Get all auth tokens:
yam.platform.request({
url: "oauth/tokens.json",
type: 'GET',
success: function (msg) {
accessTokens = msg;
/....
},
error: function (msg) {
console.log(msg);
error(msg);
}
Step 2: Set the authToken to the correct external network
var currentToken = "";
$.each(accessTokens, function (i,val) {
if (val.network_permalink == $.cookie('networkPermalink')) {
currentToken = val;
}
});
While I was working on a project last month, I used the following way to post message.
The message has to be Byte encrypted in UTF-8 format.
Specify the content type as "application/x-www-form-urlencoded".
So, an example code would be:
HttpWebRequest a = (HttpWebRequest)WebRequest.Create(postUrl);
a.Headers.Add("Authorization", "Bearer" + authToken);
a.Method = "POST";
byte[] message = Encoding.UTF8.GetBytes("body=" + message + "&replied_to_id=" + threadID);
a.ContentType = "application/x-www-form-urlencoded";
a.ContentLength = message.Length;
using (var postStream = request.GetRequestStream())
{
postStream.Write(message, 0, message.Length);
}
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
using (var postStreamForResponse = response.GetResponseStream())
{
StreamReader postReader = new StreamReader(postStreamForResponse);
string results = postReader.ReadToEnd();
postReader.Close();
}
I've discovered quite a few inconsistencies quirks with the Yammer API. I've figured out external networks in their totality now. Here are some things that may not be clear;
When doing a POST or DELETE request, do not include the network_permalink in the url! Only include the network_permalink when you're doing a GET request. This was my main issue.
Required request headers;
Content-Type : application/x-www-form-urlencoded
Accept : application/json
Cookie : _workfeed_session_id=(A code that can be extracted from the response from your first request with an auth token)
Authorization : Bearer (Access token for whichever network you wish to access)
Oh and just FYI, to request threads within the 'All Company' group this is the url; https://www.yammer.com/(network_permalink)/api/v1/messages/general.json
Thanks for the answers!