Can someone share datasets in PowerBI across different service principle profiles? - azure

We would like to expose some datasets that are common for all customers and could be imported into their powerbi reports across different workspaces.
We have a design where we use one service principle profile per customer. This in provides securoty around isolating the data of each customer. Is there any way (using service principle profiles) to be able to support the sharing of some common data across workspaces?

Is there any way (using service principal profiles) to be able to support the sharing of some common data across workspaces?
Yes, you can create a security group in Azure AD and add all the service principals that are modelled to your customers in that group and then provide that security group access to your workspace by adding the group as with required permissions on the workspace where the shared dataset exists. If you want to give access to individual service principals, you can do that too.
Your customers can then use the SP’s client ID, Tenant ID and Secret to access the dataset via their app in the browser or even by calling the data via PowerShell. You can directly share your dataset with the security group containing your Service principals.
Created a security group in Azure AD and added my service principal to the group: -
Power BI settings:-
Go to app.powerbi.com Log in to your Power BI workspace > click on Settings > Admin Portal > Tenant Settings > Developer Settings > Allow service principals to use Power BI API’s Enable > and then provide access to your security group
There are 2 ways you can share your data with the Service principal
1. Sharing directly
By directly sharing the workspace dataset with the Security group where the service principals exist.
2. Access permissions on workspace
Provide Access to the workspace so the sp’s can access data set directly via their app or call the workspace via Power shell.
Go to your workspace > Select your data and click on … dots > Manage permissions > Grant people access > Select your Power BI Embed group
You can change the read write permissions later >
Calling Dataset via Application -
Now, We can call this Dataset from our application by adding the Authentication to Service principal and adding the SP’s Client ID, Tenant ID, Client Secret etc. You can refer this document :-
https://learn.microsoft.com/en-us/power-bi/developer/embedded/embed-organization-app
Authentication Code :
This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices (IServiceCollection services) {
services
.AddMicrosoftIdentityWebAppAuthentication(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi(PowerBiServiceApi.RequiredScopes)
.AddInMemoryTokenCaches();
services.AddScoped (typeof (PowerBiServiceApi));
var mvcBuilder = services.AddControllersWithViews (options => {
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add (new AuthorizeFilter (policy));
});
mvcBuilder.AddMicrosoftIdentityUI();
services.AddRazorPages();
}
app.settings:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "xxxx.onmicrosoft.com",
"TenantId": "xxxxxxxxxxxxx",
"ClientId": "xxxxxxxxxxxxx",
"ClientSecret": "xxxxxxxx",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath": "/signout-callback-oidc"
},
"PowerBi": {
"ServiceRootUrl": "https://api.powerbi.com"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
Controller.cs
private PowerBiServiceApi powerBiServiceApi;
public HomeController (PowerBiServiceApi powerBiServiceApi) {
this.powerBiServiceApi = powerBiServiceApi;
}
[AllowAnonymous]
public IActionResult Index() {
return View();
}
public async Task<IActionResult> Embed() {
Guid workspaceId = new Guid("11111111-1111-1111-1111-111111111111");
Guid reportId = new Guid("22222222-2222-2222-2222-222222222222");
var viewModel = await powerBiServiceApi.GetReport(workspaceId, reportId);
return View(viewModel);
}
[AllowAnonymous]
[ResponseCache (Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error() {
return View (new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
Embed your powerbi data set with JS
$(function(){
// 1 - Get DOM object for div that is report container
let reportContainer = document.getElementById("embed-container");
// 2 - Get report embedding data from view model
let reportId = window.viewModel.reportId;
let embedUrl = window.viewModel.embedUrl;
let token = window.viewModel.token
// 3 - Embed report using the Power BI JavaScript API.
let models = window['powerbi-client'].models;
let config = {
type: 'report',
id: reportId,
embedUrl: embedUrl,
accessToken: token,
permissions: models.Permissions.All,
tokenType: models.TokenType.Aad,
viewMode: models.ViewMode.View,
settings: {
panes: {
filters: { expanded: false, visible: true },
pageNavigation: { visible: false }
}
}
};
// Embed the report and display it within the div container.
let report = powerbi.embed(reportContainer, config);
Add such codes depending on the framework of your customer’s App and run the app to access the Power BI data.
Accessing PowerBI workspace with Powershell -
Refer the document here :- https://learn.microsoft.com/en-us/powershell/module/microsoftpowerbimgmt.profile/connect-powerbiserviceaccount?view=powerbi-ps
Powershell commands :-
Install PowerBI Powershell module -
Install-Module -Name MicrosoftPowerBIMgmt
Connect to PowerBI SP -
Connect-PowerBIServiceAccount -ServicePrincipal -Credential (Get-Credential) -Tenant 83331f4e-7f45-4ce4-99ed-af9038592395
In the User name enter the App Id of the SP and in Password add the secret that was created for the SP during app registration.
connected to PowerBI successfully :-
Get the workspace -
Get-PowerBIWorkspace
Reference :-
https://learn.microsoft.com/en-us/power-bi/developer/embedded/embed-service-principal
https://learn.microsoft.com/en-us/power-bi/developer/embedded/embed-organization-app

Related

How to manage Azure DevOps group permissions using Rest API

I'm working on an automation task where I need a group to have a set of permissions on Repos, Pipelines, and releases, etc. I'm looking for a Rest API that can manage the permissions for this group.
For Example:
At Cross repo policies, how do I manage/set the permissions for the group "PROJECT ADMINISTRATORS" to allow the "Bypass policies when pushing", "Bypass policies when pushing", etc using a Rest API.
Thank you in Advance.
Based on your requirement, you could use the Rest API: Access Control Entries - Set Access Control Entries
POST https://dev.azure.com/{organization}/_apis/accesscontrolentries/{securityNamespaceId}?api-version=6.0
Request Body:
{
"token": "repoV2/{ProjectID}/{RepoID(If you want to set the permission for a single repo)}",
"merge": true,
"accessControlEntries": [
{
"descriptor": "Microsoft.TeamFoundation.Identity;S-....",
"allow": 32768,
"deny": 0,
"extendedinfo": {}
}
]
}
You can get the parameter values needed in the Rest API through the following methods:
securityNamespaceId:
GET https://dev.azure.com/{OrganizationName}/_apis/securitynamespaces?api-version=6.0
In the Response Body: you could search for the Git Repositories.
Then you could get the namespaceid and Parameter values corresponding to permissions.
For example:
To get the Group Identity(S-...), there is no directly Rest API to get it. You use the following method to get it:
1.Get the descriptor:
GET https://vssps.dev.azure.com/{org name}/_apis/graph/users?api-version=5.1-preview.1
2.Using the following C# code to convert it:
public static string Base64Decode(string base64EncodedData)
{
var lengthMod4 = base64EncodedData.Length % 4;
if (lengthMod4 != 0)
{
//fix Invalid length for a Base-64 char array or string
base64EncodedData += new string('=', 4 - lengthMod4);
}
var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
}
public static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
Here has a blog which written by our Azure Identity Team engineer, you could refer to it for more detailed information.
In addition, you can also obtain the values of all parameters directly through the browser F12 record.

extension attributes for ad user in azure active directory using Microsoft.Graph

Hi I am trying to add extension property to azure ad user using Microsoft.Graph package.
var schema = new SchemaExtension()
{
Id = "Location",
TargetTypes = new List<string> { "User" },
Description = "DescribesLocation",
Properties = new List<ExtensionSchemaProperty>() { new ExtensionSchemaProperty { Name = "LocationCode", Type = "String" } }
};
var result = graphClient.SchemaExtensions.Request().AddAsync(schema).Result;
I have created a daemon application in azure ad registered applications and given below permissions to the application
Directory.Read.All
Directory.ReadWrite.All
User.Invite.All
User.Read
User.Read.All
User.ReadWrite.All
Group.Read.All
Group.ReadWrite.All
I am getting error message as Code: Authorization_RequestDenied
Message: Insufficient privileges to complete the operation. What other permissions are required for this operation and which permissions are not required?
You need the Directory.AccessAsUser.All permission. Here are some test cases that you may find useful.

Accessing Azure Assigned Groups via Razor or Controllers in ASP.NET Core

My ASP.NET Core web app is using an Azure Active Directory tenant and using OpenID Connect to sign-in users. I'm able to login successfully and I'm able to view the full list of Claims on a user with the following code:
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
My security token includes the following "groups":
{
type: "groups",
value: "e8f1a447-336a-47bb-8c26-79f1183f989f"
},
{
type: "groups",
value: "38421450-61ba-457b-bec2-e908d42d6b92"
}
I'm having trouble trying to determine how to capture these groups to perform logic in my Razor views and controllers. For example, I need to hide/show a button in my Razor view depending on whether a user is in a specific group. In my controllers I may need to allow/deny an action.
What is the standard/preferred method to do this in ASP.NET Core?
When Azure AD adds applicable group claims to the token it issues for users, the value for the group claim will be the Object ID of the security group and not the name of the security group(a group’s name can be changed in the directory so it is not a reliable identifier for the group ) .You could check whether the user’s existence in the security group in controller by :
// Look for the groups claim for the 'Dev/Test' group.
const string devTestGroup = "99dbdfac-91f7-4a0f-8eb0-57bf422abf29";
Claim groupDevTestClaim = User.Claims.FirstOrDefault(
c => c.Type == "groups" &&
c.Value.Equals(devTestGroup, StringComparison.CurrentCultureIgnoreCase));
// If the app has write permissions and the user is in the Dev/Test group...
if (null != groupDevTestClaim)
{
//
// Code to add the resource goes here.
//
ViewBag.inGroup = true;
}
else
{
ViewBag.inGroup = false;
}
Then in view , you could control whether show/hide links/buttons :
#if (ViewBag.inGroup)
{
<div>show/hide button/link goes here</div>
}
In your AppSettings.json, add your group's name and GUID object ID:
"AzureAdAuthorizationGroups": {
"MyGroup": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}
Next, hook up authorisation in your Startup.cs ConfigureServices method
services.AddAuthorization(options => {
options.AddPolicy("MyGroup", policyBuilder => policyBuilder.RequireClaim("groups", Configuration.GetValue<string>("AzureAdAuthorizationGroups:MyGroup")));
});
Finally in your view:
#if ((await AuthorizationService.AuthorizeAsync(User, "MyGroup")).Succeeded)
{
// ...
}

Assigning an Azure AD Group permissions to a OneDrive For Business file via some API

I have successfully been able to share a OneDrive for Business file with a group using the Sharepoint CMOS API.
Like this:
context.Load(listItem, l => l.HasUniqueRoleAssignments);
context.ExecuteQuery();
if (!listItem.HasUniqueRoleAssignments)
{
listItem.BreakRoleInheritance(true, false);
}
var group = context.Web.SiteGroups.GetByName(groupName);
context.Load(context.Site.RootWeb, w => w.RoleDefinitions);
context.ExecuteQuery();
var roleTypeKind = readOnly ? RoleType.Reader : RoleType.Editor;
var role = context.Site.RootWeb.RoleDefinitions.FirstOrDefault(rd => rd.RoleTypeKind == roleTypeKind);
var roleDefCollection = new RoleDefinitionBindingCollection(context) { role };
listItem.RoleAssignments.Add(group, roleDefCollection);
context.ExecuteQuery();
But the problem is that group belongs to SiteGroups which is really local to one user. So if I have an Azure AD Group I cannot access it using the Sharepoint API.
I can find an Azure AD group using the GraphClient API like this:
var azuregroup = activeDirectoryClient.Groups
.Where(g=> g.DisplayName.Equals("groupname"))
.ExecuteAsync().Result.CurrentPage.ToList().First() as Group;
But how do I grant that group access to the file using an/any API ? It should be possible since it is possible in the OneDrive for Business UI.
It is not possible doing this
listItem.RoleAssignments.Add(azuregroup, roleDefCollection);
because the first parameter to Add has to be a Principal which the azuregroup is not.
Hope anybody can help - I have searched the internet for countless hours.

Query for rootDSE tokenGroups comes out empty, should contain security groups for user

A little background:
I am trying to get synchronization of one of our development ADs to Azure Active Directory set up, using the Microsoft DirSync tool. The configuration fails, as the tool claims the AD account I am trying to use lacks membership of the "Enterprise Admins" group.
Digging into the root cause, I have been able to pinpoint that the code doing the check is querying the security groups like this:
public static void Repro(string userName, string password)
{
string name = ADValidation.GetUsersForest(userName, password).Name;
string path = string.Format("LDAP://{0}/", name);
string rootDse = string.Format("{0}rootDSE", path);
Console.WriteLine("Path: {0}", path);
Console.WriteLine("RootDse: {0}", rootDse);
const AuthenticationTypes authenticationType = AuthenticationTypes.Signing | AuthenticationTypes.Secure;
using (var entry = new DirectoryEntry(rootDse, userName, password, authenticationType))
{
entry.RefreshCache(new[] { "tokenGroups" });
Console.WriteLine(entry.Properties["tokenGroups"].Count);
}
}
Running this block on our corporate AD yields the list of security groups as expected:
Path: LDAP://SOMECORPDOMAIN.EXT/
RootDse: LDAP://SOMECORPDOMAIN.EXT/rootDSE
39
While running it on the development AD yields no groups:
Path: LDAP://SOMEDEVDOMAIN.EXT/
RootDse: LDAP://SOMEDEVDOMAIN.EXT/rootDSE
0
My knowledge of AD is quite limited - can anyone give me some pointers as to why the development AD would not return any security groups for the query?
Thanks in advance
/Michael
It may be related to AD user permissions.

Resources