cosmos db, generate authentication key on client for Azure Table endpoint - azure-cosmosdb

Cosmos DB, API Azure Tables, gives you 2 endpoints in the Overview blade
Document Endpoint
Azure Table Endpoint
An example of (1) is
https://myname.documents.azure.com/dbs/tempdb/colls
An example of (2) is
https://myname.table.cosmosdb.azure.com/FirstTestTable?$filter=PartitionKey%20eq%20'car'%20and%20RowKey%20eq%20'124'
You can create the authorization code for (1) on the client using the prerequest code from this Postman script: https://github.com/MicrosoftCSA/documentdb-postman-collection/blob/master/DocumentDB.postman_collection.json
Which will give you a code like this:
Authorization: type%3Dmaster%26ver%3D1.0%26sig%3DavFQkBscU...
This is useful for playing with the rest urls
For (2) the only code I could find to generate a code that works was on the server side and gives you a code like this:
Authorization: SharedKey myname:JXkSGZlcB1gX8Mjuu...
I had to get this out of Fiddler
My questions
(i) Can you generate a code for case (2) above on the client like you can for case (1)
(ii) Can you securely use Cosmos DB from the client?

If you go to the Azure Portal for a GA Table API account you won't see the document endpoint anymore. Instead only the Azure Table Endpoint is advertised (e.g. X.table.cosmosdb.azure.com). So we'll focus on that.
When using anything but direct mode with the .NET SDK, our existing SDKs when talking to X.table.cosmosdb.azure.com endpoint are using the SharedKey authentication scheme. There is also a SharedKeyLight scheme which should also work. Both are documented in https://learn.microsoft.com/en-us/rest/api/storageservices/authentication-for-the-azure-storage-services. Make sure you read the sections specifically on the Table Service. The thing to notice is that a SharedKey header is directly tied to the request it is associated with. So basically every request needs a unique header. This is useful for security because it means that a leaked header can only be used for a limited time to replay a specific request. It can't be used to authorize other requests. But of course that is exactly what you are trying to do.
An alternative is the SharedKeyLight header which is a bit easier to implement as it just requires a date and the a URL.
But we don't have externalized code libraries to really help with either.
But there is another solution that is much friendly to things like Fiddler or Postman, which is to use a SAS URL as defined in https://blogs.msdn.microsoft.com/windowsazurestorage/2012/06/12/introducing-table-sas-shared-access-signature-queue-sas-and-update-to-blob-sas/.
There are at least two ways to get a SAS token. One way is to generate one yourself. Here is some sample code to do that:
var connectionString = "DefaultEndpointsProtocol=https;AccountName=tableaccount;AccountKey=X;TableEndpoint=https://tableaccount.table.cosmosdb.azure.com:443/;";
var tableName = "ATable";
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString);
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference(tableName);
await table.CreateIfNotExistsAsync();
SharedAccessTablePolicy policy = new SharedAccessTablePolicy()
{
SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(1000),
Permissions = SharedAccessTablePermissions.Add
| SharedAccessTablePermissions.Query
| SharedAccessTablePermissions.Update
| SharedAccessTablePermissions.Delete
};
string sasToken = table.GetSharedAccessSignature(
policy, null, null, null, null, null);
This returns the query portion of the URL you will need to create a SAS URL.
Another, code free way, to get a SAS URL is to go to https://azure.microsoft.com/en-us/features/storage-explorer/ and download the Azure Storage Explorer. When you start it up it will show you the "Connect to Azure Storage" dialog. In that case:
Select "Use a connection string or a shared access signature URI" and click next
Select "Use a connection string" and paste in your connection string from the Azure Portal for your Azure Cosmos DB Table API account and click Next and then click Connect in the next dialog
In the Explorer pane on the left look for your account under "Storage Accounts" (NOT Cosmos DB Accounts (Preview)) and then click on Tables and then right click on the specific table you want to explore. In the right click dialog you will see an entry for "Get Shared Access Signature", click on that.
A new dialog titled "Generate Shared Access Signature" will show up. Unfortunately so will an error dialog complaining about "NotImplemented", you can ignore that. Just click OK on the error dialog.
Now you can choose how to configure your SAS, I usually just take the defaults since that gives the widest access permission. Now click Create.
The result will be a dialog with both a complete URL and a query string.
So now we can take that URL (or create it ourselves using the query output from the code) and create a fiddler request:
GET https://tableaccount.table.cosmosdb.azure.com/ATable?se=2018-01-12T05%3A22%3A00Z&sp=raud&sv=2017-04-17&tn=atable&sig=X&$filter=PartitionKey%20eq%20'Foo'%20and%20RowKey%20eq%20'bar' HTTP/1.1
User-Agent: Fiddler
Host: tableaccount.table.cosmosdb.azure.com
Accept: application/json;odata=nometadata
DataServiceVersion: 3.0
To make the request more interesting I added a $filter operation. This is an OData filter that lets us explore the content. Note, btw, to make filter work both the Accept and DataServiceVersion headers are needed. But you can use the base URL (e.g. without the filter parameter) to make any of the REST API calls on a specific table.
Do be aware that the SAS token is scoped to an individual table. So higher level operations won't work with this SAS token.

Related

HTTP Connector in Microsoft Power Automate - Login against Azure AD app registration on behalf of user not working

I hope you are doing fine :)
I have the following problem/problems.
I have created a dummy (robot) user. In Azure AD there is an App Registration with some Power BI delegated permission services.
I want to automate the deletion of a push dataset via a HTTP request.
For this, I need to authenticate against that App Registration from Azure AD. With postman, everything worked perfectly, I got the token as a response.
Then, I tried to make the same request using the HTTP connector in PowerAutomate.
I get the following error
{"error":"invalid_request","error_description":"AADSTS900144: The request body must contain the following parameter: 'grant_type'.\r\nTrace ID: e6d68953-ce1c-4217-856c-ae3aada82e00\r\nCorrelation ID: f50db4d9-d5bb-4396-af11-214717721f43\r\nTimestamp: 2022-10-26 19:12:48Z","error_codes":[900144],"timestamp":"2022-10-26 19:12:48Z","trace_id":"e6d68953-ce1c-4217-856c-ae3aada82e00","correlation_id":"f50db4d9-d5bb-4396-af11-214717721f43","error_uri":"https://login.microsoftonline.com/error?code=900144"}
The connector looks like this->
I have tried other ways as well.
The following error ->
{"error":"invalid_grant","error_description":"AADSTS50126: Error validating credentials due to invalid username or password.\r\nTrace ID: fec65949-0701-4727-af3b-2c2b8eb73a00\r\nCorrelation ID: 42c6c04b-98be-477d-8d36-52a01a473a40\r\nTimestamp: 2022-10-26 19:23:50Z","error_codes":[50126],"timestamp":"2022-10-26 19:23:50Z","trace_id":"fec65949-0701-4727-af3b-2c2b8eb73a00","correlation_id":"42c6c04b-98be-477d-8d36-52a01a473a40","error_uri":"https://login.microsoftonline.com/error?code=50126"}
I get from the the next HTTP connector inputs. I tried creating a query out of the previous json. It says the username or password are invalid but I logged in successfully for a number of times with those exact credentials. That I am sure of.
The dummy inputs in the picture are used to avoid the real ids, username and password.
This worked for me ...
Add a header setting Content-Type to application/x-www-form-urlencoded and add the URL form encoded content string like thus ...
client_id=<CLIENT_ID>c&grant_type=password&username=<USERNAME>&password=<PASSWORD>&scope=User.read
If you're using the password grant type approach, you shouldn't need a client secret, just the client ID.

ADFS 2016 On behalf of flow : cannot get any user informations

I'm trying to implement the "on behalf of" flow in an application using ADFS 2016 as STS. As a reference, I look at this Microsoft tutorial (https://learn.microsoft.com/en-ca/windows-server/identity/ad-fs/development/ad-fs-on-behalf-of-authentication-in-windows-server). It's working as it should, I can login into my web application and then use my original access token in UserAssertion to generate a new access token with the proper audience to call my API BUT I found absolutely no way to include any user informations (sub, name, email, upn etc.) into the access token for my API, even if I set claim rules into my ADFS configurations for the API.
I checked the communication between my app and adfs using Fiddler and everything looks like the informations in the tutorial. See the screen shot of the "on behalf of" request below :
Here's the resulting access token :
Finally, here's the code I use to generate my new access token :
private async Task<string> GetAccessToken(ClaimsPrincipal user, string originalAccessToken)
{
var authority = "[authority]";
var context = new AuthenticationContext(authority, false);
string userName = user.FindFirstValue("upn");
var userAssertion = new UserAssertion(originalAccessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer",userName);
var cc = new ClientCredential("https://localhost:44387/", "[client_secret]");
var result = await context.AcquireTokenAsync("https://localhost:44339/", cc, userAssertion);
return result.AccessToken;
}
Have you struggle with that scenario and if yes, did you find a way to fix this ?
Thanks
I've only used the Microsoft On Behalf Of flow with Azure AD and not ADFS, but it looks like you need to send a more detailed scope in your User Info request.
Maybe try sending 'openid profile email', to indicate that you want that type of detail, as in Section 17 of my blog post. Of course this assumes that this type of data has been registered for all users.
TROUBLESHOOTING
Looks like one of these will be the cause:
A suboptimal Microsoft library that does not allow you to send the required scope
Or ADFS 2016 perhaps lacks the scope features that work correctly in Azure AD
I would concentrate on making extra sure you are sending the correct form URL encoded request message, using a tool such as curl, Postman or a plain C# HttpClient. Here is the code I used to send the correct scope - using an open source library rather than a Microsoft one:
Sample NodeJS Code
If you can get the scope sent correctly then you should have a resolution either way:
Either you get the correct data and can update your code
Or the behaviour you want is not supported by ADFS
Good luck ...

Can I use my test environment merchant ID and keys to test a flex microform post?

I'm getting started understanding what's required for Cybersource's Flex Microform integration. But to start with, I'm hoping to be able to see a valid response using my merchant ID, shared secret key and the general key that comes with generating the secret on the cybersource api reference page: https://developer.cybersource.com/api-reference-assets/index.html#flex-microform_key-generation_generate-key
This is using the HTTP Signature method and ChasePaymentech (default) processor.
If I use the default settings they supply and choose to do a test POST to here https://apitest.cybersource.com/flex/v1/keys?format=JWT&
The JSON response is good with no complaints of authentication.
If I try to do the same POST with my test environment merchant ID and keys I generated in my merchant environment here: https://ubctest.cybersource.com/ebc2/app/PaymentConfiguration/KeyManagement the POST response will return a 401 with this JSON:
{
"response": {
"rmsg": "Authentication Failed"
}}
Is this developer.cybersource.com site a valid place to perform this kind of test? Are there any other steps I need to do in the merchant account to have this Authenticate?
I'm just getting started on figuring out the CyberSource Flex Micro Form code out myself and it's pretty straight forward from what I can see. If you don't have the proper SDK already pulled in, you can fetch it from https://github.com/CyberSource
I had to use Composer to fetch all the dependencies but once I did, I was able to load up the microform checkout page in my browser window successfully. Make sure you edit the ExternalConfiguration file with your credentials that you setup in CyberSource.
The apiKeyId value is the value you can find in your CyberSource account under Key Management. This is the value with the dashes in it.
The secretKey value is the value you should have downloaded from CyberSource that is your public key. This is the value without the dashes and probably has a few slashes / in it.
That's all I had to do in my setup to get the first successful authentication / token on my end.

Updating Evernote note with Evernote php sdk

I can get notes(real contents, not just metadata) from the evernote API. However, calling notestore->update() always gives me a EDAMUserException.
My php code is below, the arguments are self-explanatory:
//add text to note
//if append=true then the text will be appended to the end, else it will be appended to the start
public function addToNote($new_content, $access_token, $note_store, $note_guid, $append = true){
$note = $note_store->getNote($access_token, $note_guid, true, false, false, false);
$note->content +="<en-note>Note updated</en-note>";
$note_store->updateNote($access_token, $note);
}
I've already did a lot of searching before I asked here, and here are the things I know:
According to: https://dev.evernote.com/doc/articles/permissions.php it says that there are two types of api keys, one is the basic access and one is for full access, I have full access, this is proved by no exception was thrown during $note_store->getNote() call, and I did output the data from that call, I can actually get the contents of the note.
In the same page as 1: "Certain API functions are only available to official Evernote applications and services. These functions are described as such in the API Reference and will throw an EDAMUserException with the error code PERMISSION_DENIED if called by a third-party application." I read the API documentation here: https://dev.evernote.com/doc/reference/NoteStore.html#Fn_NoteStore_updateNote
It does not mention that it is blocked by default.
I think I figured out what was wrong. Evernote actually has its own DTD document format, if the "content" section of the note is not a valid document, then the request is denied. In my case it was not denied because my API key's access level, but because the "content" I gave was not a proper evernote format.
if I set:
$note->content='<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div>testing complete!!!</div></en-note>';
Then the note will be successfully updated.
However, for other users that are getting this exception and using the right format, it is most likely:
1.your api key does not have full access, choose the full access option while you request an api key.
2.usually you would start testing on the "sandboxed"(https://sandbox.evernote.com) version of their site, you need to register another account on the sandboxed site(you real evernote account doesn't carry over) and test with that account. If you do not do this, your account will not exist on the sandboxed account and anything you do will not work.

Connecting an ASP.NET application to QuickBooks Online Edition

I am trying to create an ASP.NET page that connects to QuickBooks Online Edition, read a couple of values, and display the results. So far I have downloaded the QuickBooks SDK but I have been unable to find a simple step-by-step example on how to create an asp.net page to connect to QuickBooks Online. The QuickBooks SDK documentation and the SDK itself is very confusing and overwhelming. Anyone know of a simple step by step tutorial on where to get started... or maybe a hint on the very first thing to do.
Yishai's answer is partially correct, but not entirely.
You can have your ASP .NET application log in and issue requests without having to send the user over to the QuickBooks Online log in page if you make sure to set the security preferences correctly when you connect up your application to QuickBooks Online Edition.
During the application registration process/connection process, it will ask you if you want to turn on or off login security with a prompt as below. You must tell it you want to turn off login security if you want to be able to access QuickBooks Online Edition data without forcing the user to log in every time. The prompt is something like:
"Do you want to turn on login security?"
You must select:
"No. Anyone who can log into [Application Name] can use the connection".
Outside of that, Yishai is correct about the process. To re-iterate, in a nutshell:
Register for a QBOE account
Register your integrated application with Intuit's AppReg service
Visit a specific link to tie your AppReg application to your QBOE account (make sure you turn off login security when it asks you!)
Make HTTPS POST requests to Intuit's servers to sign on using the connection ticket Intuit will provide you with
Make HTTPS POST requests to send qbXML requests to Intuit's servers, which you can use to add, modify, delete, and query records within QuickBooks Online Edition.
There is some additional documentation and some example requests on my QuickBooks development and integration wiki, specifically the QuickBooks Online Edition integration page.
I have built a solution that does what you're asking in PHP which adds, modifies, and queries data within QuickBooks Online Edition without requiring the user to log in everytime, and it works like a champ. It pushes and pulls order data between a PHP shopping cart (VirtueMart) and QuickBooks Online Edition. The PHP code is available here:
QuickBooks PHP Framework
As a side note, unless you're very familiar with generating SSL certificates and sending them via HTTPS POSTs, you'll save yourself a whole lot of trouble by using the DESKTOP model of communication, and not the HOSTED model. Just make sure to keep your connection ticket securely encrypted.
Also, Yishai's suggestion to: "One is to programatically hit up their login page and submit the credentials as if you were a user. I'm sure its not "supported" but it would likely work." goes specifically against the security/developer guidelines Intuit and the SDK set forth. If they catch you doing that, they'll ban your application from connecting to QuickBooks.
Here are all the steps I took to get this working. Special thanks to Keith Palmer for his comments, answers, and his website which really helped me get this working.
Register your application at http://appreg.quickbooks.com. This will give you your App ID and Application Name. I used these settings:
Target Application: QBOE
Environment: Production
Application Type: Desktop
(using Desktop made things much easier as far as not needing certificates)
A verification key is sent to your email address which you need to enter on page 2 of this wizard.
Set up your QBOE Connection. Once you finish registering your application in Step 1, you will then have an Application ID. Use this ID in the url below to set up your QBOE Connection:
https://login.quickbooks.com/j/qbn/sdkapp/confirm?serviceid=2004&appid=APP_ID
NOTE: Make sure to replace APP_ID in the above url with the Application ID that was created when you registered your application.
The wizard will take you through the following steps:
Specifying a name for your connection.
Granting Access Rights - I gave All Accounting rights since this was easiest.
Specify Login Security - I turned Login Security Off. This is important since it makes submitting the xml to the QBOE much easier since you do not need to get a session ticket for each user.
You will then be given a Connection Key.
At this point you now have the 3 important pieces of information in order to gain access to your QuickBooks Online Edition (QBOE) account.
Application Name
Application ID
Connection Key
Post the XML to QBOE with the 3 pieces of access information and the actual request into your QBOE database. Here is sample c# code that will post to the QBOE gateway. This will return all customers in your QuickBooks database. Make sure to update the xml below with your Application Name, Application ID, and Connection Key.
string requestUrl = null;
requestUrl = "https://apps.quickbooks.com/j/AppGateway";
HttpWebRequest WebRequestObject = null;
StreamReader sr = null;
HttpWebResponse WebResponseObject = null;
StreamWriter swr = null;
try
{
WebRequestObject = (HttpWebRequest)WebRequest.Create(requestUrl);
WebRequestObject.Method = "POST";
WebRequestObject.ContentType = "application/x-qbxml";
WebRequestObject.AllowAutoRedirect = false;
string post = #"<?xml version=""1.0"" encoding=""utf-8"" ?>
<?qbxml version=""6.0""?>
<QBXML>
<SignonMsgsRq>
<SignonDesktopRq>
<ClientDateTime>%%CLIENT_DATE_TIME%%</ClientDateTime>
<ApplicationLogin>APPLICATION_LOGIN</ApplicationLogin>
<ConnectionTicket>CONNECTION_TICKET</ConnectionTicket>
<Language>English</Language>
<AppID>APP_ID</AppID>
<AppVer>1</AppVer>
</SignonDesktopRq>
</SignonMsgsRq>
<QBXMLMsgsRq onError=""continueOnError"">
<CustomerQueryRq requestID=""2"" />
</QBXMLMsgsRq>
</QBXML>";
post = post.Replace("%%CLIENT_DATE_TIME%%", DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss"));
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(post);
post = xmlDoc.InnerXml;
WebRequestObject.ContentLength = post.Length;
swr = new StreamWriter(WebRequestObject.GetRequestStream());
swr.Write(post);
swr.Close();
WebResponseObject = (HttpWebResponse)WebRequestObject.GetResponse();
sr = new StreamReader(WebResponseObject.GetResponseStream());
string Results = sr.ReadToEnd();
}
finally
{
try
{
sr.Close();
}
catch
{
}
try
{
WebResponseObject.Close();
WebRequestObject.Abort();
}
catch
{
}
}
Couple things to note:
As pointed out by Keith Palmer the qbxml version needs to be 6.0 (even though the IDN Unified On-Screen Reference shows 7.0)
I needed to include the onError="continueOnError" attribute.
Setting the WebRequestObject.ContentLength property is required.
Content Type needs to be "application/x-qbxml"
And finally I received many "The remote server returned an error: (400) Bad Request." exceptions which were not helpful at all but in the end I was able to trace them to something wrong with the xml. So if you get this exception look to your xml as the source of the problem.
The outline of what you have to do are outlined in Chapter 7 of the QBSDK documentation (at least in the 7.0 version of the SDK that I have). You have to open a test account and get permission to connect to their servers.
Once you have your account setup, the basic authentication procedure consists of redirecting your user to the QuickBooks Online site to log in, and once the user has done that, QuickBooks calls back your application with an HTTPS post with a ticket, which is basically a session handle that you can use for your requests, so that the system knows you are authenticated. When you get that response, you parse it and send your own login request to the system based on what you got back.
Then (if I understood the documentation correctly) you are basically doing Https POSTS of xml files with the QuickBooks requests, and you get XML responses that you have to parse to get the data you want.
I hope that gets you started.
The rest of the SDK is documentation (which you will need to know how to form your requests and parse your responses) and everything else is concerned with how to communicate with the desktop product. The only thing you are going to need from the rest of the documentation is how to do error handling, which is really only important if you are posting data to QuickBooks. If you are just reading, it doesn't matter (either your request works out or it doesn't, you don't need to worry about if you need to retry or if that would result in duplicate data).
EDIT: Given your specific use case I see two options. (You aren't crazy, just not the typical QuickBooks Online scenario).
One is to programatically hit up their login page and submit the credentials as if you were a user. I'm sure its not "supported" but it would likely work.
The other is to cache the results (which you should probably do anyway) and have an admin screen where someone does log into QuickBooks online and update the results every morning or evening or whatever makes sense.
In most small businesses, they are going to opt for the first option, but the second one is going to work more consistently, robustly and actually be supported by Intuit if you have an issue.
This looks pretty close to what you need: www.QuickbooksConnector.com
Wasn't able to download it yet.

Resources