PowerBI embed token with RLS through C# API - c#

We want to provide embedded PowerBI reports to our customers. Initially, we're looking at using one report with a dataset that contains data for all customers. I have created a service principal (app only auth) that has access to a workspace where I've published the report. I've also created a role "business_customer" with a DAX expression ([CustomerNumber] = USERNAME()) on this report.
I'm trying to get an embed token for the report with row level security through the C# API (Microsoft.PowerBI.Api):
var tenantId = "<tenantId>";
var datasetId = "<datasetId>";
var reportId = "<reportId>";
var groupId = "<groupId>";
var applicationPassword = "<applicationPassword>";
var context = new AuthenticationContext($"https://login.microsoftonline.com/{tenantId}/");
var clientCredential = new ClientCredential(applicationId, applicationPassword);
var result = context.AcquireTokenAsync("https://analysis.windows.net/powerbi/api",
clientCredential).Result;
var authHeader = new TokenCredentials(result.AccessToken, "Bearer");
var client = new PowerBIClient(new Uri("https://api.powerbi.com"), authHeader);
var groups = client.Groups.GetGroups();
var tokenRequest = new GenerateTokenRequest(TokenAccessLevel.View, datasetId,
new EffectiveIdentity("username", new List<string> { datasetId },
new List<string> { "business_customer" }));
var embedToken = client.Reports.GenerateTokenInGroupAsync(new Guid(groupId), new Guid(reportId),
tokenRequest, cancellationToken: CancellationToken.None).Result;
This fails on the GenerateTokenInGroupsAsync call:
"Invalid request: Creating embed token for accessing dataset <datasetId> shouldn't have effective identity"
I've tried with different usernames and I've tried leaving the username blank, but all of my attempts have failed.
Is there a way of getting an embed token this way or am I on the wrong path? What would be the "correct" way of solving this case?
Thanks in advance

Related

FirebaseAuthentication.net package in Xamarin Forms - get current user UID

In my Xamarin.Forms project, I saw that the FirebaseAuthentication.Net package can be used for the cross-platform project. How would I get the current user's UID with this? I have figured out how to get the current user's email by saving the firebase refresh token and using NewtonSoft.JSON, but am not sure how to do this with the id. I want to be able to do this so that I can store the user's data in the database under their uid and retrieve it that way.
Here's what I have so far for getting the email address of the current user:
var authProvider = new FirebaseAuthProvider(new FirebaseConfig(WebAPIKey));
var savedfirebaseauth = JsonConvert.DeserializeObject<Firebase.Auth.FirebaseAuth>(Preferences.Get("FirebaseRefreshToken", ""));
var RefreshedContent = await authProvider.RefreshAuthAsync(savedfirebaseauth);
Preferences.Set("FirebaseRefreshToken", JsonConvert.SerializeObject(RefreshedContent));
string UsersEmailToDisplay = savedfirebaseauth.User.Email;
What I am storing as "FirebaseRefreshToken" is the following.
var auth = await authProvider.CreateUserWithEmailAndPasswordAsync(Email.Text, Password.Text);
var content = await auth.GetFreshAuthAsync();
var serializedcontent = JsonConvert.SerializeObject(content);
Please let me know if any more clarification of my question is needed.
I think you want to get the UserId which unique to the Firebase project.
You could try to call its LocalId after you sign in.
var authProvider = new FirebaseAuthProvider(new FirebaseConfig(""));
var auth = await authProvider.SignInWithOAuthAsync(authType, accessToken);
var uid = auth.User.LocalId;

Extracting ListItems from a List in a SharePoint Site

I am trying to extract a list of items in a SharePoint Site below the root site at host.sharepoint.com/sites/mysite. I've tried a bunch of different methods, but only one seems to work:
var host = "host.sharepoint.com:/";
var siteName = "mysite";
var listName = "MyList";
// Generate the Client Connection
var graphHelper = new ApplicationAuthenticatedClient(ClientId, Tenant, ClientSecret);
await graphHelper.ConnectAsync().ConfigureAwait(false);
// Code: itemNotFound
//Message: The provided path does not exist, or does not represent a site
//var list = await graphHelper.GraphClient.Sites[$"{host}{siteName}"].Request().GetAsync();
// Returns a Site, no Lists.
//var list = await graphHelper.GraphClient.Sites[host].Sites[siteName].Request().GetAsync();
//Code: itemNotFound
//Message: The provided path does not exist, or does not represent a site
//var list = await graphHelper.GraphClient.Sites[host].Sites[siteName].Lists[listName].Request().GetAsync();
// List retrieved, but no Items
//var site = await graphHelper.GraphClient.Sites[host].Sites[siteName].Request().Expand("lists").GetAsync();
//var list = await graphHelper.GraphClient.Sites[site.Id].Lists[listName].Request().Expand("Items").GetAsync();
//Code: invalidRequest
//Message: Can only provide expand and select for expand options
//var queryOptions = new List<QueryOption>() { new QueryOption("expand", "fields") };
// This works
var site = await graphHelper.GraphClient.Sites[host].Sites[siteName].Request().GetAsync();
var list = await graphHelper.GraphClient.Sites[site.Id].Lists[listName].Items.Request().Expand("Fields").GetAsync();
I've finally managed to get it to connect, but I'm wondering if there's a better way to navigate to the list, rather than the two API calls? (Assuming that I don't know the Site ID beforehand)
Edit: Using the Graph Explorer, I can access the items using https://graph.microsoft.com/v1.0/sites/{host}.sharepoint.com:/sites/{siteName}:/lists/{listName}/items?expand=fields, but I don't know how to (or if) access that API call in a single call in the .NET API.
It appears that I was on the right track with var list = await graphHelper.GraphClient.Sites[$"{host}{siteName}"].Request().GetAsync(); but the URI was not formatted correctly.
The correct Site ID for https://host.sharepoint.com/sites/mysite/MyList is:
Sites[host.sharepoint.com:/sites/mysite:"]
Retrieving the list from the code in my original question would look like this:
var host = "host.sharepoint.com";
var siteName = "mysite";
var listName = "MyList";
// Generate the Client Connection
var graphHelper = new ApplicationAuthenticatedClient(ClientId, Tenant, ClientSecret);
await graphHelper.ConnectAsync().ConfigureAwait(false);
var list = await graphHelper.GraphClient.Sites[$"{host}:/sites/{siteName}:"].Lists[listName].Request().GetAsync();
it's possible in one API call.
GET https://graph.microsoft.com/v1.0/sites/{host}.sharepoint.com:/sites/{siteName}:/lists/{listTitle}/items?$expand=Fields

AADSTS500011 - PowerBi C# .NET (resource x not found in tenant y)

I am trying to integrate the power bi embedded with C#, I always have this same error that comes out, I put it to you just below, as well as the versions of the packages and the code (basic) which is supposed to do the work .
Thank you for all your answers
Microsoft.PowerBI.Api (v2.0.12)
Microsoft.PowerBI.JavaScript (v2.5.1)
Microsoft.IdentityModel.Clients.ActiveDirectory (v3.13.9)
Microsoft PowerBI JavaScript (v2.5.1)
Microsoft IdentityModel Clients.ActiveDirectory (v3.13.9)
Note that the two head variables are temporary.
The error always come out at this line : var authenticationResult = await authenticationContext.AcquireTokenAsync(this.resourceUrl, this.applicationId, credential);
There is the error message : "exceptionMessage": "AADSTS500011: The resource principal named https://analysis.windows.net/powerbi/api/ was not found in the tenant named x. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.
public async Task<EmbedConfigResource> EmbedReport([FromUri]string username, [FromUri]string roles)
{
roles = "None";
username = this.pbiUsername;
var result = new EmbedConfigResource { Username = username, Roles = roles };
var credential = new UserPasswordCredential(this.pbiUsername, this.pbiPassword);
var authenticationContext = new AuthenticationContext(this.authorityUrl);
var authenticationResult = await authenticationContext.AcquireTokenAsync(this.resourceUrl, this.applicationId, credential);
var tokenCredentials = new TokenCredentials(authenticationResult.AccessToken, "Bearer");
using (var client = new PowerBIClient(new Uri(this.apiUrl), tokenCredentials))
{
var reports = await client.Reports.GetReportsInGroupAsync(this.workspaceId);
Report report = reports.Value.FirstOrDefault(r => r.Id == this.reportId);
var datasets = await client.Datasets.GetDatasetByIdInGroupAsync(this.workspaceId, report.DatasetId);
result.IsEffectiveIdentityRequired = datasets.IsEffectiveIdentityRequired;
result.IsEffectiveIdentityRolesRequired = datasets.IsEffectiveIdentityRolesRequired;
GenerateTokenRequest generateTokenRequestParameters;
var rls = new EffectiveIdentity(this.pbiUsername, new List<string> { report.DatasetId });
if (!string.IsNullOrWhiteSpace(roles))
{
var rolesList = new List<string>();
rolesList.AddRange(roles.Split(','));
rls.Roles = rolesList;
}
generateTokenRequestParameters = new GenerateTokenRequest(accessLevel: "view", identities: new List<EffectiveIdentity> { rls });
var tokenResponse = await client.Reports.GenerateTokenInGroupAsync(this.workspaceId, report.Id, generateTokenRequestParameters);
result.EmbedToken = tokenResponse;
result.EmbedUrl = report.EmbedUrl;
result.Id = report.Id;
return result;
}
}
You must log into Azure portal, go to Azure Active Directory -> App registrations, select your app, click View API permissions, and then grant admin consent by clicking the button at the bottom:
If you don't have access to the portal, or the button is disabled, you must ask your admin to do it for you.

ElasticSearch.NET passing AWS Signature v4 in POST requests?

I am very new to ElasticSearch, and have set up an AWS Lambda function in c# to take the content of S3 object(s) (which contain JSON data) with the hopes of posting them to ES to be searchable.
I'm using the Elasticsearch.Net nuget library.
In the documentation here - https://github.com/elastic/elasticsearch-net there is samples of configuring the node URI etc, but my understanding is that any requests to ES need to be signed with AWS Signature V4 (based on Access/Secret key). I have created an IAM user for this purpose, but nowhere in the documentation can I find how to sign the POST requests. The samples show post methods, but no place to include the signature?
E.g.
var person = new Person
{
FirstName = "Martijn",
LastName = "Laarman"
};
var indexResponse = lowlevelClient.Index<BytesResponse>("people", "person", "1", PostData.Serializable(person));
byte[] responseBytes = indexResponse.Body;
var asyncIndexResponse = await lowlevelClient.IndexAsync<StringResponse>("people", "person", "1", PostData.Serializable(person));
string responseString = asyncIndexResponse.Body;
Even when instantiating the connection, nowhere is there a place to add your credentials?
var settings = new ConnectionConfiguration(new Uri("http://example.com:9200"))
.RequestTimeout(TimeSpan.FromMinutes(2));
var lowlevelClient = new ElasticLowLevelClient(settings);
I have checked ConnectionConfiguration object but there's no methods or properties that seem related. What have I missed?
Using the elasticsearch-net-aws package you should be able to set up the low level client like so:
var httpConnection = new AwsHttpConnection("us-east-1"); // or whatever region you're using
var pool = new SingleNodeConnectionPool(new Uri(TestConfig.Endpoint));
var config = new ConnectionConfiguration(pool, httpConnection);
config.DisableDirectStreaming();
var client = new ElasticLowLevelClient(config);
// ...
// if using an access key
var httpConnection = new AwsHttpConnection("us-east-1", new StaticCredentialsProvider(new AwsCredentials
{
AccessKey = "My AWS access key",
SecretKey = "My AWS secret key",
}));
// if using app.config, environment variables, or roles
var httpConnection = new AwsHttpConnection("us-east-1");
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var config = new ConnectionSettings(pool, httpConnection);
var client = new ElasticClient(config);

Azure - Programmatically Create Storage Account

I have tried the following code to create a new storage account in Azure:
Getting the token (success - I received a token):
var cc = new ClientCredential("clientId", "clientSecret");
var context = new AuthenticationContext("https://login.windows.net/subscription");
var result = context.AcquireTokenAsync("https://management.azure.com/", cc);
Create cloud storage credentials:
var credential = new TokenCloudCredentials("subscription", token);
Create the cloud storage account (fails):
using (var storageClient = new StorageManagementClient(credentials))
{
await storageClient.StorageAccounts.CreateAsync(new StorageAccountCreateParameters
{
Label = "samplestorageaccount",
Location = LocationNames.NorthEurope,
Name = "myteststorage",
AccountType = "RA-GRS"
});
}
Error:
ForbiddenError: The server failed to authenticate the request. Verify
that the certificate is valid and is associated with this
subscription.
I am not sure if this is one of those misleading messages or if I misconfigured something in Azure?
As far as I know, Azure provides two types of storage management library now.
Microsoft.Azure.Management.Storage
Microsoft.WindowsAzure.Management.Storage
Microsoft.Azure.Management.Storage is used to create new ARM storage.
Microsoft.WindowsAzure.Management.Storage is used to create classic ARM storage.
I guess you want to create the new arm storage but you used the "Microsoft.WindowsAzure.Management.Storage" library. Since the "Microsoft.WindowsAzure.Management.Storage" uses the certificate to auth requests, you will get the error. If you want to know how to use "Microsoft.WindowsAzure.Management.Storage" to create classic storage, I suggest you refer to this article.
I assume you want to create new ARM storage, I suggest you install the "Microsoft.Azure.Management.Storage" Nuget package.
More details, you could refer to the following code.
static void Main(string[] args)
{
var subscriptionId = "your subscriptionId";
var clientId = "your client id";
var tenantId = "your tenantid";
var secretKey = "secretKey";
StorageManagementClient StorageManagement = new StorageManagementClient(new Microsoft.Azure.TokenCloudCredentials(subscriptionId, GetAccessToken(tenantId, clientId, secretKey)));
var re= StorageManagement.StorageAccounts.CreateAsync("groupname", "sotrage name",new Microsoft.Azure.Management.Storage.Models.StorageAccountCreateParameters() {
Location = LocationNames.NorthEurope,
AccountType = Microsoft.Azure.Management.Storage.Models.AccountType.PremiumLRS
},new CancellationToken() { }).Result;
Console.ReadKey();
}
static string GetAccessToken(string tenantId, string clientId, string secretKey)
{
var authenticationContext = new AuthenticationContext($"https://login.windows.net/{tenantId}");
var credential = new ClientCredential(clientId, secretKey);
var result = authenticationContext.AcquireTokenAsync("https://management.core.windows.net/",
credential);
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
var token = result.Result.AccessToken;
return token;
}

Categories

Resources