diff --git a/.NET Core/Embed for your customers/AppOwnsData/Models/AzureAd.cs b/.NET Core/Embed for your customers/AppOwnsData/Models/AzureAd.cs index 245f616b..e9c571f0 100644 --- a/.NET Core/Embed for your customers/AppOwnsData/Models/AzureAd.cs +++ b/.NET Core/Embed for your customers/AppOwnsData/Models/AzureAd.cs @@ -11,7 +11,7 @@ public class AzureAd public string AuthenticationMode { get; set; } // URL used for initiating authorization request - public string AuthorityUri { get; set; } + public string AuthorityUrl { get; set; } // Client Id (Application Id) of the AAD app public string ClientId { get; set; } @@ -19,8 +19,8 @@ public class AzureAd // Id of the Azure tenant in which AAD app is hosted. Required only for Service Principal authentication mode. public string TenantId { get; set; } - // Scope of AAD app. Use the below configuration to use all the permissions provided in the AAD app through Azure portal. - public string[] Scope { get; set; } + // ScopeBase of AAD app. Use the below configuration to use all the permissions provided in the AAD app through Azure portal. + public string[] ScopeBase { get; set; } // Master user email address. Required only for MasterUser authentication mode. public string PbiUsername { get; set; } diff --git a/.NET Core/Embed for your customers/AppOwnsData/Services/AadService.cs b/.NET Core/Embed for your customers/AppOwnsData/Services/AadService.cs index 34e0538a..d604ec94 100644 --- a/.NET Core/Embed for your customers/AppOwnsData/Services/AadService.cs +++ b/.NET Core/Embed for your customers/AppOwnsData/Services/AadService.cs @@ -31,12 +31,12 @@ public string GetAccessToken() if (azureAd.Value.AuthenticationMode.Equals("masteruser", StringComparison.InvariantCultureIgnoreCase)) { // Create a public client to authorize the app with the AAD app - IPublicClientApplication clientApp = PublicClientApplicationBuilder.Create(azureAd.Value.ClientId).WithAuthority(azureAd.Value.AuthorityUri).Build(); + IPublicClientApplication clientApp = PublicClientApplicationBuilder.Create(azureAd.Value.ClientId).WithAuthority(azureAd.Value.AuthorityUrl).Build(); var userAccounts = clientApp.GetAccountsAsync().Result; try { // Retrieve Access token from cache if available - authenticationResult = clientApp.AcquireTokenSilent(azureAd.Value.Scope, userAccounts.FirstOrDefault()).ExecuteAsync().Result; + authenticationResult = clientApp.AcquireTokenSilent(azureAd.Value.ScopeBase, userAccounts.FirstOrDefault()).ExecuteAsync().Result; } catch (MsalUiRequiredException) { @@ -45,7 +45,7 @@ public string GetAccessToken() { password.AppendChar(key); } - authenticationResult = clientApp.AcquireTokenByUsernamePassword(azureAd.Value.Scope, azureAd.Value.PbiUsername, password).ExecuteAsync().Result; + authenticationResult = clientApp.AcquireTokenByUsernamePassword(azureAd.Value.ScopeBase, azureAd.Value.PbiUsername, password).ExecuteAsync().Result; } } @@ -53,7 +53,7 @@ public string GetAccessToken() else if (azureAd.Value.AuthenticationMode.Equals("serviceprincipal", StringComparison.InvariantCultureIgnoreCase)) { // For app only authentication, we need the specific tenant id in the authority url - var tenantSpecificUrl = azureAd.Value.AuthorityUri.Replace("organizations", azureAd.Value.TenantId); + var tenantSpecificUrl = azureAd.Value.AuthorityUrl.Replace("organizations", azureAd.Value.TenantId); // Create a confidential client to authorize the app with the AAD app IConfidentialClientApplication clientApp = ConfidentialClientApplicationBuilder @@ -62,7 +62,7 @@ public string GetAccessToken() .WithAuthority(tenantSpecificUrl) .Build(); // Make a client call if Access token is not available in cache - authenticationResult = clientApp.AcquireTokenForClient(azureAd.Value.Scope).ExecuteAsync().Result; + authenticationResult = clientApp.AcquireTokenForClient(azureAd.Value.ScopeBase).ExecuteAsync().Result; } return authenticationResult.AccessToken; diff --git a/.NET Core/Embed for your customers/AppOwnsData/Services/ConfigValidatorService.cs b/.NET Core/Embed for your customers/AppOwnsData/Services/ConfigValidatorService.cs index ec568ffd..b6feeecd 100644 --- a/.NET Core/Embed for your customers/AppOwnsData/Services/ConfigValidatorService.cs +++ b/.NET Core/Embed for your customers/AppOwnsData/Services/ConfigValidatorService.cs @@ -26,7 +26,7 @@ public static string ValidateConfig(IOptions azureAd, IOptions { message = "Authentication mode is not set in appsettings.json file"; } - else if (string.IsNullOrWhiteSpace(azureAd.Value.AuthorityUri)) + else if (string.IsNullOrWhiteSpace(azureAd.Value.AuthorityUrl)) { message = "Authority is not set in appsettings.json file"; } @@ -38,9 +38,9 @@ public static string ValidateConfig(IOptions azureAd, IOptions { message = "Tenant Id is not set in appsettings.json file"; } - else if (azureAd.Value.Scope is null || azureAd.Value.Scope.Length == 0) + else if (azureAd.Value.ScopeBase is null || azureAd.Value.ScopeBase.Length == 0) { - message = "Scope is not set in appsettings.json file"; + message = "Scope base is not set in appsettings.json file"; } else if (string.IsNullOrWhiteSpace(powerBI.Value.WorkspaceId)) { diff --git a/.NET Core/Embed for your customers/AppOwnsData/Services/PbiEmbedService.cs b/.NET Core/Embed for your customers/AppOwnsData/Services/PbiEmbedService.cs index 5b3de38c..301d0dde 100644 --- a/.NET Core/Embed for your customers/AppOwnsData/Services/PbiEmbedService.cs +++ b/.NET Core/Embed for your customers/AppOwnsData/Services/PbiEmbedService.cs @@ -17,7 +17,7 @@ namespace AppOwnsData.Services public class PbiEmbedService { private readonly AadService aadService; - private readonly string urlPowerBiServiceApiRoot = "https://api.powerbi.com"; + private readonly string powerBiApiUrl = "https://api.powerbi.com"; public PbiEmbedService(AadService aadService) { @@ -31,7 +31,7 @@ public PbiEmbedService(AadService aadService) public PowerBIClient GetPowerBIClient() { var tokenCredentials = new TokenCredentials(aadService.GetAccessToken(), "Bearer"); - return new PowerBIClient(new Uri(urlPowerBiServiceApiRoot ), tokenCredentials); + return new PowerBIClient(new Uri(powerBiApiUrl ), tokenCredentials); } /// diff --git a/.NET Core/Embed for your customers/AppOwnsData/appsettings.json b/.NET Core/Embed for your customers/AppOwnsData/appsettings.json index ec223865..42a1d855 100644 --- a/.NET Core/Embed for your customers/AppOwnsData/appsettings.json +++ b/.NET Core/Embed for your customers/AppOwnsData/appsettings.json @@ -1,10 +1,10 @@ { "AzureAd": { "AuthenticationMode": "ServicePrincipal", - "AuthorityUri": "https://login.microsoftonline.com/organizations/", + "AuthorityUrl": "https://login.microsoftonline.com/organizations/", "ClientId": "", "TenantId": "", - "Scope": ["https://analysis.windows.net/powerbi/api/.default"], + "ScopeBase": ["https://analysis.windows.net/powerbi/api/.default"], "PbiUsername": "", "PbiPassword": "", "ClientSecret": "" diff --git a/.NET Core/Embed for your organization/CloudAppSettings/Power BI Global/appsettings.json b/.NET Core/Embed for your organization/CloudAppSettings/Power BI Global/appsettings.json new file mode 100644 index 00000000..fb698f9c --- /dev/null +++ b/.NET Core/Embed for your organization/CloudAppSettings/Power BI Global/appsettings.json @@ -0,0 +1,6 @@ +{ + "AzureAd": { + "AuthorityUrl": "https://login.microsoftonline.com/", + "Scopes": ["https://analysis.windows.net/powerbi/api/Report.Read.All https://analysis.windows.net/powerbi/api/Dashboard.Read.All https://analysis.windows.net/powerbi/api/Workspace.Read.All"] + } +} \ No newline at end of file diff --git a/.NET Core/Embed for your organization/CloudAppSettings/Power BI US Government/appsettings.json b/.NET Core/Embed for your organization/CloudAppSettings/Power BI US Government/appsettings.json new file mode 100644 index 00000000..b46bf6d4 --- /dev/null +++ b/.NET Core/Embed for your organization/CloudAppSettings/Power BI US Government/appsettings.json @@ -0,0 +1,6 @@ +{ + "AzureAd": { + "AuthorityUrl": "https://login.microsoftonline.com/", + "Scopes": ["https://analysis.usgovcloudapi.net/powerbi/api/Report.Read.All https://analysis.usgovcloudapi.net/powerbi/api/Dashboard.Read.All https://analysis.usgovcloudapi.net/powerbi/api/Workspace.Read.All"] + } +} \ No newline at end of file diff --git a/.NET Core/Embed for your organization/CloudAppSettings/Power BI in Germany/appsettings.json b/.NET Core/Embed for your organization/CloudAppSettings/Power BI in Germany/appsettings.json new file mode 100644 index 00000000..25cfb091 --- /dev/null +++ b/.NET Core/Embed for your organization/CloudAppSettings/Power BI in Germany/appsettings.json @@ -0,0 +1,6 @@ +{ + "AzureAd": { + "AuthorityUrl": "https://login.microsoftonline.de/", + "Scopes": ["https://analysis.cloudapi.de/powerbi/api/Report.Read.All https://analysis.cloudapi.de/powerbi/api/Dashboard.Read.All https://analysis.cloudapi.de/powerbi/api/Workspace.Read.All"] + } +} \ No newline at end of file diff --git a/.NET Core/Embed for your organization/CloudAppSettings/Power BI operated by 21Vianet in China/appsettings.json b/.NET Core/Embed for your organization/CloudAppSettings/Power BI operated by 21Vianet in China/appsettings.json new file mode 100644 index 00000000..de2485ae --- /dev/null +++ b/.NET Core/Embed for your organization/CloudAppSettings/Power BI operated by 21Vianet in China/appsettings.json @@ -0,0 +1,6 @@ +{ + "AzureAd": { + "AuthorityUrl": "https://login.chinacloudapi.cn/", + "Scopes": ["https://analysis.chinacloudapi.cn/powerbi/api/Report.Read.All https://analysis.chinacloudapi.cn/powerbi/api/Dashboard.Read.All https://analysis.chinacloudapi.cn/powerbi/api/Workspace.Read.All"] + } +} \ No newline at end of file diff --git a/.NET Core/Embed for your organization/UserOwnsData/Controllers/HomeController.cs b/.NET Core/Embed for your organization/UserOwnsData/Controllers/HomeController.cs index fd86a91b..d25bf2de 100644 --- a/.NET Core/Embed for your organization/UserOwnsData/Controllers/HomeController.cs +++ b/.NET Core/Embed for your organization/UserOwnsData/Controllers/HomeController.cs @@ -5,9 +5,9 @@ namespace UserOwnsData.Controllers { - using UserOwnsData.Service; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.Configuration; using Microsoft.Identity.Web; using Microsoft.Graph; using System.Threading.Tasks; @@ -20,11 +20,15 @@ public class HomeController : Controller private readonly ITokenAcquisition m_tokenAcquisition; + public IConfiguration Configuration { get; } + public HomeController(ITokenAcquisition tokenAcquisition, - GraphServiceClient graphServiceClient) + GraphServiceClient graphServiceClient, + IConfiguration configuration) { this.m_tokenAcquisition = tokenAcquisition; this.m_graphServiceClient = graphServiceClient; + Configuration = configuration; } [AllowAnonymous] @@ -34,11 +38,11 @@ public IActionResult Index() } // Redirects to login page to request increment consent - [AuthorizeForScopes(Scopes = new string[] { PowerBiScopes.ReadDashboard, PowerBiScopes.ReadReport, PowerBiScopes.ReadWorkspace })] + [AuthorizeForScopes(ScopeKeySection = "AzureAd:Scopes")] public async Task Embed() { // Generate token for the signed in user - var accessToken = await m_tokenAcquisition.GetAccessTokenForUserAsync(new string[] { PowerBiScopes.ReadDashboard, PowerBiScopes.ReadReport, PowerBiScopes.ReadWorkspace }); + var accessToken = await m_tokenAcquisition.GetAccessTokenForUserAsync(Configuration["AzureAd:Scopes:0"].Split(" ")); // Get username of logged in user var userInfo = await m_graphServiceClient.Me.Request().GetAsync(); diff --git a/.NET Core/Embed for your organization/UserOwnsData/Service/PowerBiScopes.cs b/.NET Core/Embed for your organization/UserOwnsData/Service/PowerBiScopes.cs deleted file mode 100644 index 307664dd..00000000 --- a/.NET Core/Embed for your organization/UserOwnsData/Service/PowerBiScopes.cs +++ /dev/null @@ -1,14 +0,0 @@ -// ---------------------------------------------------------------------------- -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// ---------------------------------------------------------------------------- - -namespace UserOwnsData.Service -{ - public class PowerBiScopes - { - public const string ReadReport = "https://analysis.windows.net/powerbi/api/Report.Read.All"; - public const string ReadDashboard = "https://analysis.windows.net/powerbi/api/Dashboard.Read.All"; - public const string ReadWorkspace = "https://analysis.windows.net/powerbi/api/Workspace.Read.All"; - } -} diff --git a/.NET Core/Embed for your organization/UserOwnsData/Startup.cs b/.NET Core/Embed for your organization/UserOwnsData/Startup.cs index 2212c114..87b20c4e 100644 --- a/.NET Core/Embed for your organization/UserOwnsData/Startup.cs +++ b/.NET Core/Embed for your organization/UserOwnsData/Startup.cs @@ -5,7 +5,6 @@ namespace UserOwnsData { - using UserOwnsData.Service; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -21,6 +20,7 @@ public class Startup public Startup(IConfiguration configuration) { Configuration = configuration; + Configuration["AzureAd:Instance"] = Configuration["AzureAd:AuthorityUrl"]; } public IConfiguration Configuration { get; } diff --git a/.NET Core/Embed for your organization/UserOwnsData/appsettings.json b/.NET Core/Embed for your organization/UserOwnsData/appsettings.json index 07bac121..1ae981ce 100644 --- a/.NET Core/Embed for your organization/UserOwnsData/appsettings.json +++ b/.NET Core/Embed for your organization/UserOwnsData/appsettings.json @@ -1,8 +1,10 @@ { "AzureAd": { - "Instance": "https://login.microsoftonline.com/", + "Instance": "", + "AuthorityUrl": "https://login.microsoftonline.com/", "Domain": "", "TenantId": "common", + "Scopes": ["https://analysis.windows.net/powerbi/api/Report.Read.All https://analysis.windows.net/powerbi/api/Dashboard.Read.All https://analysis.windows.net/powerbi/api/Workspace.Read.All"], "ClientId": "", "ClientSecret": "", "CallbackPath": "/signin-oidc" diff --git a/.NET Core/EncryptCredentials/CloudAppSettings/Power BI Global/appsettings.json b/.NET Core/EncryptCredentials/CloudAppSettings/Power BI Global/appsettings.json new file mode 100644 index 00000000..1cb0389a --- /dev/null +++ b/.NET Core/EncryptCredentials/CloudAppSettings/Power BI Global/appsettings.json @@ -0,0 +1,7 @@ +{ + "AzureAd": { + "AuthorityUrl": "https://login.microsoftonline.com/organizations/", + "PowerBiApiUrl": "https://api.powerbi.com/", + "ScopeBase": ["https://analysis.windows.net/powerbi/api/.default"] + } +} \ No newline at end of file diff --git a/.NET Core/EncryptCredentials/CloudAppSettings/Power BI US Government/appsettings.json b/.NET Core/EncryptCredentials/CloudAppSettings/Power BI US Government/appsettings.json new file mode 100644 index 00000000..e13360b5 --- /dev/null +++ b/.NET Core/EncryptCredentials/CloudAppSettings/Power BI US Government/appsettings.json @@ -0,0 +1,7 @@ +{ + "AzureAd": { + "AuthorityUrl": "https://login.microsoftonline.com/organizations/", + "PowerBiApiUrl": "https://api.powerbigov.us/", + "ScopeBase": ["https://analysis.usgovcloudapi.net/powerbi/api/.default"] + } +} \ No newline at end of file diff --git a/.NET Core/EncryptCredentials/CloudAppSettings/Power BI in Germany/appsettings.json b/.NET Core/EncryptCredentials/CloudAppSettings/Power BI in Germany/appsettings.json new file mode 100644 index 00000000..7692a5cb --- /dev/null +++ b/.NET Core/EncryptCredentials/CloudAppSettings/Power BI in Germany/appsettings.json @@ -0,0 +1,7 @@ +{ + "AzureAd": { + "AuthorityUrl": "https://login.microsoftonline.de/organizations/", + "PowerBiApiUrl": "https://api.powerbi.de/", + "ScopeBase": ["https://analysis.cloudapi.de/powerbi/api/.default"] + } +} \ No newline at end of file diff --git a/.NET Core/EncryptCredentials/CloudAppSettings/Power BI operated by 21Vianet in China/appsettings.json b/.NET Core/EncryptCredentials/CloudAppSettings/Power BI operated by 21Vianet in China/appsettings.json new file mode 100644 index 00000000..6f7e92eb --- /dev/null +++ b/.NET Core/EncryptCredentials/CloudAppSettings/Power BI operated by 21Vianet in China/appsettings.json @@ -0,0 +1,7 @@ +{ + "AzureAd": { + "AuthorityUrl": "https://login.chinacloudapi.cn/organizations/", + "PowerBiApiUrl": "https://api.powerbi.cn/", + "ScopeBase": ["https://analysis.chinacloudapi.cn/powerbi/api/.default"] + } +} \ No newline at end of file diff --git a/.NET Core/EncryptCredentials/EncryptCredentials/Models/AzureAd.cs b/.NET Core/EncryptCredentials/EncryptCredentials/Models/AzureAd.cs index 78dc391e..2bb48b29 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials/Models/AzureAd.cs +++ b/.NET Core/EncryptCredentials/EncryptCredentials/Models/AzureAd.cs @@ -11,7 +11,7 @@ public class AzureAd public string AuthenticationMode { get; set; } // URL used for initiating authorization request - public string AuthorityUri { get; set; } + public string AuthorityUrl { get; set; } // Client Id (Application Id) of the AAD app public string ClientId { get; set; } @@ -19,8 +19,11 @@ public class AzureAd // Id of the Azure tenant in which AAD app is hosted. Required only for Service Principal authentication mode. public string TenantId { get; set; } - // Scope of AAD app. Use the below configuration to use all the permissions provided in the AAD app through Azure portal. - public string[] Scope { get; set; } + // End point URL for REST API + public string PowerBiApiUrl { get; set; } + + // ScopeBase of AAD app. Use the below configuration to use all the permissions provided in the AAD app through Azure portal. + public string[] ScopeBase { get; set; } // Master user email address. Required only for MasterUser authentication mode. public string PbiUsername { get; set; } diff --git a/.NET Core/EncryptCredentials/EncryptCredentials/Services/AadService.cs b/.NET Core/EncryptCredentials/EncryptCredentials/Services/AadService.cs index b58e6857..9518feb2 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials/Services/AadService.cs +++ b/.NET Core/EncryptCredentials/EncryptCredentials/Services/AadService.cs @@ -31,12 +31,12 @@ public string GetAccessToken() if (azureAd.Value.AuthenticationMode.Equals("masteruser", StringComparison.InvariantCultureIgnoreCase)) { // Create a public client to authorize the app with the AAD app - IPublicClientApplication clientApp = PublicClientApplicationBuilder.Create(azureAd.Value.ClientId).WithAuthority(azureAd.Value.AuthorityUri).Build(); + IPublicClientApplication clientApp = PublicClientApplicationBuilder.Create(azureAd.Value.ClientId).WithAuthority(azureAd.Value.AuthorityUrl).Build(); var userAccounts = clientApp.GetAccountsAsync().Result; try { // Retrieve Access token from cache if available - authenticationResult = clientApp.AcquireTokenSilent(azureAd.Value.Scope, userAccounts.FirstOrDefault()).ExecuteAsync().Result; + authenticationResult = clientApp.AcquireTokenSilent(azureAd.Value.ScopeBase, userAccounts.FirstOrDefault()).ExecuteAsync().Result; } catch (MsalUiRequiredException) { @@ -45,7 +45,7 @@ public string GetAccessToken() { password.AppendChar(key); } - authenticationResult = clientApp.AcquireTokenByUsernamePassword(azureAd.Value.Scope, azureAd.Value.PbiUsername, password).ExecuteAsync().Result; + authenticationResult = clientApp.AcquireTokenByUsernamePassword(azureAd.Value.ScopeBase, azureAd.Value.PbiUsername, password).ExecuteAsync().Result; } } @@ -53,7 +53,7 @@ public string GetAccessToken() else { // For app only authentication, we need the specific tenant id in the authority url - var tenantSpecificUrl = azureAd.Value.AuthorityUri.Replace("organizations", azureAd.Value.TenantId); + var tenantSpecificUrl = azureAd.Value.AuthorityUrl.Replace("organizations", azureAd.Value.TenantId); // Create a confidential client to authorize the app with the AAD app IConfidentialClientApplication clientApp = ConfidentialClientApplicationBuilder @@ -62,10 +62,14 @@ public string GetAccessToken() .WithAuthority(tenantSpecificUrl) .Build(); // Make a client call if Access token is not available in cache - authenticationResult = clientApp.AcquireTokenForClient(azureAd.Value.Scope).ExecuteAsync().Result; + authenticationResult = clientApp.AcquireTokenForClient(azureAd.Value.ScopeBase).ExecuteAsync().Result; } return authenticationResult.AccessToken; } + + public string GetPowerBiApiUrl() { + return azureAd.Value.PowerBiApiUrl; + } } } diff --git a/.NET Core/EncryptCredentials/EncryptCredentials/Services/ConfigValidator.cs b/.NET Core/EncryptCredentials/EncryptCredentials/Services/ConfigValidator.cs index 1742d702..d8ac74ba 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials/Services/ConfigValidator.cs +++ b/.NET Core/EncryptCredentials/EncryptCredentials/Services/ConfigValidator.cs @@ -26,7 +26,7 @@ public static string ValidateConfig(IOptions azureAd) { message = $"Authentication mode is incorrect or {Constants.InvalidAppSetting}"; } - else if (string.IsNullOrWhiteSpace(azureAd.Value.AuthorityUri)) + else if (string.IsNullOrWhiteSpace(azureAd.Value.AuthorityUrl)) { message = $"Authority {Constants.InvalidAppSetting}"; } @@ -38,9 +38,9 @@ public static string ValidateConfig(IOptions azureAd) { message = $"Tenant Id {Constants.InvalidAppSetting}"; } - else if (azureAd.Value.Scope is null || azureAd.Value.Scope.Length == 0) + else if (azureAd.Value.ScopeBase is null || azureAd.Value.ScopeBase.Length == 0) { - message = $"Scope {Constants.InvalidAppSetting}"; + message = $"Scope base {Constants.InvalidAppSetting}"; } else if (isAuthModeMasterUser && string.IsNullOrWhiteSpace(azureAd.Value.PbiUsername)) { diff --git a/.NET Core/EncryptCredentials/EncryptCredentials/Services/PowerBIService.cs b/.NET Core/EncryptCredentials/EncryptCredentials/Services/PowerBIService.cs index b87a7ea1..e0f6741b 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials/Services/PowerBIService.cs +++ b/.NET Core/EncryptCredentials/EncryptCredentials/Services/PowerBIService.cs @@ -16,7 +16,6 @@ namespace EncryptCredentials.Services public class PowerBIService { private readonly AadService aadService; - private readonly string urlPowerBiServiceApiRoot = "https://api.powerbi.com"; public PowerBIService(AadService aadService) { @@ -30,7 +29,7 @@ public PowerBIService(AadService aadService) public PowerBIClient GetPowerBIClient() { var tokenCredentials = new TokenCredentials(aadService.GetAccessToken(), "Bearer"); - return new PowerBIClient(new Uri(urlPowerBiServiceApiRoot), tokenCredentials); + return new PowerBIClient(new Uri(aadService.GetPowerBiApiUrl()), tokenCredentials); } /// @@ -110,7 +109,18 @@ public CredentialDetails GetCredentialDetails(Guid gatewayId, string credentialT var credentials = GetCredentials(credentialType, credentialsArray); // Get the Getway - var gateway = GetGateway(gatewayId); + Gateway gateway = new Gateway(gatewayId); + try + { + gateway = GetGateway(gatewayId); + } + catch (HttpOperationException e) + { + if (e.Response.ReasonPhrase != "Not Found") + { + throw; + } + } // Initialize credentialsEncryptor and encryptedConnection for Cloud gateway var credentialsEncryptor = (AsymmetricKeyEncryptor)null; diff --git a/.NET Core/EncryptCredentials/EncryptCredentials/appsettings.json b/.NET Core/EncryptCredentials/EncryptCredentials/appsettings.json index 3f63e14f..f20bf934 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials/appsettings.json +++ b/.NET Core/EncryptCredentials/EncryptCredentials/appsettings.json @@ -1,10 +1,11 @@ { "AzureAd": { "AuthenticationMode": "ServicePrincipal", - "AuthorityUri": "https://login.microsoftonline.com/organizations/", + "AuthorityUrl": "https://login.microsoftonline.com/organizations/", "ClientId": "", "TenantId": "", - "Scope": ["https://analysis.windows.net/powerbi/api/.default"], + "PowerBiApiUrl": "https://api.powerbi.com/", + "ScopeBase": ["https://analysis.windows.net/powerbi/api/.default"], "RedirectUri": "http://localhost:5001/", "PbiUsername": "", "PbiPassword": "", diff --git a/.NET Framework/Embed for your customers/AppOwnsData/Services/AadService.cs b/.NET Framework/Embed for your customers/AppOwnsData/Services/AadService.cs index 9c0e210d..c5e297da 100644 --- a/.NET Framework/Embed for your customers/AppOwnsData/Services/AadService.cs +++ b/.NET Framework/Embed for your customers/AppOwnsData/Services/AadService.cs @@ -15,7 +15,7 @@ namespace AppOwnsData.Services public class AadService { private static readonly string m_authorityUrl = ConfigurationManager.AppSettings["authorityUrl"]; - private static readonly string[] m_scope = ConfigurationManager.AppSettings["scope"].Split(';'); + private static readonly string[] m_scope = ConfigurationManager.AppSettings["scopeBase"].Split(';'); /// /// Get Access token diff --git a/.NET Framework/Embed for your customers/AppOwnsData/Services/EmbedService.cs b/.NET Framework/Embed for your customers/AppOwnsData/Services/EmbedService.cs index 0006a1ad..f6352b6b 100644 --- a/.NET Framework/Embed for your customers/AppOwnsData/Services/EmbedService.cs +++ b/.NET Framework/Embed for your customers/AppOwnsData/Services/EmbedService.cs @@ -18,12 +18,12 @@ namespace AppOwnsData.Services public static class EmbedService { - private static readonly string urlPowerBiServiceApiRoot = ConfigurationManager.AppSettings["urlPowerBiServiceApiRoot"]; + private static readonly string powerBiApiUrl = ConfigurationManager.AppSettings["powerBiApiUrl"]; public static async Task GetPowerBiClient() { var tokenCredentials = new TokenCredentials(await AadService.GetAccessToken(), "Bearer"); - return new PowerBIClient(new Uri(urlPowerBiServiceApiRoot), tokenCredentials); + return new PowerBIClient(new Uri(powerBiApiUrl), tokenCredentials); } /// diff --git a/.NET Framework/Embed for your customers/AppOwnsData/Web.config b/.NET Framework/Embed for your customers/AppOwnsData/Web.config index 18b8180f..80b1d389 100644 --- a/.NET Framework/Embed for your customers/AppOwnsData/Web.config +++ b/.NET Framework/Embed for your customers/AppOwnsData/Web.config @@ -32,8 +32,8 @@ Licensed under the MIT license.--> - - + + diff --git a/.NET Framework/Embed for your organization/CloudConfigs/Power BI Global/Cloud.config b/.NET Framework/Embed for your organization/CloudConfigs/Power BI Global/Cloud.config index 856fe989..3f1a43b7 100644 --- a/.NET Framework/Embed for your organization/CloudConfigs/Power BI Global/Cloud.config +++ b/.NET Framework/Embed for your organization/CloudConfigs/Power BI Global/Cloud.config @@ -1,7 +1,7 @@  - + - + \ No newline at end of file diff --git a/.NET Framework/Embed for your organization/CloudConfigs/Power BI US Government/Cloud.config b/.NET Framework/Embed for your organization/CloudConfigs/Power BI US Government/Cloud.config index efd056d4..f9132883 100644 --- a/.NET Framework/Embed for your organization/CloudConfigs/Power BI US Government/Cloud.config +++ b/.NET Framework/Embed for your organization/CloudConfigs/Power BI US Government/Cloud.config @@ -1,7 +1,7 @@  - - + + + \ No newline at end of file diff --git a/.NET Framework/Embed for your organization/CloudConfigs/Power BI in Germany/Cloud.config b/.NET Framework/Embed for your organization/CloudConfigs/Power BI in Germany/Cloud.config index 50488477..81703bdf 100644 --- a/.NET Framework/Embed for your organization/CloudConfigs/Power BI in Germany/Cloud.config +++ b/.NET Framework/Embed for your organization/CloudConfigs/Power BI in Germany/Cloud.config @@ -1,7 +1,7 @@  - + - + \ No newline at end of file diff --git a/.NET Framework/Embed for your organization/CloudConfigs/Power BI operated by 21Vianet in China/Cloud.config b/.NET Framework/Embed for your organization/CloudConfigs/Power BI operated by 21Vianet in China/Cloud.config index e787ea8c..af00abd6 100644 --- a/.NET Framework/Embed for your organization/CloudConfigs/Power BI operated by 21Vianet in China/Cloud.config +++ b/.NET Framework/Embed for your organization/CloudConfigs/Power BI operated by 21Vianet in China/Cloud.config @@ -1,7 +1,7 @@  - + - + \ No newline at end of file diff --git a/.NET Framework/Embed for your organization/UserOwnsData/Services/ConfigValidatorService.cs b/.NET Framework/Embed for your organization/UserOwnsData/Services/ConfigValidatorService.cs index 69d54fa5..d8bdc756 100644 --- a/.NET Framework/Embed for your organization/UserOwnsData/Services/ConfigValidatorService.cs +++ b/.NET Framework/Embed for your organization/UserOwnsData/Services/ConfigValidatorService.cs @@ -33,17 +33,17 @@ public static string ValidateConfig() { message = "Redirect Uri is not set in Web.config file"; } - else if (string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["authorityUri"])) + else if (string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["authorityUrl"])) { - message = "Authority Uri is not set in Web.config file"; + message = "Authority Url is not set in Web.config file"; } else if (string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["powerBiApiUrl"])) { message = "Power BI Api Url is not set in Web.config file"; } - else if (string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["powerBiPermissionApi"])) + else if (string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["scopeBase"])) { - message = "Power BI Permission Api is not set in Web.config file"; + message = "Scope base is not set in Web.config file"; } return message; diff --git a/.NET Framework/Embed for your organization/UserOwnsData/Services/Security/OwinOpenIdConnect.cs b/.NET Framework/Embed for your organization/UserOwnsData/Services/Security/OwinOpenIdConnect.cs index f00dd339..0b1fc72f 100644 --- a/.NET Framework/Embed for your organization/UserOwnsData/Services/Security/OwinOpenIdConnect.cs +++ b/.NET Framework/Embed for your organization/UserOwnsData/Services/Security/OwinOpenIdConnect.cs @@ -19,7 +19,7 @@ namespace UserOwnsData.Services.Security public class OwinOpenIdConnect { - private static readonly string tenantCommonAuthority = ConfigurationManager.AppSettings["authorityUri"]; + private static readonly string tenantCommonAuthority = ConfigurationManager.AppSettings["authorityUrl"]; private static readonly string clientId = ConfigurationManager.AppSettings["clientId"]; private static readonly string clientSecret = ConfigurationManager.AppSettings["clientSecret"]; private static readonly string redirectUri = ConfigurationManager.AppSettings["redirectUri"]; diff --git a/.NET Framework/Embed for your organization/UserOwnsData/Services/Security/PowerBiPermissionScopes.cs b/.NET Framework/Embed for your organization/UserOwnsData/Services/Security/PowerBiPermissionScopes.cs index 85843b72..b45cc381 100644 --- a/.NET Framework/Embed for your organization/UserOwnsData/Services/Security/PowerBiPermissionScopes.cs +++ b/.NET Framework/Embed for your organization/UserOwnsData/Services/Security/PowerBiPermissionScopes.cs @@ -9,12 +9,12 @@ namespace UserOwnsData.Services.Security public class PowerBIPermissionScopes { - private static readonly string powerBiPermissionApi = ConfigurationManager.AppSettings["powerBiPermissionApi"]; + private static readonly string scopeBase = ConfigurationManager.AppSettings["scopeBase"]; public static readonly string[] ReadUserWorkspaces = new string[] { - powerBiPermissionApi + "Workspace.Read.All", - powerBiPermissionApi + "Report.Read.All", - powerBiPermissionApi + "Dashboard.Read.All" + scopeBase + "Workspace.Read.All", + scopeBase + "Report.Read.All", + scopeBase + "Dashboard.Read.All" }; } } diff --git a/.NET Framework/Embed for your organization/UserOwnsData/Web.config b/.NET Framework/Embed for your organization/UserOwnsData/Web.config index f51e7eeb..e763338c 100644 --- a/.NET Framework/Embed for your organization/UserOwnsData/Web.config +++ b/.NET Framework/Embed for your organization/UserOwnsData/Web.config @@ -6,9 +6,9 @@ - + - + diff --git a/Java/Embed for your customers/AppOwnsData/src/main/java/com/embedsample/appownsdata/config/Config.java b/Java/Embed for your customers/AppOwnsData/src/main/java/com/embedsample/appownsdata/config/Config.java index 2fe80603..d66133cc 100644 --- a/Java/Embed for your customers/AppOwnsData/src/main/java/com/embedsample/appownsdata/config/Config.java +++ b/Java/Embed for your customers/AppOwnsData/src/main/java/com/embedsample/appownsdata/config/Config.java @@ -39,7 +39,7 @@ public abstract class Config { // DO NOT CHANGE public static final String authorityUrl = "https://login.microsoftonline.com/"; - public static final String scopeUrl = "https://analysis.windows.net/powerbi/api/.default"; + public static final String scopeBase = "https://analysis.windows.net/powerbi/api/.default"; private Config(){ diff --git a/Java/Embed for your customers/AppOwnsData/src/main/java/com/embedsample/appownsdata/services/AzureADService.java b/Java/Embed for your customers/AppOwnsData/src/main/java/com/embedsample/appownsdata/services/AzureADService.java index dd5a06ea..5bb33cd2 100644 --- a/Java/Embed for your customers/AppOwnsData/src/main/java/com/embedsample/appownsdata/services/AzureADService.java +++ b/Java/Embed for your customers/AppOwnsData/src/main/java/com/embedsample/appownsdata/services/AzureADService.java @@ -70,7 +70,7 @@ private static String getAccessTokenUsingServicePrincipal(String clientId, Strin .build(); ClientCredentialParameters clientCreds = ClientCredentialParameters.builder( - Collections.singleton(Config.scopeUrl)) + Collections.singleton(Config.scopeBase)) .build(); // Acquire new AAD token @@ -103,7 +103,7 @@ private static String getAccessTokenUsingMasterUser(String clientId, String user .build(); UserNamePasswordParameters userCreds = UserNamePasswordParameters.builder( - Collections.singleton(Config.scopeUrl), + Collections.singleton(Config.scopeBase), username, password.toCharArray()).build(); diff --git a/Java/EncryptCredentials/CloudConfigs/Power BI Global/Config.java b/Java/EncryptCredentials/CloudConfigs/Power BI Global/Config.java new file mode 100644 index 00000000..2a8ae387 --- /dev/null +++ b/Java/EncryptCredentials/CloudConfigs/Power BI Global/Config.java @@ -0,0 +1,13 @@ +// ---------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// ---------------------------------------------------------------------------- + +//Replace these configs with the one in the Config.java file for sovereign cloud + +public abstract class Config { + + public static final String authorityUrl = "https://login.microsoftonline.com/"; + public static final String powerBiApiUrl = "https://api.powerbi.com/"; + public static final String scopeBase = "https://analysis.windows.net/powerbi/api/.default"; +} \ No newline at end of file diff --git a/Java/EncryptCredentials/CloudConfigs/Power BI US Government/Config.java b/Java/EncryptCredentials/CloudConfigs/Power BI US Government/Config.java new file mode 100644 index 00000000..f50dc40d --- /dev/null +++ b/Java/EncryptCredentials/CloudConfigs/Power BI US Government/Config.java @@ -0,0 +1,13 @@ +// ---------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// ---------------------------------------------------------------------------- + +//Replace these configs with the one in the Config.java file for sovereign cloud + +public abstract class Config { + + public static final String authorityUrl = "https://login.microsoftonline.com/"; + public static final String powerBiApiUrl = "https://api.powerbigov.us/"; + public static final String scopeBase = "https://analysis.usgovcloudapi.net/powerbi/api/.default"; +} \ No newline at end of file diff --git a/Java/EncryptCredentials/CloudConfigs/Power BI in Germany/Config.java b/Java/EncryptCredentials/CloudConfigs/Power BI in Germany/Config.java new file mode 100644 index 00000000..af7f8b30 --- /dev/null +++ b/Java/EncryptCredentials/CloudConfigs/Power BI in Germany/Config.java @@ -0,0 +1,13 @@ +// ---------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// ---------------------------------------------------------------------------- + +//Replace these configs with the one in the Config.java file for sovereign cloud + +public abstract class Config { + + public static final String authorityUrl = "https://login.microsoftonline.de/"; + public static final String powerBiApiUrl = "https://api.powerbi.de/"; + public static final String scopeBase = "https://analysis.cloudapi.de/powerbi/api/.default"; +} \ No newline at end of file diff --git a/Java/EncryptCredentials/CloudConfigs/Power BI operated by 21Vianet in China/Config.java b/Java/EncryptCredentials/CloudConfigs/Power BI operated by 21Vianet in China/Config.java new file mode 100644 index 00000000..80606c61 --- /dev/null +++ b/Java/EncryptCredentials/CloudConfigs/Power BI operated by 21Vianet in China/Config.java @@ -0,0 +1,13 @@ +// ---------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// ---------------------------------------------------------------------------- + +//Replace these configs with the one in the Config.java file for sovereign cloud + +public abstract class Config { + + public static final String authorityUrl = "https://login.chinacloudapi.cn/"; + public static final String powerBiApiUrl = "https://api.powerbi.cn/"; + public static final String scopeBase = "https://analysis.chinacloudapi.cn/powerbi/api/.default"; +} \ No newline at end of file diff --git a/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/config/Config.java b/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/config/Config.java index 3ec6fa42..f6ba6c24 100644 --- a/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/config/Config.java +++ b/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/config/Config.java @@ -35,7 +35,8 @@ public abstract class Config { // DO NOT CHANGE public static final String authorityUrl = "https://login.microsoftonline.com/"; - public static final String scopeUrl = "https://analysis.windows.net/powerbi/api/.default"; + public static final String powerBiApiUrl = "https://api.powerbi.com/"; + public static final String scopeBase = "https://analysis.windows.net/powerbi/api/.default"; private Config() { // Private Constructor will prevent the instantiation of this class directly diff --git a/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/controllers/DataSourceController.java b/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/controllers/DataSourceController.java index 2f92ce53..ea05ccc6 100644 --- a/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/controllers/DataSourceController.java +++ b/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/controllers/DataSourceController.java @@ -94,14 +94,11 @@ public ResponseEntity updateDataController(@RequestBody UpdateDatasourceCrede // Get access token and Update Datasource Credentials String accessToken = AzureADService.getAccessToken(); - Gateway gateway = GetDatasourceData.getGateway(accessToken, request.gatewayId); - return UpdateCredentialsService.updateDatasource( accessToken, request.credType, request.privacyLevel, request.credentialsArray, - gateway.publicKey, request.gatewayId, request.datasourceId); } catch (HttpClientErrorException hcex) { diff --git a/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/services/AddCredentialsService.java b/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/services/AddCredentialsService.java index df09c28d..9aaea356 100644 --- a/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/services/AddCredentialsService.java +++ b/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/services/AddCredentialsService.java @@ -13,6 +13,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; +import com.encryptcredentialsample.encryptcredential.config.Config; import com.encryptcredentialsample.encryptcredential.models.CredentialDetails; import com.encryptcredentialsample.encryptcredential.models.GatewayPublicKey; import com.encryptcredentialsample.encryptcredential.models.PublishDatasourceToGatewayRequest; @@ -49,7 +50,7 @@ public static ResponseEntity addDataSource( public static ResponseEntity makeAddDataSourcePostRequest(String gatewayId, PublishDatasourceToGatewayRequest requestBody, String accessToken) throws ClientProtocolException, IOException { // Gateways - Create Datasource Power BI REST API // https://docs.microsoft.com/en-us/rest/api/power-bi/gateways/createdatasource - String endPointUrl = "https://api.powerbi.com/v1.0/myorg/gateways/" + gatewayId + "/datasources"; + String endPointUrl = Config.powerBiApiUrl + "v1.0/myorg/gateways/" + gatewayId + "/datasources"; // Request header HttpHeaders reqHeader = Utils.generateAuthorizationHeaders(accessToken); diff --git a/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/services/AzureADService.java b/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/services/AzureADService.java index bddd9e87..b5e7d9f5 100644 --- a/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/services/AzureADService.java +++ b/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/services/AzureADService.java @@ -73,7 +73,7 @@ private static String getAccessTokenUsingServicePrincipal(String clientId, Strin .authority(Config.authorityUrl + tenantId).build(); ClientCredentialParameters clientCreds = ClientCredentialParameters - .builder(Collections.singleton(Config.scopeUrl)).build(); + .builder(Collections.singleton(Config.scopeBase)).build(); // Acquire new AAD token IAuthenticationResult result = app.acquireToken(clientCreds).get(); @@ -109,7 +109,7 @@ private static String getAccessTokenUsingMasterUser(String clientId, String user .build(); UserNamePasswordParameters userCreds = UserNamePasswordParameters - .builder(Collections.singleton(Config.scopeUrl), username, password.toCharArray()).build(); + .builder(Collections.singleton(Config.scopeBase), username, password.toCharArray()).build(); // Acquire new AAD token IAuthenticationResult result = app.acquireToken(userCreds).get(); diff --git a/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/services/GetDatasourceData.java b/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/services/GetDatasourceData.java index fefafefe..4f73f606 100644 --- a/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/services/GetDatasourceData.java +++ b/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/services/GetDatasourceData.java @@ -13,15 +13,16 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; +import com.encryptcredentialsample.encryptcredential.config.Config; import com.encryptcredentialsample.encryptcredential.models.Gateway; import com.encryptcredentialsample.encryptcredential.models.GetDatasourcesResponse; public class GetDatasourceData { - public static Gateway getGateway(String accessToken, String getwayId) { + public static Gateway getGateway(String accessToken, String gatewayId) { // REST API URL to get data sources - String endPointUrl = "https://api.powerbi.com/v1.0/myorg/gateways/" + getwayId; + String endPointUrl = Config.powerBiApiUrl + "v1.0/myorg/gateways/" + gatewayId; // Request header HttpHeaders reqHeader = new HttpHeaders(); @@ -49,7 +50,7 @@ public static GetDatasourcesResponse getDatasourcesInGroup(String accessToken, S HttpEntity reqEntity = new HttpEntity<>(reqHeader); // https://docs.microsoft.com/en-us/rest/api/power-bi/datasets/getdatasourcesingroup - String endPointUrl = "https://api.powerbi.com/v1.0/myorg/groups/" + groupId + "/datasets/" + datasetId + "/datasources"; + String endPointUrl = Config.powerBiApiUrl + "v1.0/myorg/groups/" + groupId + "/datasets/" + datasetId + "/datasources"; // Rest API get datasources's details RestTemplate getDatasourceRestTemplate = new RestTemplate(); diff --git a/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/services/UpdateCredentialsService.java b/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/services/UpdateCredentialsService.java index ec0f10e0..21e974d3 100644 --- a/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/services/UpdateCredentialsService.java +++ b/Java/EncryptCredentials/src/main/java/com/encryptcredentialsample/encryptcredential/services/UpdateCredentialsService.java @@ -12,12 +12,13 @@ import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; +import com.encryptcredentialsample.encryptcredential.config.Config; import com.encryptcredentialsample.encryptcredential.models.CredentialDetails; import com.encryptcredentialsample.encryptcredential.models.CredentialDetailsRequestBody; import com.encryptcredentialsample.encryptcredential.models.Gateway; -import com.encryptcredentialsample.encryptcredential.models.GatewayPublicKey; public class UpdateCredentialsService { @@ -27,7 +28,6 @@ public static ResponseEntity updateDatasource( String credType, String privacyLevel, String[] credentialsArray, - GatewayPublicKey pubKey, String gatewayId, String datasourceId) throws Exception { @@ -35,16 +35,24 @@ public static ResponseEntity updateDatasource( String serializedCredentials = Utils.serializeCredentials(credentialsArray, credType); String encryptedCredentialsString = null; - - Gateway gateway = GetDatasourceData.getGateway(accessToken, gatewayId); String encryptedConnection = null; + + Gateway gateway = new Gateway(); + try { + gateway = GetDatasourceData.getGateway(accessToken, gatewayId); + } + catch (HttpClientErrorException e) { + if (!e.getStatusText().toString().equals("Not Found")) { + throw e; + } + } // On-premises gateway contains name property // Use on-premises gateway if (gateway.name != null) { encryptedConnection = "Encrypted"; // Encrypt the credentials Asymmetric Key Encryption - AsymmetricKeyEncryptorService credentialsEncryptor = new AsymmetricKeyEncryptorService(pubKey); + AsymmetricKeyEncryptorService credentialsEncryptor = new AsymmetricKeyEncryptorService(gateway.publicKey); encryptedCredentialsString = credentialsEncryptor.encodeCredentials(serializedCredentials); } else { // Use cloud gateway @@ -70,7 +78,7 @@ public static ResponseEntity makeUpdateDataSourcePatchRequest( // Gateways - Update Datasource Power BI REST API // https://docs.microsoft.com/en-us/rest/api/power-bi/gateways/updatedatasource - String endPointUrl = "https://api.powerbi.com/v1.0/myorg/gateways/" + gatewayId + "/datasources/" + datasourceId; + String endPointUrl = Config.powerBiApiUrl + "v1.0/myorg/gateways/" + gatewayId + "/datasources/" + datasourceId; // Request header HttpHeaders reqHeader = Utils.generateAuthorizationHeaders(accessToken); diff --git a/NodeJS/Embed for your customers/AppOwnsData/config/config.json b/NodeJS/Embed for your customers/AppOwnsData/config/config.json index c6e2f221..4b142fe5 100644 --- a/NodeJS/Embed for your customers/AppOwnsData/config/config.json +++ b/NodeJS/Embed for your customers/AppOwnsData/config/config.json @@ -1,8 +1,8 @@ { "authenticationMode": "MasterUser", - "authorityUri": "https://login.microsoftonline.com/common/v2.0", - "scope": "https://analysis.windows.net/powerbi/api", - "apiUrl": "https://api.powerbi.com/", + "authorityUrl": "https://login.microsoftonline.com/", + "scopeBase": "https://analysis.windows.net/powerbi/api/.default", + "powerBiApiUrl": "https://api.powerbi.com/", "clientId": "", "workspaceId": "", "reportId": "", diff --git a/NodeJS/Embed for your customers/AppOwnsData/package.json b/NodeJS/Embed for your customers/AppOwnsData/package.json index af57bba0..f36ba2c2 100644 --- a/NodeJS/Embed for your customers/AppOwnsData/package.json +++ b/NodeJS/Embed for your customers/AppOwnsData/package.json @@ -11,7 +11,7 @@ "author": "", "license": "ISC", "dependencies": { - "adal-node": "^0.2.1", + "@azure/msal-node": "^1.12.0", "body-parser": "^1.19.0", "bootstrap": "^4.4.1", "express": "^4.17.1", diff --git a/NodeJS/Embed for your customers/AppOwnsData/src/authentication.js b/NodeJS/Embed for your customers/AppOwnsData/src/authentication.js index 1480b99b..ff94065a 100644 --- a/NodeJS/Embed for your customers/AppOwnsData/src/authentication.js +++ b/NodeJS/Embed for your customers/AppOwnsData/src/authentication.js @@ -4,53 +4,43 @@ // ---------------------------------------------------------------------------- const getAccessToken = async function () { - - // Use ADAL.js for authentication - let adal = require("adal-node"); - - let AuthenticationContext = adal.AuthenticationContext; - // Create a config variable that store credentials from config.json - let config = require(__dirname + "/../config/config.json"); + const config = require(__dirname + "/../config/config.json"); - let authorityUrl = config.authorityUri; + // Use MSAL.js for authentication + const msal = require("@azure/msal-node"); + + const msalConfig = { + auth: { + clientId: config.clientId, + authority: `${config.authorityUrl}${config.tenantId}`, + } + }; // Check for the MasterUser Authentication if (config.authenticationMode.toLowerCase() === "masteruser") { - let context = new AuthenticationContext(authorityUrl); - - return new Promise( - (resolve, reject) => { - context.acquireTokenWithUsernamePassword(config.scope, config.pbiUsername, config.pbiPassword, config.clientId, function (err, tokenResponse) { - - // Function returns error object in tokenResponse - // Invalid Username will return empty tokenResponse, thus err is used - if (err) { - reject(tokenResponse == null ? err : tokenResponse); - } - resolve(tokenResponse); - }) - } - ); - - // Service Principal auth is the recommended by Microsoft to achieve App Owns Data Power BI embedding - } else if (config.authenticationMode.toLowerCase() === "serviceprincipal") { - authorityUrl = authorityUrl.replace("common", config.tenantId); - let context = new AuthenticationContext(authorityUrl); - - return new Promise( - (resolve, reject) => { - context.acquireTokenWithClientCredentials(config.scope, config.clientId, config.clientSecret, function (err, tokenResponse) { - - // Function returns error object in tokenResponse - // Invalid Username will return empty tokenResponse, thus err is used - if (err) { - reject(tokenResponse == null ? err : tokenResponse); - } - resolve(tokenResponse); - }) - } - ); + const clientApplication = new msal.PublicClientApplication(msalConfig); + + const usernamePasswordRequest = { + scopes: [config.scopeBase], + username: config.pbiUsername, + password: config.pbiPassword + }; + + return clientApplication.acquireTokenByUsernamePassword(usernamePasswordRequest); + + }; + + // Service Principal auth is the recommended by Microsoft to achieve App Owns Data Power BI embedding + if (config.authenticationMode.toLowerCase() === "serviceprincipal") { + msalConfig.auth.clientSecret = config.clientSecret + const clientApplication = new msal.ConfidentialClientApplication(msalConfig); + + const clientCredentialRequest = { + scopes: [config.scopeBase], + }; + + return clientApplication.acquireTokenByClientCredential(clientCredentialRequest); } } diff --git a/NodeJS/Embed for your customers/AppOwnsData/src/utils.js b/NodeJS/Embed for your customers/AppOwnsData/src/utils.js index a1a7a381..b3f045bc 100644 --- a/NodeJS/Embed for your customers/AppOwnsData/src/utils.js +++ b/NodeJS/Embed for your customers/AppOwnsData/src/utils.js @@ -49,8 +49,8 @@ function validateConfig() { return "WorkspaceId must be a Guid object. Please select a workspace you own and fill its Id in config.json."; } - if (!config.authorityUri) { - return "AuthorityUri is empty. Please fill valid AuthorityUri in config.json."; + if (!config.authorityUrl) { + return "AuthorityUrl is empty. Please fill valid AuthorityUrl in config.json."; } if (config.authenticationMode.toLowerCase() === "masteruser") { diff --git a/Python/Embed for your customers/AppOwnsData/config.py b/Python/Embed for your customers/AppOwnsData/config.py index 80fef337..bb10a2c5 100644 --- a/Python/Embed for your customers/AppOwnsData/config.py +++ b/Python/Embed for your customers/AppOwnsData/config.py @@ -21,11 +21,11 @@ class BaseConfig(object): # Client Secret (App Secret) of the AAD app. Required only for ServicePrincipal authentication mode. CLIENT_SECRET = '' - # Scope of AAD app. Use the below configuration to use all the permissions provided in the AAD app through Azure portal. - SCOPE = ['https://analysis.windows.net/powerbi/api/.default'] + # Scope Base of AAD app. Use the below configuration to use all the permissions provided in the AAD app through Azure portal. + SCOPE_BASE = ['https://analysis.windows.net/powerbi/api/.default'] # URL used for initiating authorization request - AUTHORITY = 'https://login.microsoftonline.com/organizations' + AUTHORITY_URL = 'https://login.microsoftonline.com/organizations' # Master user email address. Required only for MasterUser authentication mode. POWER_BI_USER = '' diff --git a/Python/Embed for your customers/AppOwnsData/services/aadservice.py b/Python/Embed for your customers/AppOwnsData/services/aadservice.py index e8cdc96e..9ec780ea 100644 --- a/Python/Embed for your customers/AppOwnsData/services/aadservice.py +++ b/Python/Embed for your customers/AppOwnsData/services/aadservice.py @@ -18,24 +18,24 @@ def get_access_token(): if app.config['AUTHENTICATION_MODE'].lower() == 'masteruser': # Create a public client to authorize the app with the AAD app - clientapp = msal.PublicClientApplication(app.config['CLIENT_ID'], authority=app.config['AUTHORITY']) + clientapp = msal.PublicClientApplication(app.config['CLIENT_ID'], authority=app.config['AUTHORITY_URL']) accounts = clientapp.get_accounts(username=app.config['POWER_BI_USER']) if accounts: # Retrieve Access token from user cache if available - response = clientapp.acquire_token_silent(app.config['SCOPE'], account=accounts[0]) + response = clientapp.acquire_token_silent(app.config['SCOPE_BASE'], account=accounts[0]) if not response: # Make a client call if Access token is not available in cache - response = clientapp.acquire_token_by_username_password(app.config['POWER_BI_USER'], app.config['POWER_BI_PASS'], scopes=app.config['SCOPE']) + response = clientapp.acquire_token_by_username_password(app.config['POWER_BI_USER'], app.config['POWER_BI_PASS'], scopes=app.config['SCOPE_BASE']) # Service Principal auth is the recommended by Microsoft to achieve App Owns Data Power BI embedding elif app.config['AUTHENTICATION_MODE'].lower() == 'serviceprincipal': - authority = app.config['AUTHORITY'].replace('organizations', app.config['TENANT_ID']) + authority = app.config['AUTHORITY_URL'].replace('organizations', app.config['TENANT_ID']) clientapp = msal.ConfidentialClientApplication(app.config['CLIENT_ID'], client_credential=app.config['CLIENT_SECRET'], authority=authority) # Make a client call if Access token is not available in cache - response = clientapp.acquire_token_for_client(scopes=app.config['SCOPE']) + response = clientapp.acquire_token_for_client(scopes=app.config['SCOPE_BASE']) try: return response['access_token'] diff --git a/Python/Embed for your customers/AppOwnsData/utils.py b/Python/Embed for your customers/AppOwnsData/utils.py index 5ba88c65..22b71a24 100644 --- a/Python/Embed for your customers/AppOwnsData/utils.py +++ b/Python/Embed for your customers/AppOwnsData/utils.py @@ -31,8 +31,8 @@ def check_config(app): elif app.config['AUTHENTICATION_MODE'].lower() == 'serviceprincipal': if app.config['CLIENT_SECRET'] == '': return 'Client secret is not provided in config.py file' - elif app.config['SCOPE'] == '': - return 'Scope is not provided in the config.py file' + elif app.config['SCOPE_BASE'] == '': + return 'Scope base is not provided in the config.py file' elif app.config['AUTHORITY_URL'] == '': return 'Authority URL is not provided in the config.py file' diff --git a/Python/Encrypt credentials/CloudConfigs/Power BI Global/config.py b/Python/Encrypt credentials/CloudConfigs/Power BI Global/config.py new file mode 100644 index 00000000..c86af4a8 --- /dev/null +++ b/Python/Encrypt credentials/CloudConfigs/Power BI Global/config.py @@ -0,0 +1,15 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +# Replace these configs with the one in the config.py file for sovereign cloud + +class BaseConfig(object): + + # Scope Base of AAD app. Use the below configuration to use all the permissions provided in the AAD app through Azure portal. + SCOPE_BASE = ['https://analysis.windows.net/powerbi/api/.default'] + + # URL used for initiating authorization request + AUTHORITY_URL = 'https://login.microsoftonline.com/organizations' + + # End point URL for Power BI API + POWER_BI_API_URL = 'https://api.powerbi.com/' \ No newline at end of file diff --git a/Python/Encrypt credentials/CloudConfigs/Power BI US Government/config.py b/Python/Encrypt credentials/CloudConfigs/Power BI US Government/config.py new file mode 100644 index 00000000..ee1a6d35 --- /dev/null +++ b/Python/Encrypt credentials/CloudConfigs/Power BI US Government/config.py @@ -0,0 +1,15 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +# Replace these configs with the one in the config.py file for sovereign cloud + +class BaseConfig(object): + + # Scope Base of AAD app. Use the below configuration to use all the permissions provided in the AAD app through Azure portal. + SCOPE_BASE = ['https://analysis.usgovcloudapi.net/powerbi/api/.default'] + + # URL used for initiating authorization request + AUTHORITY_URL = 'https://login.microsoftonline.com/organizations' + + # End point URL for Power BI API + POWER_BI_API_URL = 'https://api.powerbigov.us/' \ No newline at end of file diff --git a/Python/Encrypt credentials/CloudConfigs/Power BI in Germany/config.py b/Python/Encrypt credentials/CloudConfigs/Power BI in Germany/config.py new file mode 100644 index 00000000..b23bc530 --- /dev/null +++ b/Python/Encrypt credentials/CloudConfigs/Power BI in Germany/config.py @@ -0,0 +1,15 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +# Replace these configs with the one in the config.py file for sovereign cloud + +class BaseConfig(object): + + # Scope Base of AAD app. Use the below configuration to use all the permissions provided in the AAD app through Azure portal. + SCOPE_BASE = ['https://analysis.cloudapi.de/powerbi/api/.default'] + + # URL used for initiating authorization request + AUTHORITY_URL = 'https://login.microsoftonline.de/organizations' + + # End point URL for Power BI API + POWER_BI_API_URL = 'https://api.powerbi.de/' \ No newline at end of file diff --git a/Python/Encrypt credentials/CloudConfigs/Power BI operated by 21Vianet in China/config.py b/Python/Encrypt credentials/CloudConfigs/Power BI operated by 21Vianet in China/config.py new file mode 100644 index 00000000..42ac1628 --- /dev/null +++ b/Python/Encrypt credentials/CloudConfigs/Power BI operated by 21Vianet in China/config.py @@ -0,0 +1,15 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +# Replace these configs with the one in the config.py file for sovereign cloud + +class BaseConfig(object): + + # Scope Base of AAD app. Use the below configuration to use all the permissions provided in the AAD app through Azure portal. + SCOPE_BASE = ['https://analysis.chinacloudapi.cn/powerbi/api/.default'] + + # URL used for initiating authorization request + AUTHORITY_URL = 'https://login.chinacloudapi.cn/organizations' + + # End point URL for Power BI API + POWER_BI_API_URL = 'https://api.powerbi.cn/' \ No newline at end of file diff --git a/Python/Encrypt credentials/Encryption sample/app.py b/Python/Encrypt credentials/Encryption sample/app.py index b58c4a58..1830ea02 100644 --- a/Python/Encrypt credentials/Encryption sample/app.py +++ b/Python/Encrypt credentials/Encryption sample/app.py @@ -59,6 +59,10 @@ def update_datasource(): request_data = request.json['data'] gateway_id = request_data['gatewayId'] + gateway = { + 'id': gateway_id, + 'publicKey': None, + } # Validate the credentials data by the user data_validation_service = DataValidationService() @@ -68,9 +72,10 @@ def update_datasource(): gateway_api_response = data_source_service.get_gateway(access_token, gateway_id) if not gateway_api_response.ok: - return json.dumps({'errorMsg' : str(f'Error {gateway_api_response.status_code} {gateway_api_response.reason}\nRequest Id:\t{gateway_api_response.headers.get("RequestId")}')}), gateway_api_response.status_code - - gateway = gateway_api_response.json() + if not gateway_api_response.reason == "Not Found": + return json.dumps({'errorMsg' : str(f'Error {gateway_api_response.status_code} {gateway_api_response.reason}\nRequest Id:\t{gateway_api_response.headers.get("RequestId")}')}), gateway_api_response.status_code + else: + gateway = gateway_api_response.json() # Send fetched data to update credentials update_creds_service = UpdateCredentialsService() diff --git a/Python/Encrypt credentials/Encryption sample/config.py b/Python/Encrypt credentials/Encryption sample/config.py index 48f45482..0c44c65c 100644 --- a/Python/Encrypt credentials/Encryption sample/config.py +++ b/Python/Encrypt credentials/Encryption sample/config.py @@ -15,11 +15,14 @@ class BaseConfig(object): # Client Secret (App Secret) of the AAD app. Required only for ServicePrincipal authentication mode. CLIENT_SECRET = '' - # Scope of AAD app. Use the below configuration to use all the permissions provided in the AAD app through Azure portal. - SCOPE = ['https://analysis.windows.net/powerbi/api/.default'] + # Scope Base of AAD app. Use the below configuration to use all the permissions provided in the AAD app through Azure portal. + SCOPE_BASE = ['https://analysis.windows.net/powerbi/api/.default'] # URL used for initiating authorization request - AUTHORITY = 'https://login.microsoftonline.com/organizations' + AUTHORITY_URL = 'https://login.microsoftonline.com/organizations' + + # End point URL for Power BI API + POWER_BI_API_URL = 'https://api.powerbi.com/' # Master user email address. Required only for MasterUser authentication mode. POWER_BI_USER = '' diff --git a/Python/Encrypt credentials/Encryption sample/services/aadservice.py b/Python/Encrypt credentials/Encryption sample/services/aadservice.py index d98a0275..c2f27340 100644 --- a/Python/Encrypt credentials/Encryption sample/services/aadservice.py +++ b/Python/Encrypt credentials/Encryption sample/services/aadservice.py @@ -25,8 +25,8 @@ def get_access_token(): username = app.config['POWER_BI_USER'] password = app.config['POWER_BI_PASS'] client_secret = app.config['CLIENT_SECRET'] - scope = app.config['SCOPE'] - authority = app.config['AUTHORITY'] + scope = app.config['SCOPE_BASE'] + authority = app.config['AUTHORITY_URL'] response = None try: diff --git a/Python/Encrypt credentials/Encryption sample/services/addcredentialsservice.py b/Python/Encrypt credentials/Encryption sample/services/addcredentialsservice.py index 85ed07d1..ac02507c 100644 --- a/Python/Encrypt credentials/Encryption sample/services/addcredentialsservice.py +++ b/Python/Encrypt credentials/Encryption sample/services/addcredentialsservice.py @@ -2,6 +2,7 @@ # Licensed under the MIT license. import requests +from flask import current_app as app from flask import json from models.credentialsdetails import CredentialsDetails from models.publishdatasourcetogatewayrequest import PublishDatasourceToGatewayRequest @@ -65,7 +66,7 @@ def make_add_datasource_post_request(self, gateway_id, request_body, access_toke # Gateways - Create Datasource Power BI REST API # https://docs.microsoft.com/en-us/rest/api/power-bi/gateways/createdatasource - endpoint_url = f'https://api.powerbi.com/v1.0/myorg/gateways/{gateway_id}/datasources' + endpoint_url = f'{app.config["POWER_BI_API_URL"]}v1.0/myorg/gateways/{gateway_id}/datasources' api_response = requests.post(endpoint_url, data=json.dumps(request_body.__dict__), headers=self.headers) diff --git a/Python/Encrypt credentials/Encryption sample/services/getdatasource.py b/Python/Encrypt credentials/Encryption sample/services/getdatasource.py index 3fd80f29..0cb85132 100644 --- a/Python/Encrypt credentials/Encryption sample/services/getdatasource.py +++ b/Python/Encrypt credentials/Encryption sample/services/getdatasource.py @@ -2,7 +2,7 @@ # Licensed under the MIT license. import requests - +from flask import current_app as app class GetDatasourceService: @@ -23,7 +23,7 @@ def get_datasources_in_group(self, access_token, group_id, dataset_id): self.headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + access_token} # https://docs.microsoft.com/en-us/rest/api/power-bi/datasets/getdatasourcesingroup - endpoint_url = f'https://api.powerbi.com/v1.0/myorg/groups/{group_id}/datasets/{dataset_id}/datasources' + endpoint_url = f'{app.config["POWER_BI_API_URL"]}v1.0/myorg/groups/{group_id}/datasets/{dataset_id}/datasources' api_response = requests.get(endpoint_url, headers=self.headers) @@ -42,7 +42,7 @@ def get_gateway(self, access_token, gateway_id): self.headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + access_token} - endpoint_url = f'https://api.powerbi.com/v1.0/myorg/gateways/{gateway_id}' + endpoint_url = f'{app.config["POWER_BI_API_URL"]}v1.0/myorg/gateways/{gateway_id}' api_response = requests.get(endpoint_url, headers=self.headers) diff --git a/Python/Encrypt credentials/Encryption sample/services/updatecredentialsservice.py b/Python/Encrypt credentials/Encryption sample/services/updatecredentialsservice.py index d3760804..ff131286 100644 --- a/Python/Encrypt credentials/Encryption sample/services/updatecredentialsservice.py +++ b/Python/Encrypt credentials/Encryption sample/services/updatecredentialsservice.py @@ -2,6 +2,7 @@ # Licensed under the MIT license. import requests +from flask import current_app as app from flask import json from models.credentialsdetails import CredentialsDetails from models.credentialsdetailsrequest import CredentialsDetailsRequest @@ -72,7 +73,7 @@ def make_update_datasource_patch_request(self, credentials_details_req, gateway_ # Gateways - Update Datasource Power BI REST API # https://docs.microsoft.com/en-us/rest/api/power-bi/gateways/updatedatasource - endpoint_url = f'https://api.powerbi.com/v1.0/myorg/gateways/{gateway_id}/datasources/{datasource_id}' + endpoint_url = f'{app.config["POWER_BI_API_URL"]}v1.0/myorg/gateways/{gateway_id}/datasources/{datasource_id}' api_response = requests.patch(endpoint_url, data=json.dumps(credentials_details_req.__dict__), headers=self.headers) diff --git a/Python/Encrypt credentials/Encryption sample/utils.py b/Python/Encrypt credentials/Encryption sample/utils.py index fa2e3d61..7ecb0ae1 100644 --- a/Python/Encrypt credentials/Encryption sample/utils.py +++ b/Python/Encrypt credentials/Encryption sample/utils.py @@ -27,8 +27,8 @@ def validate_config(app): username = app.config['POWER_BI_USER'] password = app.config['POWER_BI_PASS'] client_secret = app.config['CLIENT_SECRET'] - scope = app.config['SCOPE'] - authority = app.config['AUTHORITY'] + scope = app.config['SCOPE_BASE'] + authority = app.config['AUTHORITY_URL'] if authenticate_mode == '': return 'Please specify one of the two authentication modes in config.py file' @@ -46,7 +46,7 @@ def validate_config(app): if authenticate_mode.lower() == 'serviceprincipal' and client_secret == '': return 'Client secret is not provided in config.py file' if scope == '': - return 'Scope is not provided in config.py file' + return 'Scope base is not provided in config.py file' if authority == '': return 'Authority URL is not provided in config.py file' diff --git a/Python/Encrypt credentials/requirements.txt b/Python/Encrypt credentials/requirements.txt index b8cb2952..3305f39a 100644 --- a/Python/Encrypt credentials/requirements.txt +++ b/Python/Encrypt credentials/requirements.txt @@ -1,4 +1,4 @@ cryptography==3.4.6 -Flask==1.1.2 +Flask==2.1.2 msal==1.9.0 requests==2.25.1 \ No newline at end of file diff --git a/README.md b/README.md index 7ea186c4..bea2e0de 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This repository contains Power BI Embedded solutions in 5 different frameworks t In addition, it has samples for calling the Power BI REST APIs using PowerShell. -Available Power BI Embedded solutions: +**Available solutions for embedding Power BI content:** * Embed for your customers - aka App Owns Data * .NET Framework * .NET Core @@ -34,6 +34,11 @@ Available Power BI Embedded solutions: * .NET Core * React TS +Solutions for encrypting credentials and updating data sources are available in the following frameworks: + * .NET Core + * Python + * Node JS + > **Note:** > 1. README.md files specific to samples are present in their respective folders. diff --git a/React-TS/Embed for your organization/CloudConfigs/Power BI Global/Config.ts b/React-TS/Embed for your organization/CloudConfigs/Power BI Global/Config.ts new file mode 100644 index 00000000..0d8a32a0 --- /dev/null +++ b/React-TS/Embed for your organization/CloudConfigs/Power BI Global/Config.ts @@ -0,0 +1,17 @@ +// ---------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// ---------------------------------------------------------------------------- + +// Refer https://aka.ms/PowerBIPermissions for complete list of Power BI scopes + +// Replace this config with the one in the Config.ts file for sovereign cloud + +// URL used for initiating authorization request +export const authorityUrl: string = "https://login.microsoftonline.com/common/"; + +// End point URL for Power BI API +export const powerBiApiUrl: string = "https://api.powerbi.com"; + +// Scope for securing access token +export const scopeBase: string[] = ["https://analysis.windows.net/powerbi/api/Report.Read.All"]; \ No newline at end of file diff --git a/React-TS/Embed for your organization/CloudConfigs/Power BI US Government/Config.ts b/React-TS/Embed for your organization/CloudConfigs/Power BI US Government/Config.ts new file mode 100644 index 00000000..0e26e201 --- /dev/null +++ b/React-TS/Embed for your organization/CloudConfigs/Power BI US Government/Config.ts @@ -0,0 +1,17 @@ +// ---------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// ---------------------------------------------------------------------------- + +// Refer https://aka.ms/PowerBIPermissions for complete list of Power BI scopes + +// Replace this config with the one in the Config.ts file for sovereign cloud + +// URL used for initiating authorization request +export const authorityUrl: string = "https://login.microsoftonline.com/common/"; + +// End point URL for Power BI API +export const powerBiApiUrl: string = "https://api.powerbigov.us/"; + +// Scope for securing access token +export const scopeBase: string[] = ["https://analysis.usgovcloudapi.net/powerbi/api/Report.Read.All"]; \ No newline at end of file diff --git a/React-TS/Embed for your organization/CloudConfigs/Power BI in Germany/Config.ts b/React-TS/Embed for your organization/CloudConfigs/Power BI in Germany/Config.ts new file mode 100644 index 00000000..b7db22fe --- /dev/null +++ b/React-TS/Embed for your organization/CloudConfigs/Power BI in Germany/Config.ts @@ -0,0 +1,17 @@ +// ---------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// ---------------------------------------------------------------------------- + +// Refer https://aka.ms/PowerBIPermissions for complete list of Power BI scopes + +// Replace this config with the one in the Config.ts file for sovereign cloud + +// URL used for initiating authorization request +export const authorityUrl: string = "https://login.microsoftonline.de/common/"; + +// End point URL for Power BI API +export const powerBiApiUrl: string = "https://api.powerbi.de/"; + +// Scope for securing access token +export const scopeBase: string[] = ["https://analysis.cloudapi.de/powerbi/api/Report.Read.All"]; \ No newline at end of file diff --git a/React-TS/Embed for your organization/CloudConfigs/Power BI operated by 21Vianet in China/Config.ts b/React-TS/Embed for your organization/CloudConfigs/Power BI operated by 21Vianet in China/Config.ts new file mode 100644 index 00000000..1acad4fa --- /dev/null +++ b/React-TS/Embed for your organization/CloudConfigs/Power BI operated by 21Vianet in China/Config.ts @@ -0,0 +1,17 @@ +// ---------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// ---------------------------------------------------------------------------- + +// Refer https://aka.ms/PowerBIPermissions for complete list of Power BI scopes + +// Replace this config with the one in the Config.ts file for sovereign cloud + +// URL used for initiating authorization request +export const authorityUrl: string = "https://login.chinacloudapi.cn/common/"; + +// End point URL for Power BI API +export const powerBiApiUrl: string = "https://api.powerbi.cn/"; + +// Scope for securing access token +export const scopeBase: string[] = ["https://analysis.chinacloudapi.cn/powerbi/api/Report.Read.All"]; \ No newline at end of file diff --git a/React-TS/Embed for your organization/UserOwnsData/package.json b/React-TS/Embed for your organization/UserOwnsData/package.json index e4e9b0f6..166d453e 100644 --- a/React-TS/Embed for your organization/UserOwnsData/package.json +++ b/React-TS/Embed for your organization/UserOwnsData/package.json @@ -3,9 +3,10 @@ "version": "1.0.0", "private": true, "dependencies": { + "@azure/msal-browser": "^2.28.0", + "@azure/msal-react": "^1.4.4", "@types/react": "^16.9.23", "@types/react-dom": "^16.9.5", - "msal": "^1.4.1", "powerbi-client": "^2.16.5", "react": "^16.13.0", "react-app-polyfill": "^1.0.6", diff --git a/React-TS/Embed for your organization/UserOwnsData/src/App.tsx b/React-TS/Embed for your organization/UserOwnsData/src/App.tsx index 3d4bfd03..cf6aacc4 100644 --- a/React-TS/Embed for your organization/UserOwnsData/src/App.tsx +++ b/React-TS/Embed for your organization/UserOwnsData/src/App.tsx @@ -3,8 +3,9 @@ // Licensed under the MIT license. // ---------------------------------------------------------------------------- +import { AuthenticationResult, InteractionType, EventMessage, EventType, AuthError } from "@azure/msal-browser"; +import { MsalContext } from "@azure/msal-react"; import React from "react"; -import { UserAgentApplication, AuthError, AuthResponse } from "msal"; import { service, factories, models, IEmbedConfiguration } from "powerbi-client"; import "./App.css"; import * as config from "./Config"; @@ -22,6 +23,7 @@ interface AppProps { }; interface AppState { accessToken: string; embedUrl: string; error: string[] }; class App extends React.Component { + static contextType = MsalContext; constructor(props: AppProps) { super(props); @@ -103,19 +105,22 @@ class App extends React.Component { // React function componentDidMount(): void { - if (reportRef !== null) { reportContainer = reportRef["current"]; } // User input - null check if (config.workspaceId === "" || config.reportId === "") { - this.setState({ error: ["Please assign values to workspace Id and report Id in Config.ts file"] }) - } else { - - // Authenticate the user and generate the access token - this.authenticate(); + this.setState({ error: ["Please assign values to workspace Id and report Id in Config.ts file"] }); + return; } + + this.authenticate(); + } + + // React function + componentDidUpdate(): void { + this.authenticate(); } // React function @@ -125,72 +130,58 @@ class App extends React.Component { // Authenticating to get the access token authenticate(): void { - const thisObj = this; + const msalInstance = this.context.instance; + const msalAccounts = this.context.accounts; + const msalInProgress = this.context.inProgress; + const isAuthenticated = this.context.accounts.length > 0; + + if (this.state.error.length > 0) { + return; + } - const msalConfig = { - auth: { - clientId: config.clientId + const eventCallback = msalInstance.addEventCallback((message: EventMessage) => { + if (message.eventType === EventType.LOGIN_SUCCESS && !accessToken) { + const payload = message.payload as AuthenticationResult; + const name: string = payload.account?.name ? payload.account?.name: ""; + + accessToken = payload.accessToken; + this.setUsername(name); + this.tryRefreshUserPermissions(); } - }; + }); const loginRequest = { - scopes: config.scopes + scopes: config.scopeBase, + account: msalAccounts[0] }; - const msalInstance: UserAgentApplication = new UserAgentApplication(msalConfig); - - function successCallback(response: AuthResponse): void { - if (response.tokenType === "id_token") { - thisObj.authenticate(); - - } else if (response.tokenType === "access_token") { - - accessToken = response.accessToken; - thisObj.setUsername(response.account.name); - - // Refresh User Permissions - thisObj.tryRefreshUserPermissions(); - thisObj.getembedUrl(); - - } else { - - thisObj.setState({ error: [("Token type is: " + response.tokenType)] }); - } + if (!isAuthenticated && msalInProgress === InteractionType.None) { + msalInstance.loginRedirect(loginRequest); } - - function failCallBack(error: AuthError): void { - thisObj.setState({ error: ["Redirect error: " + error] }); + else if (isAuthenticated && accessToken && !embedUrl) { + this.getembedUrl(); + msalInstance.removeEventCallback(eventCallback); } - - msalInstance.handleRedirectCallback(successCallback, failCallBack); - - // check if there is a cached user - if (msalInstance.getAccount()) { + else if (isAuthenticated && !accessToken && !embedUrl && msalInProgress === InteractionType.None) { + this.setUsername(msalAccounts[0].name); // get access token silently from cached id-token - msalInstance.acquireTokenSilent(loginRequest) - .then((response: AuthResponse) => { - - // get access token from response: response.accessToken - accessToken = response.accessToken; - this.setUsername(response.account.name); - this.getembedUrl(); - }) - .catch((err: AuthError) => { - - // refresh access token silently from cached id-token - // makes the call to handleredirectcallback - if (err.name === "InteractionRequiredAuthError") { - msalInstance.acquireTokenRedirect(loginRequest); - } - else { - thisObj.setState({ error: [err.toString()] }) - } - }); - } else { - - // user is not logged in or cached, you will need to log them in to acquire a token - msalInstance.loginRedirect(loginRequest); + msalInstance.acquireTokenSilent(loginRequest).then((response: AuthenticationResult) => { + accessToken = response.accessToken; + this.getembedUrl(); + }).catch((error: AuthError) => { + // Refresh access token silently from cached id-token + // Makes the call to handleredirectcallback + if (error.errorCode === "consent_required" || error.errorCode === "interaction_required" || error.errorCode === "login_required") { + msalInstance.acquireTokenRedirect(loginRequest); + } + else if (error.errorCode === '429') { + this.setState({ error: ["Our Service Token Server (STS) is overloaded, please try again in sometime"] }); + } + else { + this.setState({ error: ["There was some problem fetching the access token" + error.toString()] }); + } + }); } } @@ -198,7 +189,7 @@ class App extends React.Component { // Refreshes user permissions and makes sure the user permissions are fully updated // https://docs.microsoft.com/rest/api/power-bi/users/refreshuserpermissions tryRefreshUserPermissions(): void { - fetch("https://api.powerbi.com/v1.0/myorg/RefreshUserPermissions", { + fetch(config.powerBiApiUrl + "v1.0/myorg/RefreshUserPermissions", { headers: { "Authorization": "Bearer " + accessToken }, @@ -225,7 +216,7 @@ class App extends React.Component { getembedUrl(): void { const thisObj: this = this; - fetch("https://api.powerbi.com/v1.0/myorg/groups/" + config.workspaceId + "/reports/" + config.reportId, { + fetch(config.powerBiApiUrl + "v1.0/myorg/groups/" + config.workspaceId + "/reports/" + config.reportId, { headers: { "Authorization": "Bearer " + accessToken }, diff --git a/React-TS/Embed for your organization/UserOwnsData/src/Config.ts b/React-TS/Embed for your organization/UserOwnsData/src/Config.ts index e3f5a2b7..be8fff27 100644 --- a/React-TS/Embed for your organization/UserOwnsData/src/Config.ts +++ b/React-TS/Embed for your organization/UserOwnsData/src/Config.ts @@ -5,9 +5,17 @@ /* eslint-disable @typescript-eslint/no-inferrable-types */ -// Scope of AAD app. Use the below configuration to use all the permissions provided in the AAD app through Azure portal. +// Scope Base of AAD app. Use the below configuration to use all the permissions provided in the AAD app through Azure portal. // Refer https://aka.ms/PowerBIPermissions for complete list of Power BI scopes -export const scopes: string[] = ["https://analysis.windows.net/powerbi/api/Report.Read.All"]; + +// URL used for initiating authorization request +export const authorityUrl: string = "https://login.microsoftonline.com/common/"; + +// End point URL for Power BI API +export const powerBiApiUrl: string = "https://api.powerbi.com/"; + +// Scope for securing access token +export const scopeBase: string[] = ["https://analysis.windows.net/powerbi/api/Report.Read.All"]; // Client Id (Application Id) of the AAD app. export const clientId: string = ""; diff --git a/React-TS/Embed for your organization/UserOwnsData/src/index.tsx b/React-TS/Embed for your organization/UserOwnsData/src/index.tsx index 1664caa9..e056efdc 100644 --- a/React-TS/Embed for your organization/UserOwnsData/src/index.tsx +++ b/React-TS/Embed for your organization/UserOwnsData/src/index.tsx @@ -3,10 +3,37 @@ // Licensed under the MIT license. // ---------------------------------------------------------------------------- +import { PublicClientApplication } from "@azure/msal-browser"; +import { MsalProvider } from "@azure/msal-react"; import 'react-app-polyfill/ie11'; import 'react-app-polyfill/stable'; import React from 'react'; import ReactDOM from 'react-dom'; + import App from './App'; +import * as config from "./Config"; + +const msalInstanceProvider = new PublicClientApplication(generateMsalConfig()); + +ReactDOM.render( + + +, +document.getElementById('root')); + +// Refer https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-react#configure-your-javascript-spa +function generateMsalConfig() { + const msalConfig = { + auth: { + clientId: config.clientId, + authority: config.authorityUrl, + redirectUri: "http://localhost:3000", + }, + cache: { + cacheLocation: "sessionStorage", // This configures where your cache will be stored + storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge + } + }; -ReactDOM.render(, document.getElementById('root')); \ No newline at end of file + return msalConfig; +} \ No newline at end of file