I'm trying to use the Create Datasource REST endpoint from Microsoft's Power BI API, but I'm getting a 400 error when I send the encrypted string of my Windows credentials. My API is written in nodejs, so I'm using node-rsa to replicate the functionality from the C# solution in their docs.
I generated an encrypted string of my credentials with this C# code and it works fine and returns 201 for creating the Datasource:
var credentials = new WindowsCredentials(username: "myusername", password: "mypassword");
var publicKey = new GatewayPublicKey("<exponent>", "<modulus>");
var credentialsEncryptor = new AsymmetricKeyEncryptor(publicKey);
var credentialDetails = new CredentialDetails(credentials, PrivacyLevel.Organizational, EncryptedConnection.Encrypted, credentialsEncryptor);
This is the encrypted string from the Credentials prop of the credentialDetails object in the code above: oavtaHCi2Nkn0euRQxtRvZ2aDPnLA5/T1B6YHHeQzfBDXLJsU4JOx3ZOm31nEWqPRUh2DLARKI0y+C106wgOhWKgxI4nIz5jmJCQqPWRzOYlJlvdPQCOrTtuvIZb3IRy2OAhBfKOcOA8xqHIkKsqvTX1u4LUgfNuwxmU16FB/d3mzZOtc3+ld2MUp15HEPBCen6qTOkdEOwOm2AqxSidx46xoahLrqjkMtLZqLimooVGeTmuXm7bwoRQa1VKUwPaAuKcBBfAuRJulXyeSOMkwKz90pA30y8g7aB2NvT+92SzzjpuHcs1bzPrbPR1J+WXSRTN7xzlCOyQmC5YiQKn6A==AADPIC5L5rjd7WWThtCNAPunINW330ka1ts53SsBkaelIrZ8f9msgE4JjliaeDM91Qq3KDqm1DUjLw6Fg92LzZhMkjcwdEBued/piCdvIpP0eA9rhX0kjWWaMzQF6T3fHr798OkL7yMPEGi+m7Z3cOz22HP2Ot1ORxf6RH/JUNJmLEepEYCRVVubmKL04IyEtx8dShtSG+upeOaBOaD/GOS5
This is my nodejs solution using node-rsa. Having trouble understanding why the encrypted string from the C# code above works, but this one doesn't.
const nodersa = require('node-rsa');
const credentials =
'{"credentialData":[{"name":"username", "value":"myusername"},{"name":"password", "value":"mypassword"}]}';
const exponentString = '<exponent>';
const modulusString = '<modulus>';
const key = new nodersa();
key.setOptions({
encryptionScheme: 'pkcs1_oaep',
});
const pubKey = key.importKey({
n: Buffer.from(modulusString, 'base64'),
e: Buffer.from(exponentString, 'base64'),
});
const encrypted = pubKey.encrypt(credentials, 'base64');
This is the encrypted string from this code above: UCLIIGF8u6HMdlLAwVeZcM0iLks5YLEWhRfpywnNBrAdSoPlP8/vRqe4knMCnAFiSimHiX8fu/CQlAP0b98Xlzx6sHnW4sQHV5KgUErLQfBP5i+5LGj7lBDB0/nsuf2hLDSrXXUb3F+XXv1mlUTgNZzwanizUuRcqYRxcdMaYOjI1vCaQbW++kHXMgOQPjMBj8hrJ1gW1WznS3zCYy6v8oUwEzJtp2aQEP8Pycvx5KwjVdy9KxkB675+TfddauMlz0B+EpQjC1Z+k87uiuFpGZxwA5FAi+4r8ztZ+9/su+8eieCSBK/8ZokAUNcrLmU0sPuDewaojW1MBdxvJiv7YA==
This is the error that Power BI API returns using string encrypted via node solution. Everything else about my request is identical except the encrypted creds, so I'm confident that that is the issue.
{
"error": {
"code": "DM_GWPipeline_UnknownError",
"pbi.error": {
"code": "DM_GWPipeline_UnknownError",
"parameters": {},
"details": [
{
"code": "DM_ErrorDetailNameCode_UnderlyingErrorMessage",
"detail": {
"type": 1,
"value": "The parameter is incorrect.\r\n"
}
},
{
"code": "DM_ErrorDetailNameCode_UnderlyingHResult",
"detail": {
"type": 1,
"value": "-2146893785"
}
}
],
"exceptionCulprit": 1
}
}
}
You can fix this by upgrading to the latest RSA encryption that supports 2048 bits.
Related
I am making an app with Xamarin Forms and Azure. I am authenticating with Google and Facebook.
When requesting user's data, I use a HttpClient and make a request. Requesting from Google works fine but Facebook always returns null.
I have tested the Url in Postman with the same access token, it returns the correct data.
The Url is:
https://graph.facebook.com/v2.8/me/?fields=name,picture,locale,link,devices,email,first_name,last_name&access_token=
This returns:
{
"name": "Reece Russell",
"picture": {
"data": {
"height": 50,
"is_silhouette": false,
"url": "https://lookaside.facebook.com/platform/profilepic/?asid=2139651619601242&height=50&width=50&ext=1526558014&hash=AeSKoNrLS6O4UmMV",
"width": 50
}
},
"locale": "en_US",
"link": "https://www.facebook.com/app_scoped_user_id/YXNpZADpBWEhTLXBnRko4LWlBRUVzc0RBZAXhsc3R1eXpNNGZAxNmJMaXhWT013WTFCMVNnOFpLNE1jblh3dWJjLUwyRDhLQ0QyOHh6NmxnVGNyX2REMU9vUzNYTHFKNjlfN0J5R0lVbkV5ZA1V4aDRBZAVNDMnpwS0EZD/",
"devices": [
{
"hardware": "iPhone",
"os": "iOS"
}
],
"first_name": "Reece",
"last_name": "Russell",
"id": "2139651619601242"
}
But when making this request from Xamarin Forms using a HttpClient it always returns null.
The code I am using is below. I have also tried many ways of making the request including making the request from my server, which gets the correct data from FaceBook but returns null when I request from my server it still returns null.
using (var client = new HttpClient(new NativeMessageHandler()))
{
string url = "https://graph.facebook.com/v2.8/me/?fields=name,picture,locale,link,devices,email,first_name,last_name&access_token=" + Settings.AccessToken;
string json = await client.GetStringAsync(url);
FacebookUserProfile profile = JsonConvert.DeserializeObject<FacebookUserProfile>(json);
profile.PhotoUrl = profile.Picture.Data.Url;
profile.Name = profile.FirstName + " " + profile.LastName;
return profile;
}
I am using Azure Data factory HTTP connector as a linked service to read data from the REST endpoint using basic authentication.
{
"name": "LS_HTTP",
"properties": {
"hubName": "Hub name",
"type": "Http",
"typeProperties": {
"url": "http://*****.azurewebsites.net",
"authenticationType": "Basic",
"gatewayName": "",
"encryptedCredential": "",
"username": "test",
"password": "**********",
"enableServerCertificateValidation": true
}
}
}
Following code snippet is written to fetch the username and password from the headerin my web API
string authHeader = context.Request.Headers["Authorization"];
if (authHeader != null && authHeader.StartsWith("Basic"))
{
//Extract credentials
string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim();
Encoding encoding = Encoding.GetEncoding("iso-8859-1");
string usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));
int seperatorIndex = usernamePassword.IndexOf(':');
var username = usernamePassword.Substring(0, seperatorIndex);
var password = usernamePassword.Substring(seperatorIndex + 1);
if (username == "test" && password == "****")
{
await _next.Invoke(context);
}
else
{
context.Response.StatusCode = 401; //Unauthorized
return;
}
}
else
{
// no authorization header
context.Response.StatusCode = 401; //Unauthorized
return;
}
When I run Azure data factory pipeline with this setup, I am not able to get username and password from the request header in the web api, basically Authorization header itself is null.
Help me to fetch the username and password passed from my ADF connector service in my web API.
Looking at your definition, you're working with Data Factory v1. I see you configure some properties that are not required for basic authentication.
encryptedCredential, is not required. The documentation states:
Description: Encrypted credential to access the HTTP endpoint. Auto-generated when you configure the authentication information in copy wizard or the ClickOnce popup dialog.
Required: No. Apply only when copying data from an on-premises HTTP server.
gatewayName, is not required since you're not using an on-premises HTTP server
Description: Name of the Data Management Gateway to connect to an on-premises HTTP source.
enableServerCertificateValidation, already defaults to true
Documentation gives this basic example:
{
"name": "HttpLinkedService",
"properties":
{
"type": "Http",
"typeProperties":
{
"authenticationType": "basic",
"url" : "https://en.wikipedia.org/wiki/",
"userName": "user name",
"password": "password"
}
}
}
I have a bucket on Amazon S3 and I have created IAM user Now I want to download private bucket file using temporary credential.
This is my bucket policy
{
"Id": "Policy1509026195925",
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1509026179419",
"Action": [
"s3:GetObject"
],
"Effect": "Allow",
"Resource": "arn:aws:s3:::test-folder/*",
"Principal": {
"AWS": [
"arn:aws:iam::461567291450:user/john"
]
}
}
]
}
this is my c# .Net code
ServicePointManager.Expect100Continue = false;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
try
{
// In real applications, the following code is part of your trusted code. It has
// your security credentials you use to obtain temporary security credentials.
AmazonSecurityTokenServiceConfig config = new AmazonSecurityTokenServiceConfig();
AmazonSecurityTokenServiceClient stsClient =
new AmazonSecurityTokenServiceClient(config);
GetFederationTokenRequest federationTokenRequest =
new GetFederationTokenRequest();
federationTokenRequest.Name = "testuser";
// federationTokenRequest.Policy = "Policy1509026195925";
federationTokenRequest.DurationSeconds = 7200;
GetFederationTokenResponse federationTokenResponse = stsClient.GetFederationToken(federationTokenRequest);
//FederatedUser federationTokenResult = federationTokenResponse.;
Credentials credentials = federationTokenResponse.Credentials;
SessionAWSCredentials sessionCredentials =
new SessionAWSCredentials(credentials.AccessKeyId,
credentials.SecretAccessKey,
credentials.SessionToken);
// The following will be part of your less trusted code. You provide temporary security
// credentials so it can send authenticated requests to Amazon S3.
// Create Amazon S3 client by passing in the basicSessionCredentials object.
AmazonS3Client s3Client = new AmazonS3Client(sessionCredentials, Amazon.RegionEndpoint.USEast1);
// Test. For example, send list object keys in a bucket.
ListObjectsRequest listObjectRequest = new ListObjectsRequest();
listObjectRequest.BucketName = bucketName;
ListObjectsResponse response = s3Client.ListObjects(listObjectRequest);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Every time when I run the code I got Access denied message. Why? How to download the bucket file using Temporary credential?
You can try something like :
assumeRoleResult = AssumeRole(role-arn);
tempCredentials = new SessionAWSCredentials(
assumeRoleResult.AccessKeyId,
assumeRoleResult.SecretAccessKey,
assumeRoleResult.SessionToken);
s3Request = CreateAmazonS3Client(tempCredentials);
You need to to call AssumeRole to get temporary security credentials, and then use those credentials to make a call to Amazon S3, see Switching to an IAM Role (API).
Refer : Using Temporary Security Credentials with the AWS SDKs
I'm trying to create drafts in Gmail using the .Net Client Library. I can successfully login and retrieve a list of drafts so the authentication and api are working. Now I need to create an instance of the Draft class and send it to the API. But what does the draft message need to look like? It doesn't matter what I fill in the API explorer on https://developers.google.com/gmail/api/v1/reference/users/drafts/create, my draft is always empty.
Also when doing it from my C# code I need to set the draft.Message.Raw field to something else I get an error:
Missing draft message [400]
Using the client library you can set the base64 encoded email to the raw property of a Message then use that Message as the message property of the Draft.
More generally:
The Draft Message consists of an id and a Message Resource
https://developers.google.com/gmail/api/v1/reference/users/drafts
{
"id": string,
"message": users.messages Resource
}
The Message Resource should have its "raw" field set to a base64 encoded RCF 2822 formatted string.
Eg:
from: me#email.com
to: you#email.com
subject: test email
email body
As a base64 encoded string is:
ZnJvbTogbWVAZW1haWwuY29tDQp0bzogeW91QGVtYWlsLmNvbQ0Kc3ViamVjdDogdGVzdCBlbWFpbA0KDQplbWFpbCBib2R5
So the request body of a draft.create should look something like:
{
"message": {
"raw": "ZnJvbTogbWVAZW1haWwuY29tDQp0bzogeW91QGVtYWlsLmNvbQ0Kc3ViamVjdDogdGVzdCBlbWFpbA0KDQplbWFpbCBib2R5"
}
}
I was struggling with this until today.
What I did was adapting the solution on this link for drafts.
http://jason.pettys.name/2014/10/27/sending-email-with-the-gmail-api-in-net-c/
Jason uses a nuget called AE.Net.Mail to serialize an mail object to RFC 2822.
what I did was I installed both nugets
Install-Package Google.Apis.Gmail.v1
Install-Package AE.Net.Mail
And after that I created two methods
static GmailService Service;
public static void CriaService(string emailaConectar)
{
var certificate = new X509Certificate2(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ClientCredentials.CertificatePath), ClientCredentials.ClientSecret, X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(ClientCredentials.ServiceAccountEmail)
{
Scopes = new[] { GmailService.Scope.GmailCompose },
User = emailaConectar
}.FromCertificate(certificate)) { };
Service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ClientCredentials.ApplicationName,
});
}
private static string Base64UrlEncode(string input)
{
var inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
// Special "url-safe" base64 encode.
return Convert.ToBase64String(inputBytes)
.Replace('+', '-')
.Replace('/', '_')
.Replace("=", "");
}
And on my Main method I designed like this
CriaService("xpto#gmail.com");
var msg = new AE.Net.Mail.MailMessage
{
Subject = "Your Subject",
Body = "Hello, World, from Gmail API!",
From = new MailAddress("xpto#gmail.com")
};
msg.To.Add(new MailAddress("someone#gmail.com"));
msg.ReplyTo.Add(msg.From);
var msgStr = new StringWriter();
msg.Save(msgStr);
Message m = new Message();
m.Raw = Base64UrlEncode(msgStr.ToString());
var draft = new Draft();
draft.Message = m;
try
{
Service.Users.Drafts.Create(draft, "xpto#opportunity.com.br").Execute();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
message > raw is expected to be the full SMTP message.
{
"message": {
"raw": "From: me#example.com\nTo:you#example.com\nSubject:Ignore\n\nTest message\n"
}
Alternatively, you can also set the appropriate fields in message > payload:
{
"message": {
"payload": {
"headers": {
{"name": "From", "value": "me#example.com},
{"name": "To", "value": "you#example.com"},
{"name": "Subject", "value":"Ignore"}
},
"body": {
"data": "Test message"
}
}
}
}
Short version
Logged in as a Facebook user, I use my oAuth token to assume an IAM role on AWS. It returns what looks to be valid credentials, e.g. there is an AccessKeyId, SecretAccessKey that are similar length to our master keys.
When I try to use these credentials to access a DynamoDB table, I get one of two exceptions:
"The remote server returned an error: (400) Bad Request."; or
"The security token included in the request is invalid.".
I'm using the AWS C# SDK version 1.5.25.0
Long version
As I said above, I'm trying to access a DynamoDB table on AWS using credentials supplied by AmazonSecurityTokenServiceClient authorized by Facebook Identity as described in this AWS guide.
The policy for the IAM role that I've created is:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:PutItem",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:UpdateItem"
],
"Sid": "Stmt1372350145000",
"Resource": [
"*"
],
"Effect": "Allow"
}
]
}
How I get the credentials:
The user logs in with Facebook using oAuth.
Using the access token, I assume the IAM role using a AmazonSecurityTokenServiceClient.AssumeRoleWithWebIdentity with a request.
This returns what looks like to be valid credentials, e.g. a AccessKeyId, SecretAccessKey that are similar length to our master keys.
using (var tokenServiceClient = new AmazonSecurityTokenServiceClient(RegionEndpoint.USEast1))
{
var request = new AssumeRoleWithWebIdentityRequest
{
DurationSeconds = (int)TimeSpan.FromHours(1).TotalSeconds,
ProviderId = "graph.facebook.com",
RoleArn = "arn:aws:iam::193557284355:role/Facebook-OAuth",
RoleSessionName = result.id,
WebIdentityToken = FBClient.AccessToken
};
var response = tokenServiceClient.AssumeRoleWithWebIdentity(request);
AWSAssumedRoleUser = response.AssumeRoleWithWebIdentityResult.AssumedRoleUser;
AWSCredentials = response.AssumeRoleWithWebIdentityResult.Credentials;
}
How I use these credentials:
Using the returned credentials, I then try to access a AWS DynamoDB resource.
using (var client = new AmazonDynamoDBClient(AWSCredentials.AccessKeyId, AWSCredentials.SecretAccessKey, AWSCredentials.SessionToken, RegionEndpoint.USEast1))
{
var context = new DynamoDBContext(client);
var data = context.Scan<SomeData>();
}
This returns "The remote server returned an error: (400) Bad Request." when trying to Scan the table.
This is where the variation in the exception message is; if I omit the AWSCredentials.SessionToken from the above AmazonDynamoDBClient
using (var client = new AmazonDynamoDBClient(AWSCredentials.AccessKeyId, AWSCredentials.SecretAccessKey, RegionEndpoint.USEast1))
{
var context = new DynamoDBContext(client);
var data = context.Scan<SomeData>();
}
This returns "The security token included in the request is invalid." when trying to Scan the table.
Question
At this point I cannot tell what is wrong, are the credentials invalid or that I'm not passing everything through that is needed to AWS.
Can anyone offer any insight to what is wrong or how I could debug this further?
I cross-posted my question to the AWS forums and received an answer from an Amazon engineer.
https://forums.aws.amazon.com/message.jspa?messageID=465057
DynamoDBContext object invokes DescribeTable on the target table (and caches this data, so for optimal performance you would want to keep the context object around for as long as possible, so this call is only done once per target table). Modify your policy as follows:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:PutItem",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:UpdateItem",
"dynamodb:DescribeTable"
],
"Sid": "Stmt1372350145000",
"Resource": [
"*"
],
"Effect": "Allow"
}
]
}