I wrote a function to fetch data using query from Azure Cosmos Database in Flutter/Dart, however it's giving error :
response.body: {"code":"BadRequest","message":"Message: {\"Errors\":[\"The input content is invalid because the required properties - 'æ©; ' - are missing\"]}\r\nActivityId: f75a0c6e-2c8d-4f13-a020-6e3c13fa5458, Request URI: /apps/f4533d11-81e3-4512-b639-0f0475c10611/services/401c9130-a85e-46a6-8311-c2dc8e5070d6/partitions/b5d2a58d-1304-414b-92c7-10e7fa95f679/replicas/131768862196689298p, RequestStats: , SDK: Microsoft.Azure.Documents.Common/2.0.0.0"}
I/flutter ( 5284): response.status: 400
Here is my piece of code
final response = await http.post(
endpointResource,
// Query
body: query,
// Post new message
headers: {
HttpHeaders.AUTHORIZATION: authToken,
HttpHeaders.CONTENT_TYPE: "application/query+json",
//'content-type': 'application/json',
'Accept': 'application/json',
//c.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
'x-ms-version': '2017-02-22',
'x-ms-date': date,
'x-ms-documentdb-isquery': 'true',
'x-ms-query-enable-crosspartition': 'true',
'x-ms-documentdb-query-enable-scan': 'true',
},
);
What should I do to get data back?
I managed to translate a code snippet from a working example in Javascript to Dart.
import 'dart:io';
import 'dart:convert';
import 'dart:async';
import 'dart:core';
import 'package:crypto/crypto.dart';
import 'package:hex/hex.dart';
class Cosmos{
String documentDBMasterKey;
Cosmos({this.documentDBMasterKey});
Future queryCosmos({url, method, body}) async{
String auth;
String documentDBMasterKey = this.documentDBMasterKey;
print("mastKey: $documentDBMasterKey");
method = method.trim(); //GET, POST, PUT or DEL
url = url.trim();
String utcString = HttpDate.format(DateTime.now());
print('RFC1123time: $utcString');
print('request url = ' + url);
String strippedurl =
url.replaceAllMapped(RegExp(r'^https?://[^/]+/'), (match) {
return '/';
});
print('stripped Url: $strippedurl');
List strippedparts = strippedurl.split('/');
int truestrippedcount = strippedparts.length - 1;
print('$truestrippedcount');
String resourceId;
String resType;
if (truestrippedcount % 2 != 0){
print('odd');
resType = strippedparts[truestrippedcount];
print('$resType');
if (truestrippedcount > 1){
int lastPart = strippedurl.lastIndexOf('/');
resourceId = strippedurl.substring(1, lastPart);
print('ResourceId: ' + resourceId);
}
}
else{
print('even');
resType = strippedparts[truestrippedcount -1];
print('resType: $resType');
strippedurl = strippedurl.substring(1);
print('strippedurl $strippedurl');
resourceId = strippedurl;
print('ResourceId: ' + resourceId);
}
String verb = method.toLowerCase();
String date = utcString.toLowerCase();
Base64Codec base64 = const Base64Codec();
var key = base64.decode(documentDBMasterKey); //Base64Bits --> BITS
print('key = ${HEX.encode(key)}');
print('masterKey = $documentDBMasterKey');
String text = (verb ?? '').toLowerCase() + '\n' +
(resType ?? '').toLowerCase() + '\n' +
(resourceId ?? '') + '\n' +
(date ?? '').toLowerCase() + '\n' +
'' + '\n';
print('text: $text');
var hmacSha256 = Hmac(sha256, key);
List<int> utf8Text = utf8.encode(text);
var hashSignature = hmacSha256.convert(utf8Text);
String base64Bits = base64.encode(hashSignature.bytes);
//Format our authentication token and URI encode it.
var masterToken = "master";
var tokenVersion = "1.0";
auth = Uri.encodeComponent('type=' + masterToken + '&ver=' + tokenVersion + '&sig=' + base64Bits);
print('auth= $auth');
Map<String, String> headers = {
'Accept': 'application/json',
'x-ms-version': '2016-07-11',
'Authorization': auth,
'x-ms-date': utcString,
'x-ms-documentdb-isquery' : 'true',
'Content-Type' : 'application/query+json',
'x-ms-documentdb-query-enablecrosspartition' : 'true',
};
Future<String> readResponse(HttpClientResponse response) {
final completer = Completer<String>();
final contents = StringBuffer();
response.transform(utf8.decoder).listen((data) {
contents.write(data);
}, onDone: () => completer.complete(contents.toString()));
return completer.future;
}
HttpClientRequest request;
HttpClient httpClient = new HttpClient();
if (method=='GET'){
request = await httpClient.getUrl(Uri.parse(url));
}
else if(method=='POST'){
request = await httpClient.postUrl(Uri.parse(url));
}
else if(method=='PUT'){
request = await httpClient.putUrl(Uri.parse(url));
}
else if(method=='DEL'){
request = await httpClient.deleteUrl(Uri.parse(url));
}
headers.forEach((key, value) {
request.headers.set(key,value);
});
if(body != null) {
request.add(utf8.encode(json.encode(body)));
}
HttpClientResponse aresponse = await request.close();
httpClient.close();
String aresponseString = await readResponse(aresponse);
return jsonDecode(aresponseString);
}
}
Just instantiate the class with your Cosmos Master Key:
Cosmos cosmos = Cosmos( documentDBMasterKey:'{your_cosmos_db_master_key}');
Query Cosmos Db by calling queryCosmos method. Pass 'url', 'method' and an optional 'body' as parameters:
// GET Example
() async {
Map<String, dynamic> get_dbs = await cosmos.queryCosmos(
url: 'https://{your_base_url}.documents.azure.com:443/dbs', method: 'GET');
print(get_dbs);
}
// POST Example (Query)
() async {
final Map<String, dynamic> body = {
"query":
"SELECT * FROM Faults f WHERE f.FaultId = #faultId",
"parameters": [
{"name": "#faultId", "value": 306000}
]
};
Map<String, dynamic> get_fault = await cosmos.queryCosmos(
url:
'https://{your_base_url}.documents.azure.com:443/dbs/{your_db}/colls/{your_collection}/docs',
method: 'POST',
body: body);
print('Get fault $get_fault');
}
Original code (JavaScript - Download to Postman and check pre-req script): https://github.com/MicrosoftCSA/documentdb-postman-collection
My code (dart): https://github.com/fvarela/cosmos_db_dart
I've created a Dart library to fetch data from a CosmosDB. For example to fetch documents from a collection you can just call this function:
import 'package:cosmosdb/cosmosdb.dart';
void main() {
final cosmosDB = CosmosDB(
masterKey: '<YOUR_MASTER_KEY>',
baseUrl: '<YOUR_BASE_URL>',
);
// get all documents from a collection
final documents = cosmosDB.documents.list('<YOUR_DATABASE>', '<YOUR_COLLECTION>');
print(documents);
}
GitHub: https://github.com/jonasfranz/cosmosdb
Package: https://pub.dev/packages/cosmosdb
Related
I'm creating an app in which I need to post tweets in twitter on behalf of different users. So, I using Application User authentication. For me to redirect the user to twitter page to provide access I need oauth_token from twitter.
For that I am using Request_token_api.
But everytime am getting could not authenticate you and the twitter api page says authorization is not required for this API
I tried using many libraries like passport, Twit, OAuth, etc.. But nothing worked and I am pretty sure all my developer account consumer_key, consumer_secret, access_token, access_token_secret are correct.
function getAuthorization(httpMethod, baseUrl, reqParams) {
// Get acces keys
const consumerKey = keysJson.TWITTER_CONSUMER_KEY,
consumerSecret = keysJson.TWITTER_CONSUMER_SECRET,
accessToken = keysJson.TWITTER_ACCESS_TOKEN,
accessTokenSecret = keysJson.TWITTER_ACCESS_TOKEN_SECRET;
// timestamp as unix epoch
let timestamp = Math.round(Date.now() / 1000);
// nonce as base64 encoded unique random string
let nonce = crypto.randomBytes(32).toString('base64').replace(/[^0-9a-zA-Z]/g, '') ;
let cburl = percentEncode(keysJson.cb);
// generate signature from base string & signing key
let baseString = oAuthBaseString(httpMethod, baseUrl, reqParams, consumerKey, accessToken, timestamp, nonce);
console.log(baseString);
let signingKey = oAuthSigningKey(consumerSecret, accessTokenSecret);
// console.log(signingKey);
let signature = oAuthSignature(baseString, signingKey);
console.log(signature);
// return interpolated string
return 'OAuth ' +
'oauth_nonce="' + nonce + '", ' +
'oauth_callback="' + cburl + '", ' +
'oauth_signature_method="HMAC-SHA1", ' +
'oauth_timestamp="' + timestamp + '", ' +
'oauth_consumer_key="' + consumerKey + '", ' +
'oauth_signature="' + signature + '", ' +
'oauth_version="1.0"' ;
}
function oAuthBaseString(method, url, params, key, token, timestamp, nonce) {
return method
+ '&' + percentEncode(url)
+ '&' + percentEncode(genSortedParamStr(params, key, token, timestamp, nonce));
};
function oAuthSigningKey(consumer_secret, token_secret) {
return consumer_secret + '&' + token_secret;
};
function oAuthSignature(base_string, signing_key) {
var signature = hmac_sha1(base_string, signing_key);
return percentEncode(signature);
};
function percentEncode(str) {
return encodeURIComponent(str).replace(/[!*()']/g, (character) => {
return '%' + character.charCodeAt(0).toString(16);
});
};
function hmac_sha1(string, secret) {
let shaObj = new jsSHA("SHA-1", "TEXT");
shaObj.setHMACKey(secret, "TEXT");
shaObj.update(string);
let hmac = shaObj.getHMAC("B64");
return hmac;
};
function mergeObjs(obj1, obj2) {
for (var attr in obj2) {
obj1[attr] = obj2[attr];
}
return obj1;
};
function genSortedParamStr(params, key, token, timestamp, nonce) {
// Merge oauth params & request params to single object
let paramObj = mergeObjs(
{
include_entities: 'true',
oauth_consumer_key : key,
oauth_nonce : nonce,
oauth_signature_method : 'HMAC-SHA1',
oauth_timestamp : timestamp,
// oauth_token : token,
oauth_version : '1.0'
},
);
// Sort alphabetically
let paramObjKeys = Object.keys(paramObj);
let len = paramObjKeys.length;
paramObjKeys.sort();
// Interpolate to string with format as key1=val1&key2=val2&...
let paramStr = paramObjKeys[0] + '=' + paramObj[paramObjKeys[0]];
for (var i = 1; i < len; i++) {
paramStr += '&' + paramObjKeys[i] + '=' + percentEncode(decodeURIComponent(paramObj[paramObjKeys[i]]));
}
return paramStr;
};
var redirect_uri = encodeURIComponent('http://localhost:4200/admin/marketing/loginsuccess');
var auth = getAuthorization('POST','https://api.twitter.com/oauth/request_token',{oauth_callback:redirect_uri});
console.log(auth);
var options = {
"method": "POST",
"hostname": "api.twitter.com",
"port": null,
"path": '/oauth/request_token',
"headers": {
'Authorization' : auth
}
};
try {
console.log(options);
var req = https.request(options, function (res1) {
var chunks = [];
console.log('statusCode:', res1.statusCode);
console.log('headers:', res1.headers);
res1.on("data", function (chunk) {
chunks.push(chunk);
});
res1.on("end", function () {
var body = Buffer.concat(chunks);
var disp = JSON.parse(body.toString());
console.log(disp);
});
});
req.end();
} catch (error) {
console.log(error);
res.json({success:false,message:error});
}
I need the oauth access token from Twitter API but am getting.
{ errors: [ { code: 32, message: 'Could not authenticate you.' } ] }
I am struggling for 3 days. Kindly let me know is there any alternate way to implement twitter API in nodejs or some sample code in github.
Thanks in advance..
Hey after struggling to download the excel from server below is the solution i found very easy.But API side they will just read the path and send the files.
How can i differentiate the file type?
If server files are in your project directory or server , we would like to down load the excel or any file directly. Added the below implementation which works only for excel.
API(.net):
public ActionResult Download()
{
string fileName = WebConfigurationManager.AppSettings["filename"];
var filePath = System.Web.HttpContext.Current.Server.MapPath("~/" + fileName);
if (System.IO.File.Exists(filePath))
{
byte[] fileBytes = System.IO.File.ReadAllBytes(filePath);
return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}
else
{
var response = new WebServiceStatus()
{
code = -1,
data = null,
message = "File is Not available"
};
var data = JsonConvert.SerializeObject(response);
return HandleTrivialHttpRequests(data);
}
}
Angular V7
//Declaration
headers: HttpHeaders;
options: any;
//Constructor or u can have for specific method
this.headers = new HttpHeaders({ 'Content-Type': 'application/json' });
this.options = {
observe: 'response',
headers: this.headers,
responseType: 'arraybuffer'
};
//Service request:
this.httpClient.post('http://localhost:8080/api/report', this.data,
this.option)
.pipe(
catchError(err => this.handleError(err))
).subscribe(response => {
Helper.exportExelFile(response, 'FileName');
});
//In component or in helper function in one class, I have used helper
function which can be reused in other places also
import * as FileSaver from 'file-saver';
function exportExelFile(data, filename) {
const blobValue = new Blob([data['body']], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
FileSaver.saveAs(blobValue, filename + '.' + FileType.EXCEL);
}
export const Helper = {
exportExelFile
};
I am trying to run the following code:
public class Item
{
[JsonProperty(PropertyName = "api-key")]
public string apikey { get; set; }
}
[[some method]]{
var url = "https://[search service name].search.windows.net/indexes/temp?api-version=2016-09-01";
using (var httpClient = new HttpClient())
{
using (var request = new HttpRequestMessage(HttpMethod.Put,url))
{
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var sItem = new Item { apikey = [AzureSearchAdminKey] };
var tststring = JsonConvert.SerializeObject(sItem);
var body=new StringContent(tststring, Encoding.UTF8,"application/json" );
request.Content = body;
request.Method = HttpMethod.Put;
using (HttpResponseMessage response = httpClient.SendAsync(request).Result)
{
var stringr = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(stringr);
Console.ReadLine();
}
}
}
}
I get the following error:
"Error reading JObject from JsonReader. Path '', line 0, position 0."
Could someone from search team tell me what I did wrong?
The api key should be in the HTTP header, and the index definition should be in the HTTP body.
Here's some sample code for creating a data source, index, and indexer, which reads from an Azure SQL DB and indexes its rows.
class Program
{
static void Main(string[] args)
{
var searchServiceName = "[search service name]";
var apiKey = "[api key]";
var dataSourceName = "[data source name]";
var indexName = "[index name]";
var indexerName = "[indexer name]";
var azureSqlConnectionString = "[Azure SQL connection string]";
var azureSqlTableName = "[table name]";
using (var httpClient = new HttpClient())
{
var dataSourceDefinition = AzureSqlDatasourceDefinition(azureSqlConnectionString, azureSqlTableName);
var putDataSourceRequest = PutDataSourceRequest(searchServiceName, apiKey, dataSourceName, dataSourceDefinition);
Console.WriteLine($"Put data source {putDataSourceRequest.RequestUri}");
Console.WriteLine();
var putDataSourceResponse = httpClient.SendAsync(putDataSourceRequest).Result;
var putDataSourceResponseContent = putDataSourceResponse.Content.ReadAsStringAsync().Result;
Console.WriteLine(putDataSourceResponseContent);
Console.WriteLine();
var indexDefinition = IndexDefinition();
var putIndexRequest = PutIndexRequest(searchServiceName, apiKey, indexName, indexDefinition);
Console.WriteLine($"Put index {putIndexRequest.RequestUri}");
Console.WriteLine();
var putIndexResponse = httpClient.SendAsync(putIndexRequest).Result;
var putIndexResponseContent = putIndexResponse.Content.ReadAsStringAsync().Result;
Console.WriteLine(putIndexResponseContent);
Console.WriteLine();
var indexerDefinition = IndexerDefinition(dataSourceName, indexName);
var putIndexerRequest = PutIndexerRequest(searchServiceName, apiKey, indexerName, indexerDefinition);
Console.WriteLine($"Put indexer {putIndexerRequest.RequestUri}");
Console.WriteLine();
var putIndexerResponse = httpClient.SendAsync(putIndexerRequest).Result;
var putIndexerResponseContent = putIndexerResponse.Content.ReadAsStringAsync().Result;
Console.WriteLine(putIndexerResponseContent);
Console.WriteLine();
var runIndexerRequest = RunIndexerRequest(searchServiceName, apiKey, indexerName);
Console.WriteLine($"Run indexer {runIndexerRequest.RequestUri}");
Console.WriteLine();
var runIndexerResponse = httpClient.SendAsync(runIndexerRequest).Result;
Console.WriteLine($"Success: {runIndexerResponse.IsSuccessStatusCode}");
Console.ReadLine();
}
}
static HttpRequestMessage PutDataSourceRequest(string searchServiceName, string apiKey, string dataSourceName,
string datasourceDefinition)
{
var request = new HttpRequestMessage(HttpMethod.Put,
$"https://{searchServiceName}.search.windows.net/datasources/{dataSourceName}?api-version=2016-09-01");
request.Headers.Add("api-key", apiKey);
var body = new StringContent(datasourceDefinition, Encoding.UTF8, "application/json");
request.Content = body;
return request;
}
static HttpRequestMessage PutIndexRequest(string searchServiceName, string apiKey, string indexName,
string indexDefinition)
{
var request = new HttpRequestMessage(HttpMethod.Put,
$"https://{searchServiceName}.search.windows.net/indexes/{indexName}?api-version=2016-09-01");
request.Headers.Add("api-key", apiKey);
var body = new StringContent(indexDefinition, Encoding.UTF8, "application/json");
request.Content = body;
return request;
}
static HttpRequestMessage PutIndexerRequest(string searchServiceName, string apiKey, string indexerName,
string indexerDefinition)
{
var request = new HttpRequestMessage(HttpMethod.Put,
$"https://{searchServiceName}.search.windows.net/indexers/{indexerName}?api-version=2016-09-01");
request.Headers.Add("api-key", apiKey);
var body = new StringContent(indexerDefinition, Encoding.UTF8, "application/json");
request.Content = body;
return request;
}
static HttpRequestMessage RunIndexerRequest(string searchServiceName, string apiKey, string indexerName)
{
var request = new HttpRequestMessage(HttpMethod.Post,
$"https://{searchServiceName}.search.windows.net/indexers/{indexerName}/run?api-version=2016-09-01");
request.Headers.Add("api-key", apiKey);
return request;
}
static string AzureSqlDatasourceDefinition(string connectionString, string tableName)
{
return #"
{
""description"": ""azure sql datasource"",
""type"": ""azuresql"",
""credentials"": { ""connectionString"": """ + connectionString + #""" },
""container"": { ""name"": """ + tableName + #""" },
""dataChangeDetectionPolicy"": {
""#odata.type"": ""#Microsoft.Azure.Search.HighWaterMarkChangeDetectionPolicy"",
""highWaterMarkColumnName"": ""highwatermark""
},
""dataDeletionDetectionPolicy"": {
""#odata.type"": ""#Microsoft.Azure.Search.SoftDeleteColumnDeletionDetectionPolicy"",
""softDeleteColumnName"": ""deleted"",
""softDeleteMarkerValue"": ""true""
}
}
";
}
static string IndexDefinition()
{
return #"
{
""fields"": [
{
""name"": ""id"",
""type"": ""Edm.String"",
""key"": true,
""searchable"": true,
""sortable"": true,
""retrievable"": true
},
{
""name"": ""field1"",
""type"": ""Edm.String"",
""searchable"": true,
""retrievable"": true
},
{
""name"": ""field3"",
""type"": ""Edm.Int32"",
""retrievable"": true
}
]
}
";
}
static string IndexerDefinition(string dataSourceName, string indexName)
{
return #"
{
""description"": ""indexer for azure sql datasource"",
""dataSourceName"": """ + dataSourceName + #""",
""targetIndexName"": """ + indexName + #""",
""schedule"": { ""interval"": ""P1D"" }
}
";
}
}
The indexer is scheduled to run once per day. You can set it to run more frequently if the data changes often, but it might affect your search throughput.
This is the table definition if you're interested
CREATE TABLE [dbo].[testtable](
[id] [int] IDENTITY(1,1) NOT NULL,
[field1] [nchar](10) NULL,
[field2] [nchar](10) NULL,
[field3] [int] NULL,
[highwatermark] [timestamp] NOT NULL,
[deleted] [bit] NOT NULL
) ON [PRIMARY]
INSERT INTO [dbo].[testtable] (field1, field2, field3, deleted) VALUES ('abc', 'def', 123, 0)
It looks like you're trying to modify your index definition, but the body of the request contains the api-key instead of the JSON for the index definition. The api-key needs to be in the request headers, not the body.
You might find it simpler to use the Azure Search .NET SDK instead of calling the REST API directly.
REST API has been released in february to set blob CORS property, but this hasn't been implemented for NodeJS yet.
Since I need this feature, I tried to implement it in a module for my azure website running NodeJS.
Based on REST API documentation to change CORS properties and to generate authentification key, on this implementation of authentification key generation using NodeJS, I tried to follow the accepted answer from this post, but it didn't work for me.
Here is what I've got in setcrosproperties.js :
var crypto = require('crypto');
var request = require('request');
exports.setCors = function (MY_ACCOUNT_URL, MY_ACCOUNT_NAME, MY_ACCOUNT_HOST, accountKey) {
var MY_CORS_XML =
'<?xml version="1.0" encoding="utf-8"?>'+
'<StorageServiceProperties>'+
'<Cors>'+
'<CorsRule>'+
'<AllowedOrigins>*</AllowedOrigins>'+
'<AllowedMethods>GET,PUT</AllowedMethods>'+
'<MaxAgeInSeconds>500</MaxAgeInSeconds>'+
'<ExposedHeaders>x-ms-meta-data*,x-ms-meta-customheader</ExposedHeaders>'+
'<AllowedHeaders>x-ms-meta-target*,x-ms-meta-customheader</AllowedHeaders>'+
'</CorsRule>'+
'</Cors>'+
'<DefaultServiceVersion>2013-08-15</DefaultServiceVersion>'+
'</StorageServiceProperties>';
var url = MY_ACCOUNT_URL + '/?restype=service&comp=properties';
var canonicalizedResource = '/' + MY_ACCOUNT_NAME + '/?comp=properties';
var corsMD5 = crypto.createHash('md5' ).update(MY_CORS_XML).digest('base64');
var date = (new Date()).toUTCString();
var headers = {
'x-ms-version': '2013-08-15',
'x-ms-date': date,
'Host': MY_ACCOUNT_HOST
};
var canonicalizedHeaders = buildCanonicalizedHeaders( headers );
// THIS
var key = buildSharedKeyLite( 'PUT', corsMD5, 'text/plain; charset=UTF-8', canonicalizedHeaders, canonicalizedResource, accountKey);
// AND THIS, BOTH YIELD THE SAME SERVER RESPONSE
// var key = buildSharedKeyLite( 'PUT', "", "", canonicalizedHeaders, canonicalizedResource, accountKey);
headers['Authorization'] = 'SharedKeyLite ' + MY_ACCOUNT_NAME + ':' + key;
var options = {
url: url,
body: MY_CORS_XML,
headers: headers
};
console.log("url : " + url);
console.log("canonicalizedResource : " + canonicalizedResource);
console.log("canonicalizedHeaders : " + canonicalizedHeaders);
console.log("corsMD5 : " + corsMD5);
console.log("key : " + key);
console.log("options : " + JSON.stringify(options));
function onPropertiesSet(error, response, body) {
if (!error && response.statusCode == 202) {
console.log("CORS: OK");
}
else {
console.log("CORS: " + response.statusCode);
console.log("body : " + body);
}
}
request.put(options, onPropertiesSet); // require('request')
};
function buildCanonicalizedHeaders( headers ) {
var xmsHeaders = [];
var canHeaders = "";
for ( var name in headers ) {
if ( name.indexOf('x-ms-') == 0 ) {
xmsHeaders.push( name );
}
}
xmsHeaders.sort();
for ( var i = 0; i < xmsHeaders.length; i++ ) {
name = xmsHeaders[i];
canHeaders = canHeaders + name.toLowerCase().trim() + ':' + headers[name] + '\n';
}
return canHeaders;
}
function buildSharedKeyLite( verb, contentMD5, contentType, canonicalizedHeaders, canonicalizedResource, accountKey) {
var stringToSign = verb + "\n" +
contentMD5 + "\n" +
contentType + "\n" +
"" + "\n" + // date is to be empty because we use x-ms-date
canonicalizedHeaders +
canonicalizedResource;
// return crypto.createHmac('sha256', accountKey).update(encodeURIComponent(stringToSign)).digest('base64');
return crypto.createHmac('sha256', new Buffer(accountKey, 'base64')).update(stringToSign).digest('base64');
}
And here is how I call this function from my server.js file :
var setcrosproperties = require('./setcrosproperties.js');
// setCors(MY_ACCOUNT_URL, MY_ACCOUNT_NAME, MY_ACCOUNT_HOST, accountKey)
setcrosproperties.setCors(
'https://'+process.env['AZURE_STORAGE_ACCOUNT']+'.blob.core.windows.net',
process.env['AZURE_STORAGE_ACCOUNT'],
process.env['AZURE_STORAGE_ACCOUNT']+'.blob.core.windows.net',
process.env['AZURE_STORAGE_ACCESS_KEY']);
I did not understand what was the difference intended with variables MY_ACCOUNT_UTL (I assumed URL) and MY_ACCOUNT_HOST, so I use the same value for both parameters of the function.
(I removed the "cors" parameter, which seemed to be unused.)
Here is what I get in the console :
url : https://NAME_OF_MY_STORAGE_ACCOUNT.blob.core.windows.net/?restype=service&comp=properties
canonicalizedResource : /NAME_OF_MY_STORAGE_ACCOUNT/?comp=properties
canonicalizedHeaders : x-ms-date:Sun, 09 Mar 2014 12:33:41 GMT
x-ms-version:2013-08-15
corsMD5 : +ij...w==
key : sNB...JrY=
options : {"url":"https://NAME_OF_MY_STORAGE_ACCOUNT.blob.core.windows.net/?restype=service&comp=properties","body":"GET,PUT500x-ms-meta-data,x-ms-meta-customheaderx-ms-meta-target*,x-ms-meta-customheader2013-08-15","headers":{"x-ms-version":"2013-08-15","x-ms-date":"Sun, 09 Mar 2014 12:33:41 GMT","Host":"NAME_OF_MY_STORAGE_ACCOUNT.blob.core.windows.net","Authorization":"SharedKeyLite NAME_OF_MY_STORAGE_ACCOUNT:sNB...rY="}}
CORS: 403
body : AuthenticationFailedServer failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
RequestId:1e6abfe3-e0e8-4b9c-922d-7cb34485eec9
Time:2014-03-09T12:33:41.7262308ZThe MAC signature found in the HTTP request 'sNB...JrY=' is not the same as any computed signature. Server used following string to sign: 'PUT
x-ms-date:Sun, 09 Mar 2014 12:33:41 GMT
x-ms-version:2013-08-15
/NAME_OF_MY_STORAGE_ACCOUNT/?comp=properties'.
Any idea about what I am doing wrong here? Thanks for your help
To configure CORS, use the Azure storage library for Node.js.
You can do npm install azure-storage to get it. Source code is at https://github.com/Azure/azure-storage-node.
The one that come with npm package azure (a.k.a. azure-sdk-for-node) use older azure-storage-legacy package, which does not support CORS.
You can set the CORS with the following code:
var service = azure.createBlobService();
var serviceProperties = {
Cors: {
CorsRule: [{
AllowedOrigins: ['*'],
AllowedMethods: ['GET'],
AllowedHeaders: [],
ExposedHeaders: [],
MaxAgeInSeconds: 60
}]
}
};
service.setServiceProperties(serviceProperties, callback);
Please add Content-Type and Content-MD5 in your headers array and that should do the trick. Here's the modified code:
var crypto = require('crypto');
var request = require('request');
exports.setCors = function (MY_ACCOUNT_URL, MY_ACCOUNT_NAME, MY_ACCOUNT_HOST, accountKey) {
var MY_CORS_XML =
'<?xml version="1.0" encoding="utf-8"?>'+
'<StorageServiceProperties>'+
'<Cors>'+
'<CorsRule>'+
'<AllowedOrigins>*</AllowedOrigins>'+
'<AllowedMethods>GET,PUT</AllowedMethods>'+
'<MaxAgeInSeconds>500</MaxAgeInSeconds>'+
'<ExposedHeaders>x-ms-meta-data*,x-ms-meta-customheader</ExposedHeaders>'+
'<AllowedHeaders>x-ms-meta-target*,x-ms-meta-customheader</AllowedHeaders>'+
'</CorsRule>'+
'</Cors>'+
'<DefaultServiceVersion>2013-08-15</DefaultServiceVersion>'+
'</StorageServiceProperties>';
var url = MY_ACCOUNT_URL + '/?restype=service&comp=properties';
var canonicalizedResource = '/' + MY_ACCOUNT_NAME + '/?comp=properties';
var corsMD5 = crypto.createHash('md5' ).update(MY_CORS_XML).digest('base64');
var date = (new Date()).toUTCString();
var headers = {
'x-ms-version': '2013-08-15',
'x-ms-date': date,
'Host': MY_ACCOUNT_HOST,
'Content-Type': 'text/plain; charset=UTF-8',//Added this line
'Content-MD5': corsMD5,//Added this line
};
var canonicalizedHeaders = buildCanonicalizedHeaders( headers );
// THIS
var key = buildSharedKeyLite( 'PUT', corsMD5, 'text/plain; charset=UTF-8', canonicalizedHeaders, canonicalizedResource, accountKey);
// AND THIS, BOTH YIELD THE SAME SERVER RESPONSE
// var key = buildSharedKeyLite( 'PUT', "", "", canonicalizedHeaders, canonicalizedResource, accountKey);
headers['Authorization'] = 'SharedKeyLite ' + MY_ACCOUNT_NAME + ':' + key;
var options = {
url: url,
body: MY_CORS_XML,
headers: headers
};
console.log("url : " + url);
console.log("canonicalizedResource : " + canonicalizedResource);
console.log("canonicalizedHeaders : " + canonicalizedHeaders);
console.log("corsMD5 : " + corsMD5);
console.log("key : " + key);
console.log("options : " + JSON.stringify(options));
function onPropertiesSet(error, response, body) {
if (!error && response.statusCode == 202) {
console.log("CORS: OK");
}
else {
console.log("CORS: " + response.statusCode);
console.log("body : " + body);
}
}
request.put(options, onPropertiesSet); // require('request')
};
function buildCanonicalizedHeaders( headers ) {
var xmsHeaders = [];
var canHeaders = "";
for ( var name in headers ) {
if ( name.indexOf('x-ms-') == 0 ) {
xmsHeaders.push( name );
}
}
xmsHeaders.sort();
for ( var i = 0; i < xmsHeaders.length; i++ ) {
name = xmsHeaders[i];
canHeaders = canHeaders + name.toLowerCase().trim() + ':' + headers[name] + '\n';
}
return canHeaders;
}
function buildSharedKeyLite( verb, contentMD5, contentType, canonicalizedHeaders, canonicalizedResource, accountKey) {
var stringToSign = verb + "\n" +
contentMD5 + "\n" +
contentType + "\n" +
"" + "\n" + // date is to be empty because we use x-ms-date
canonicalizedHeaders +
canonicalizedResource;
// return crypto.createHmac('sha256', accountKey).update(encodeURIComponent(stringToSign)).digest('base64');
return crypto.createHmac('sha256', new Buffer(accountKey, 'base64')).update(stringToSign).digest('base64');
}
I have been following the blob service and authentication documentation in order to set the CORS properties on my azure blob storage account from my mobile service.
I can't figure out what I am doing wrong.
The server response is:
The MAC signature found in the HTTP request 'JI...Tk=' is not the same as any computed signature. Server used following string to sign: 'PUT
x-ms-date:Wed, 19 Feb 2014 07:24:06 GMT x-ms-version:2013-08-15
/apporotest/?comp=properties'
When I log the string to sign (not passing contentMD5 and content type) on my end, it turns out to be the same string. So I guess my function to build the shared key is wrong.
This should build : Base64(HMAC-SHA256(UTF8(StringToSign))):
function buildSharedKeyLite( verb, contentMD5, contentType, canonicalizedHeaders, canonicalizedResource ) {
var stringToSign = verb + "\n" +
contentMD5 + "\n" +
contentType + "\n" +
"" + "\n" + // date is to be empty because we use x-ms-date
canonicalizedHeaders +
canonicalizedResource;
return crypto.createHmac('sha256', self.accountKey).update(encodeURIComponent(stringToSign)).digest('base64');
}
What confuses me though is that the formerly mentioned documentation for the Shared Key Lite requires MD5 of the content as well as the content type to be set. However, the server response with the string to sign does not seem to expect these.
If the creation of the shared key lite is correct, then I assume I am not handling the creation of the MD-5 content correctly or the canonicalized headers:
function setCors( cors ) {
var url = MY_ACCOUNT_UTL + '/?restype=service&comp=properties';
var canonicalizedResource = '/' + MY_ACCOUNT_NAME + '/?comp=properties';
var corsMD5 = crypto.createHash('md5' ).update(MY_CORS_XML).digest('base64');
var date = (new Date()).toUTCString();
var headers = {
'x-ms-version': '2013-08-15',
'x-ms-date': date,
'Host': MY_ACCOUNT_HOST
};
var canonicalizedHeaders = buildCanonicalizedHeaders( headers );
// THIS
var key = buildSharedKeyLite( 'PUT', corsMD5, 'text/plain; charset=UTF-8', canonicalizedHeaders, canonicalizedResource );
// AND THIS, BOTH YIELD THE SAME SERVER RESPONSE
var key = buildSharedKeyLite( 'PUT', "", "", canonicalizedHeaders, canonicalizedResource );
headers['Authorization'] = 'SharedKeyLite ' + MY_ACCOUNT_NAME + ':' + key;
var options = {
url: url,
headers: headers
};
function onPropertiesSet(error, response, body) {
if (!error && response.statusCode == 202) {
console.log("CORS: OK");
}
else {
console.log("CORS: "+ response.statusCode);
console.log(body);
}
}
request.put(options, onPropertiesSet); // require('request')
}
function buildCanonicalizedHeaders( headers ) {
var xmsHeaders = [];
var canHeaders = "";
for ( var name in headers ) {
if ( name.indexOf('x-ms-') == 0 ) )
xmsHeaders.push( name );
}
}
xmsHeaders.sort();
for ( var i = 0; i < xmsHeaders.length; i++ ) {
name = xmsHeaders[i];
canHeaders = canHeaders + name.toLowerCase().trim() + ':' + headers[name] + '\n';
}
return canHeaders;
}
I am really thankful for any pointers.
I'm not 100% sure but I believe encodeURIComponent is creating problem for you. For example, look at the code below:
var a = "PUT\n\n\nFeb 2014 09:08:18 GMT\nx-ms-version:2013-08-15\n/cynapta/?comp=properties";
var b = encodeURIComponent(a);
console.log(a);
console.log("\n");
console.log(b);
and this is how a and b are displayed on my console:
Can you try by removing encodeURIComponent and just pass stringToSign directly for signature calculation?
Update
Looking at the source code here for signing the request and assuming you're passing storage account key as string, can you try the following in your buildSharedKeyLite function:
return crypto.createHmac('sha256', new Buffer(self.accountKey, 'base64')).update(stringToSign).digest('base64');