From a07a4e28ef66cdc9d52d871fcea8a01a0f3fb4d4 Mon Sep 17 00:00:00 2001 From: Tomer Wintner Date: Wed, 2 Sep 2026 15:46:48 +0300 Subject: [PATCH 1/3] Secure EncryptCredentials datasource endpoints Require an assigned Entra app role for privileged datasource operations, add CSRF protection, and return fail-closed API authorization responses. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../AuthorizationTests.cs | 190 ++++++++++++++++++ .../EncryptCredentials.Tests.csproj | 18 ++ .../EncryptCredentials/EncryptCredentials.sln | 20 +- .../EncryptCredetialsController.cs | 3 + .../Controllers/HomeController.cs | 2 + .../EncryptCredentials.csproj | 8 +- .../Properties/launchSettings.json | 14 ++ .../EncryptCredentials/Startup.cs | 82 +++++++- .../Views/Home/Index.cshtml | 1 + .../EncryptCredentials/appsettings.json | 7 + .../EncryptCredentials/wwwroot/js/index.js | 6 + .NET Core/EncryptCredentials/README.md | 17 +- 12 files changed, 355 insertions(+), 13 deletions(-) create mode 100644 .NET Core/EncryptCredentials/EncryptCredentials.Tests/AuthorizationTests.cs create mode 100644 .NET Core/EncryptCredentials/EncryptCredentials.Tests/EncryptCredentials.Tests.csproj create mode 100644 .NET Core/EncryptCredentials/EncryptCredentials/Properties/launchSettings.json diff --git a/.NET Core/EncryptCredentials/EncryptCredentials.Tests/AuthorizationTests.cs b/.NET Core/EncryptCredentials/EncryptCredentials.Tests/AuthorizationTests.cs new file mode 100644 index 00000000..56d43112 --- /dev/null +++ b/.NET Core/EncryptCredentials/EncryptCredentials.Tests/AuthorizationTests.cs @@ -0,0 +1,190 @@ +// ---------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// ---------------------------------------------------------------------------- + +namespace EncryptCredentials.Tests +{ + using EncryptCredentials.Controllers; + using Microsoft.AspNetCore.Authentication; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Testing; + using Microsoft.AspNetCore.TestHost; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Security.Claims; + using System.Text.Encodings.Web; + using System.Threading.Tasks; + using Xunit; + + public class AuthorizationTests : IClassFixture + { + private readonly DatasourceWebApplicationFactory factory; + + public AuthorizationTests(DatasourceWebApplicationFactory factory) + { + this.factory = factory; + } + + public static IEnumerable PrivilegedEndpoints() + { + yield return new object[] { HttpMethod.Get, "/encryptcredential/getdatasourcesingroup" }; + yield return new object[] { HttpMethod.Post, "/encryptcredential/updatedatasource" }; + yield return new object[] { HttpMethod.Post, "/encryptcredential/adddatasource" }; + yield return new object[] { HttpMethod.Post, "/encryptcredential/encrypt" }; + } + + [Theory] + [MemberData(nameof(PrivilegedEndpoints))] + public async Task AnonymousRequestsAreRejected(HttpMethod method, string path) + { + using var request = new HttpRequestMessage(method, path); + using var response = await factory.CreateClient().SendAsync(request); + + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + } + + [Fact] + public async Task UserWithoutDatasourceAdministratorRoleIsForbidden() + { + using var request = new HttpRequestMessage(HttpMethod.Get, "/encryptcredential/getdatasourcesingroup"); + request.Headers.Authorization = new AuthenticationHeaderValue("Test", "user"); + + using var response = await factory.CreateClient().SendAsync(request); + + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + } + + [Fact] + public async Task AnonymousApiChallengeDoesNotContactIdentityProvider() + { + using var identityProviderUnavailableFactory = new UnavailableIdentityProviderFactory(); + using var client = identityProviderUnavailableFactory.CreateClient( + new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); + + using var response = await client.GetAsync("/encryptcredential/getdatasourcesingroup"); + + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + } + + [Fact] + public async Task AdministratorPostWithoutAntiforgeryTokenIsRejected() + { + using var request = new HttpRequestMessage(HttpMethod.Post, "/encryptcredential/adddatasource"); + request.Headers.Authorization = new AuthenticationHeaderValue("Test", Startup.DatasourceAdministratorRole); + request.Content = new FormUrlEncodedContent(new Dictionary()); + + using var response = await factory.CreateClient().SendAsync(request); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public void DatasourceControllerRequiresAdministratorPolicy() + { + var authorize = typeof(EncryptCredentialsController) + .GetCustomAttributes(typeof(AuthorizeAttribute), true) + .Cast() + .Single(); + + Assert.Equal(Startup.DatasourceAdministratorPolicy, authorize.Policy); + Assert.NotEmpty(typeof(EncryptCredentialsController) + .GetCustomAttributes(typeof(AutoValidateAntiforgeryTokenAttribute), true)); + } + } + + public class UnavailableIdentityProviderFactory : WebApplicationFactory + { + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.ConfigureAppConfiguration((context, configuration) => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["OperatorAzureAd:Instance"] = "https://127.0.0.1:1/", + ["OperatorAzureAd:TenantId"] = "00000000-0000-0000-0000-000000000000", + ["OperatorAzureAd:ClientId"] = "00000000-0000-0000-0000-000000000000", + ["OperatorAzureAd:ClientSecret"] = "test-secret", + ["OperatorAzureAd:CallbackPath"] = "/signin-oidc" + }); + }); + } + } + + public class DatasourceWebApplicationFactory : WebApplicationFactory + { + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.ConfigureAppConfiguration((context, configuration) => + { + configuration.AddInMemoryCollection(new Dictionary + { + ["OperatorAzureAd:Instance"] = "https://login.microsoftonline.com/", + ["OperatorAzureAd:TenantId"] = "00000000-0000-0000-0000-000000000000", + ["OperatorAzureAd:ClientId"] = "00000000-0000-0000-0000-000000000000", + ["OperatorAzureAd:ClientSecret"] = "test-secret", + ["OperatorAzureAd:CallbackPath"] = "/signin-oidc" + }); + }); + + builder.ConfigureTestServices(services => + { + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = TestAuthenticationHandler.SchemeName; + options.DefaultChallengeScheme = TestAuthenticationHandler.SchemeName; + options.DefaultForbidScheme = TestAuthenticationHandler.SchemeName; + options.DefaultScheme = TestAuthenticationHandler.SchemeName; + }).AddScheme( + TestAuthenticationHandler.SchemeName, + options => { }); + }); + } + } + + public class TestAuthenticationHandler : AuthenticationHandler + { + public const string SchemeName = "Test"; + + public TestAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder) + : base(options, logger, encoder) + { + } + + protected override Task HandleAuthenticateAsync() + { + if (!Request.Headers.TryGetValue("Authorization", out var authorization)) + { + return Task.FromResult(AuthenticateResult.NoResult()); + } + + var role = AuthenticationHeaderValue.Parse(authorization).Parameter; + var claims = new List + { + new Claim(ClaimTypes.NameIdentifier, "test-user"), + new Claim(ClaimTypes.Name, "Test User") + }; + + if (!string.IsNullOrWhiteSpace(role)) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, SchemeName)); + var ticket = new AuthenticationTicket(principal, SchemeName); + return Task.FromResult(AuthenticateResult.Success(ticket)); + } + } +} diff --git a/.NET Core/EncryptCredentials/EncryptCredentials.Tests/EncryptCredentials.Tests.csproj b/.NET Core/EncryptCredentials/EncryptCredentials.Tests/EncryptCredentials.Tests.csproj new file mode 100644 index 00000000..2f73a295 --- /dev/null +++ b/.NET Core/EncryptCredentials/EncryptCredentials.Tests/EncryptCredentials.Tests.csproj @@ -0,0 +1,18 @@ + + + net8.0 + false + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/.NET Core/EncryptCredentials/EncryptCredentials.sln b/.NET Core/EncryptCredentials/EncryptCredentials.sln index b74ab26a..83b80352 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials.sln +++ b/.NET Core/EncryptCredentials/EncryptCredentials.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 15.0.26124.0 MinimumVisualStudioVersion = 15.0.26124.0 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EncryptCredentials", "EncryptCredentials\EncryptCredentials.csproj", "{1A13B615-E49B-47EA-B23D-EE164230C682}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EncryptCredentials.Tests", "EncryptCredentials.Tests\EncryptCredentials.Tests.csproj", "{D87A2107-A5FB-4817-879D-77439CC12762}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -14,9 +16,6 @@ Global Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {1A13B615-E49B-47EA-B23D-EE164230C682}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1A13B615-E49B-47EA-B23D-EE164230C682}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -30,5 +29,20 @@ Global {1A13B615-E49B-47EA-B23D-EE164230C682}.Release|x64.Build.0 = Release|Any CPU {1A13B615-E49B-47EA-B23D-EE164230C682}.Release|x86.ActiveCfg = Release|Any CPU {1A13B615-E49B-47EA-B23D-EE164230C682}.Release|x86.Build.0 = Release|Any CPU + {D87A2107-A5FB-4817-879D-77439CC12762}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D87A2107-A5FB-4817-879D-77439CC12762}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D87A2107-A5FB-4817-879D-77439CC12762}.Debug|x64.ActiveCfg = Debug|Any CPU + {D87A2107-A5FB-4817-879D-77439CC12762}.Debug|x64.Build.0 = Debug|Any CPU + {D87A2107-A5FB-4817-879D-77439CC12762}.Debug|x86.ActiveCfg = Debug|Any CPU + {D87A2107-A5FB-4817-879D-77439CC12762}.Debug|x86.Build.0 = Debug|Any CPU + {D87A2107-A5FB-4817-879D-77439CC12762}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D87A2107-A5FB-4817-879D-77439CC12762}.Release|Any CPU.Build.0 = Release|Any CPU + {D87A2107-A5FB-4817-879D-77439CC12762}.Release|x64.ActiveCfg = Release|Any CPU + {D87A2107-A5FB-4817-879D-77439CC12762}.Release|x64.Build.0 = Release|Any CPU + {D87A2107-A5FB-4817-879D-77439CC12762}.Release|x86.ActiveCfg = Release|Any CPU + {D87A2107-A5FB-4817-879D-77439CC12762}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection EndGlobal diff --git a/.NET Core/EncryptCredentials/EncryptCredentials/Controllers/EncryptCredetialsController.cs b/.NET Core/EncryptCredentials/EncryptCredentials/Controllers/EncryptCredetialsController.cs index 9942959f..42b4b11c 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials/Controllers/EncryptCredetialsController.cs +++ b/.NET Core/EncryptCredentials/EncryptCredentials/Controllers/EncryptCredetialsController.cs @@ -7,12 +7,15 @@ namespace EncryptCredentials.Controllers { using EncryptCredentials.Models; using EncryptCredentials.Services; + using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Microsoft.PowerBI.Api.Models; using Microsoft.Rest; using System; + [Authorize(Policy = Startup.DatasourceAdministratorPolicy)] + [AutoValidateAntiforgeryToken] public class EncryptCredentialsController : Controller { private readonly PowerBIService powerBIService; diff --git a/.NET Core/EncryptCredentials/EncryptCredentials/Controllers/HomeController.cs b/.NET Core/EncryptCredentials/EncryptCredentials/Controllers/HomeController.cs index a818ea16..bb149909 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials/Controllers/HomeController.cs +++ b/.NET Core/EncryptCredentials/EncryptCredentials/Controllers/HomeController.cs @@ -7,10 +7,12 @@ namespace EncryptCredentials.Controllers { using EncryptCredentials.Models; using EncryptCredentials.Services; + using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using System; + [Authorize(Policy = Startup.DatasourceAdministratorPolicy)] public class HomeController : Controller { private readonly IOptions azureAd; diff --git a/.NET Core/EncryptCredentials/EncryptCredentials/EncryptCredentials.csproj b/.NET Core/EncryptCredentials/EncryptCredentials/EncryptCredentials.csproj index ff0f6611..77fc92e2 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials/EncryptCredentials.csproj +++ b/.NET Core/EncryptCredentials/EncryptCredentials/EncryptCredentials.csproj @@ -1,14 +1,14 @@ - netcoreapp3.1 + net8.0 Exe - - - + + + \ No newline at end of file diff --git a/.NET Core/EncryptCredentials/EncryptCredentials/Properties/launchSettings.json b/.NET Core/EncryptCredentials/EncryptCredentials/Properties/launchSettings.json new file mode 100644 index 00000000..10fb222f --- /dev/null +++ b/.NET Core/EncryptCredentials/EncryptCredentials/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "EncryptCredentials": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/.NET Core/EncryptCredentials/EncryptCredentials/Startup.cs b/.NET Core/EncryptCredentials/EncryptCredentials/Startup.cs index bf28a264..df41af06 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials/Startup.cs +++ b/.NET Core/EncryptCredentials/EncryptCredentials/Startup.cs @@ -7,14 +7,27 @@ namespace EncryptCredentials { using EncryptCredentials.Models; using EncryptCredentials.Services; + using Microsoft.AspNetCore.Authentication; + using Microsoft.AspNetCore.Authentication.Cookies; + using Microsoft.AspNetCore.Authentication.OpenIdConnect; + using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; + using Microsoft.Identity.Web; + using Microsoft.IdentityModel.Protocols.OpenIdConnect; + using System.Threading.Tasks; public class Startup { + public const string DatasourceAdministratorPolicy = "DatasourceAdministrator"; + public const string DatasourceAdministratorRole = "PowerBI.DatasourceAdmin"; + private const string OperatorChallengeScheme = "OperatorChallenge"; + public Startup(IConfiguration configuration) { Configuration = configuration; @@ -25,11 +38,77 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + .AddMicrosoftIdentityWebApp(Configuration.GetSection("OperatorAzureAd")); + + services.AddAuthentication() + .AddPolicyScheme(OperatorChallengeScheme, OperatorChallengeScheme, options => + { + options.ForwardDefaultSelector = context => + context.Request.Path.StartsWithSegments("/encryptcredential") + ? CookieAuthenticationDefaults.AuthenticationScheme + : OpenIdConnectDefaults.AuthenticationScheme; + }); + + services.AddAuthentication(options => + { + options.DefaultChallengeScheme = OperatorChallengeScheme; + }); + + services.Configure(CookieAuthenticationDefaults.AuthenticationScheme, options => + { + options.Cookie.HttpOnly = true; + options.Cookie.SameSite = SameSiteMode.Lax; + options.Cookie.SecurePolicy = CookieSecurePolicy.Always; + options.Events.OnRedirectToLogin = context => + { + if (context.Request.Path.StartsWithSegments("/encryptcredential")) + { + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + return Task.CompletedTask; + } + + context.Response.Redirect(context.RedirectUri); + return Task.CompletedTask; + }; + options.Events.OnRedirectToAccessDenied = context => + { + if (context.Request.Path.StartsWithSegments("/encryptcredential")) + { + context.Response.StatusCode = StatusCodes.Status403Forbidden; + return Task.CompletedTask; + } + + context.Response.Redirect(context.RedirectUri); + return Task.CompletedTask; + }; + }); + + var datasourceAdministratorPolicy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .RequireRole(DatasourceAdministratorRole) + .Build(); + + services.AddAuthorization(options => + { + options.AddPolicy(DatasourceAdministratorPolicy, datasourceAdministratorPolicy); + options.FallbackPolicy = datasourceAdministratorPolicy; + }); + + services.Configure(OpenIdConnectDefaults.AuthenticationScheme, options => + { + options.ResponseType = OpenIdConnectResponseType.Code; + options.UsePkce = true; + }); + // Register AadService and PbiEmbedService for dependency injection services.AddScoped(typeof(AadService)) .AddScoped(typeof(PowerBIService)); - services.AddControllersWithViews(); + services.AddControllersWithViews(options => + { + options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); + }); // Loading appsettings.json in C# Model classes services.Configure(Configuration.GetSection("AzureAd")); @@ -53,6 +132,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseRouting(); + app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => diff --git a/.NET Core/EncryptCredentials/EncryptCredentials/Views/Home/Index.cshtml b/.NET Core/EncryptCredentials/EncryptCredentials/Views/Home/Index.cshtml index c10ada0a..f2deb6f1 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials/Views/Home/Index.cshtml +++ b/.NET Core/EncryptCredentials/EncryptCredentials/Views/Home/Index.cshtml @@ -14,6 +14,7 @@ Licensed under the MIT license. --> + @Html.AntiForgeryToken()
Encrypt Power BI Data Source Credentials diff --git a/.NET Core/EncryptCredentials/EncryptCredentials/appsettings.json b/.NET Core/EncryptCredentials/EncryptCredentials/appsettings.json index f20bf934..21ebe158 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials/appsettings.json +++ b/.NET Core/EncryptCredentials/EncryptCredentials/appsettings.json @@ -11,6 +11,13 @@ "PbiPassword": "", "ClientSecret": "" }, + "OperatorAzureAd": { + "Instance": "https://login.microsoftonline.com/", + "TenantId": "", + "ClientId": "", + "ClientSecret": "", + "CallbackPath": "/signin-oidc" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/.NET Core/EncryptCredentials/EncryptCredentials/wwwroot/js/index.js b/.NET Core/EncryptCredentials/EncryptCredentials/wwwroot/js/index.js index d98d8efd..9a14d501 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials/wwwroot/js/index.js +++ b/.NET Core/EncryptCredentials/EncryptCredentials/wwwroot/js/index.js @@ -19,6 +19,12 @@ $(function () { // Freezing the contents for endpoint objects Object.freeze(Endpoints); + $.ajaxSetup({ + headers: { + "RequestVerificationToken": $("input[name='__RequestVerificationToken']").val() + } + }); + // Cache constants const ENABLED = "btn-primary"; const DISABLED = "btn-secondary"; diff --git a/.NET Core/EncryptCredentials/README.md b/.NET Core/EncryptCredentials/README.md index 845b6f17..70099a3f 100644 --- a/.NET Core/EncryptCredentials/README.md +++ b/.NET Core/EncryptCredentials/README.md @@ -2,17 +2,24 @@ ## Requirements -1. [.NET Core 3.1](https://aka.ms/netcore31) SDK or higher. +1. [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) SDK or higher. -2. IDE/code editor. We recommend using Visual Studio Code or Visual Studio 2019 (or a later version). -
-> **Note:** Visual Studio version >=16.5 is required to use .NET Core SDK 3.1. +2. IDE/code editor. We recommend using Visual Studio Code or Visual Studio 2022 (version 17.8 or later). -### Set up a Power BI app +### Set up the applications Follow the steps on [aka.ms/EmbedForCustomer](https://aka.ms/embedforcustomer) +Create a separate Microsoft Entra app registration for users who operate this sample: + +1. Add a web redirect URI for `https://localhost:5001/signin-oidc`. +2. Define an app role with the value `PowerBI.DatasourceAdmin` and allow users or groups as members. +3. Assign only the users or groups that are allowed to manage Power BI datasource credentials to that role. +4. Create a client secret and configure the tenant ID, client ID, and secret in the `OperatorAzureAd` section. Prefer environment variables, user secrets, or a secret store instead of writing the secret to `appsettings.json`. + +The operator app registration authenticates and authorizes incoming users. Keep it separate from the privileged Power BI identity configured in the `AzureAd` section. + ### Run the application on localhost 1. Open the [EncryptCredentials.sln](./EncryptCredentials.sln) file in Visual Studio. If you are using Visual Studio Code, open [EncryptCredentials](./EncryptCredentials) folder. From 81a390ce53c4b164cc5f8c530dcec158c4427dec Mon Sep 17 00:00:00 2001 From: Tomer Wintner Date: Thu, 3 Sep 2026 13:57:12 +0300 Subject: [PATCH 2/3] Test authorized datasource access Replace the controller metadata assertion with integration coverage proving authenticated datasource administrators can access every protected endpoint. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../AuthorizationTests.cs | 147 ++++++++++++++++-- .../Services/PowerBIService.cs | 10 +- 2 files changed, 138 insertions(+), 19 deletions(-) diff --git a/.NET Core/EncryptCredentials/EncryptCredentials.Tests/AuthorizationTests.cs b/.NET Core/EncryptCredentials/EncryptCredentials.Tests/AuthorizationTests.cs index 56d43112..d79cd8e5 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials.Tests/AuthorizationTests.cs +++ b/.NET Core/EncryptCredentials/EncryptCredentials.Tests/AuthorizationTests.cs @@ -5,24 +5,27 @@ namespace EncryptCredentials.Tests { - using EncryptCredentials.Controllers; + using EncryptCredentials.Models; + using EncryptCredentials.Services; using Microsoft.AspNetCore.Authentication; - using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; + using Microsoft.PowerBI.Api.Models; + using Microsoft.PowerBI.Api.Models.Credentials; + using System; using System.Collections.Generic; - using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Security.Claims; using System.Text.Encodings.Web; + using System.Text.RegularExpressions; using System.Threading.Tasks; using Xunit; @@ -88,17 +91,79 @@ public async Task AdministratorPostWithoutAntiforgeryTokenIsRejected() Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } - [Fact] - public void DatasourceControllerRequiresAdministratorPolicy() + [Theory] + [MemberData(nameof(PrivilegedEndpoints))] + public async Task AdministratorCanAccessPrivilegedEndpoints(HttpMethod method, string path) { - var authorize = typeof(EncryptCredentialsController) - .GetCustomAttributes(typeof(AuthorizeAttribute), true) - .Cast() - .Single(); + using var client = factory.CreateClient(); + using var homeRequest = CreateAdministratorRequest(HttpMethod.Get, "/"); + using var homeResponse = await client.SendAsync(homeRequest); + var home = await homeResponse.Content.ReadAsStringAsync(); + var antiforgeryToken = Regex.Match( + home, + "name=\"__RequestVerificationToken\" type=\"hidden\" value=\"([^\"]+)\"") + .Groups[1] + .Value; + + Assert.True( + homeResponse.IsSuccessStatusCode, + $"Expected the home page to succeed but received {homeResponse.StatusCode}: {home}"); + Assert.NotEmpty(antiforgeryToken); + + using var request = CreateAdministratorRequest(method, GetSuccessfulRequestPath(path)); + request.Headers.Add("RequestVerificationToken", antiforgeryToken); + if (method == HttpMethod.Post) + { + request.Content = CreateSuccessfulRequestContent(path); + } + + using var response = await client.SendAsync(request); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + private static HttpRequestMessage CreateAdministratorRequest(HttpMethod method, string path) + { + var request = new HttpRequestMessage(method, path); + request.Headers.Authorization = new AuthenticationHeaderValue( + TestAuthenticationHandler.SchemeName, + Startup.DatasourceAdministratorRole); + return request; + } + + private static string GetSuccessfulRequestPath(string path) + { + if (path.EndsWith("getdatasourcesingroup", StringComparison.Ordinal)) + { + return path + "?GroupId=00000000-0000-0000-0000-000000000001" + + "&DatasetId=00000000-0000-0000-0000-000000000002"; + } + + return path; + } + + private static FormUrlEncodedContent CreateSuccessfulRequestContent(string path) + { + var values = new Dictionary + { + ["GatewayId"] = "00000000-0000-0000-0000-000000000003", + ["CredentialType"] = Constants.KeyCredentials, + ["Credentials"] = "test-key", + ["PrivacyLevel"] = "None" + }; + + if (path.EndsWith("updatedatasource", StringComparison.Ordinal)) + { + values["DatasourceId"] = "00000000-0000-0000-0000-000000000004"; + } + else if (path.EndsWith("adddatasource", StringComparison.Ordinal)) + { + values["DatasourceType"] = "Sql"; + values["DatasourceName"] = "Test datasource"; + values["ConnectionDetails"] = "{\"server\":\"test\",\"database\":\"test\"}"; + } - Assert.Equal(Startup.DatasourceAdministratorPolicy, authorize.Policy); - Assert.NotEmpty(typeof(EncryptCredentialsController) - .GetCustomAttributes(typeof(AutoValidateAntiforgeryTokenAttribute), true)); + return new FormUrlEncodedContent(values); } } @@ -132,7 +197,14 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) ["OperatorAzureAd:TenantId"] = "00000000-0000-0000-0000-000000000000", ["OperatorAzureAd:ClientId"] = "00000000-0000-0000-0000-000000000000", ["OperatorAzureAd:ClientSecret"] = "test-secret", - ["OperatorAzureAd:CallbackPath"] = "/signin-oidc" + ["OperatorAzureAd:CallbackPath"] = "/signin-oidc", + ["AzureAd:AuthenticationMode"] = Constants.ServicePrincipal, + ["AzureAd:AuthorityUrl"] = "https://login.microsoftonline.com/organizations/", + ["AzureAd:ClientId"] = "00000000-0000-0000-0000-000000000000", + ["AzureAd:TenantId"] = "00000000-0000-0000-0000-000000000000", + ["AzureAd:PowerBiApiUrl"] = "https://api.powerbi.com/", + ["AzureAd:ScopeBase:0"] = "https://analysis.windows.net/powerbi/api/.default", + ["AzureAd:ClientSecret"] = "test-secret" }); }); @@ -147,10 +219,57 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) }).AddScheme( TestAuthenticationHandler.SchemeName, options => { }); + + services.RemoveAll(); + services.AddScoped(); }); } } + public class TestPowerBIService : PowerBIService + { + public TestPowerBIService() + : base(null) + { + } + + public override Datasources GetDatasourcesInGroup(Guid groupId, Guid datasetId) + { + return new Datasources(); + } + + public override Gateway GetGateway(Guid gatewayId) + { + return new Gateway(gatewayId) { Name = "Test gateway" }; + } + + public override CredentialDetails GetCredentialDetails( + Guid gatewayId, + string credentialType, + string[] credentialsArray, + string privacyLevel) + { + return new CredentialDetails( + new KeyCredentials("test-key"), + privacyLevel, + EncryptedConnection.NotEncrypted); + } + + public override void UpdateDatasource( + Guid gatewayId, + Guid datasourceId, + UpdateDatasourceRequest dataSourceRequest) + { + } + + public override GatewayDatasource AddDatasource( + Guid gatewayId, + PublishDatasourceToGatewayRequest publishDatasourceToGatewayRequest) + { + return new GatewayDatasource(); + } + } + public class TestAuthenticationHandler : AuthenticationHandler { public const string SchemeName = "Test"; diff --git a/.NET Core/EncryptCredentials/EncryptCredentials/Services/PowerBIService.cs b/.NET Core/EncryptCredentials/EncryptCredentials/Services/PowerBIService.cs index e0f6741b..d9b7b020 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials/Services/PowerBIService.cs +++ b/.NET Core/EncryptCredentials/EncryptCredentials/Services/PowerBIService.cs @@ -38,7 +38,7 @@ public PowerBIClient GetPowerBIClient() /// Power BI group Id /// Power BI dataset Id in corresponding Workspace /// Datasources present in corresponding Power BI Workspace - public Datasources GetDatasourcesInGroup(Guid groupId, Guid datasetId) + public virtual Datasources GetDatasourcesInGroup(Guid groupId, Guid datasetId) { PowerBIClient pbiClient = this.GetPowerBIClient(); @@ -52,7 +52,7 @@ public Datasources GetDatasourcesInGroup(Guid groupId, Guid datasetId) /// /// Gateway Id of corresponding Dataset /// Corresponding gateway - public Gateway GetGateway(Guid gatewayId) + public virtual Gateway GetGateway(Guid gatewayId) { PowerBIClient pbiClient = this.GetPowerBIClient(); @@ -102,7 +102,7 @@ public CredentialsBase GetCredentials(string credentialType, string[] credential /// Credentials entered by the user /// Privacy level selected by the user /// Credentials details updating the datasource - public CredentialDetails GetCredentialDetails(Guid gatewayId, string credentialType, string[] credentialsArray, string privacyLevel) + public virtual CredentialDetails GetCredentialDetails(Guid gatewayId, string credentialType, string[] credentialsArray, string privacyLevel) { // Capture credentials based on credential type selected by the user @@ -150,7 +150,7 @@ public CredentialDetails GetCredentialDetails(Guid gatewayId, string credentialT /// Gateway Id of corresponding dataset /// Datasource Id of corresponding gateway /// Request body for Update Datasource API - public void UpdateDatasource(Guid gatewayId, Guid datasourceId, UpdateDatasourceRequest dataSourceRequest) + public virtual void UpdateDatasource(Guid gatewayId, Guid datasourceId, UpdateDatasourceRequest dataSourceRequest) { PowerBIClient pbiClient = this.GetPowerBIClient(); @@ -164,7 +164,7 @@ public void UpdateDatasource(Guid gatewayId, Guid datasourceId, UpdateDatasource /// /// Gateway Id of corresponding Dataset /// Request body for Add Datasource API - public GatewayDatasource AddDatasource(Guid gatewayId, PublishDatasourceToGatewayRequest publishDatasourceToGatewayRequest) + public virtual GatewayDatasource AddDatasource(Guid gatewayId, PublishDatasourceToGatewayRequest publishDatasourceToGatewayRequest) { PowerBIClient pbiClient = this.GetPowerBIClient(); From 8fdeed07b52a9ec0652941a71503d5a4a3c64eb7 Mon Sep 17 00:00:00 2001 From: Tomer Wintner Date: Thu, 3 Sep 2026 16:12:30 +0300 Subject: [PATCH 3/3] Remove EncryptCredentials tests Remove the test project and restore the production service methods that were made virtual only for test substitution. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../AuthorizationTests.cs | 309 ------------------ .../EncryptCredentials.Tests.csproj | 18 - .../EncryptCredentials/EncryptCredentials.sln | 14 - .../Services/PowerBIService.cs | 10 +- 4 files changed, 5 insertions(+), 346 deletions(-) delete mode 100644 .NET Core/EncryptCredentials/EncryptCredentials.Tests/AuthorizationTests.cs delete mode 100644 .NET Core/EncryptCredentials/EncryptCredentials.Tests/EncryptCredentials.Tests.csproj diff --git a/.NET Core/EncryptCredentials/EncryptCredentials.Tests/AuthorizationTests.cs b/.NET Core/EncryptCredentials/EncryptCredentials.Tests/AuthorizationTests.cs deleted file mode 100644 index d79cd8e5..00000000 --- a/.NET Core/EncryptCredentials/EncryptCredentials.Tests/AuthorizationTests.cs +++ /dev/null @@ -1,309 +0,0 @@ -// ---------------------------------------------------------------------------- -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// ---------------------------------------------------------------------------- - -namespace EncryptCredentials.Tests -{ - using EncryptCredentials.Models; - using EncryptCredentials.Services; - using Microsoft.AspNetCore.Authentication; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Mvc.Testing; - using Microsoft.AspNetCore.TestHost; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.DependencyInjection.Extensions; - using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Options; - using Microsoft.PowerBI.Api.Models; - using Microsoft.PowerBI.Api.Models.Credentials; - using System; - using System.Collections.Generic; - using System.Net; - using System.Net.Http; - using System.Net.Http.Headers; - using System.Security.Claims; - using System.Text.Encodings.Web; - using System.Text.RegularExpressions; - using System.Threading.Tasks; - using Xunit; - - public class AuthorizationTests : IClassFixture - { - private readonly DatasourceWebApplicationFactory factory; - - public AuthorizationTests(DatasourceWebApplicationFactory factory) - { - this.factory = factory; - } - - public static IEnumerable PrivilegedEndpoints() - { - yield return new object[] { HttpMethod.Get, "/encryptcredential/getdatasourcesingroup" }; - yield return new object[] { HttpMethod.Post, "/encryptcredential/updatedatasource" }; - yield return new object[] { HttpMethod.Post, "/encryptcredential/adddatasource" }; - yield return new object[] { HttpMethod.Post, "/encryptcredential/encrypt" }; - } - - [Theory] - [MemberData(nameof(PrivilegedEndpoints))] - public async Task AnonymousRequestsAreRejected(HttpMethod method, string path) - { - using var request = new HttpRequestMessage(method, path); - using var response = await factory.CreateClient().SendAsync(request); - - Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); - } - - [Fact] - public async Task UserWithoutDatasourceAdministratorRoleIsForbidden() - { - using var request = new HttpRequestMessage(HttpMethod.Get, "/encryptcredential/getdatasourcesingroup"); - request.Headers.Authorization = new AuthenticationHeaderValue("Test", "user"); - - using var response = await factory.CreateClient().SendAsync(request); - - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - [Fact] - public async Task AnonymousApiChallengeDoesNotContactIdentityProvider() - { - using var identityProviderUnavailableFactory = new UnavailableIdentityProviderFactory(); - using var client = identityProviderUnavailableFactory.CreateClient( - new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); - - using var response = await client.GetAsync("/encryptcredential/getdatasourcesingroup"); - - Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); - } - - [Fact] - public async Task AdministratorPostWithoutAntiforgeryTokenIsRejected() - { - using var request = new HttpRequestMessage(HttpMethod.Post, "/encryptcredential/adddatasource"); - request.Headers.Authorization = new AuthenticationHeaderValue("Test", Startup.DatasourceAdministratorRole); - request.Content = new FormUrlEncodedContent(new Dictionary()); - - using var response = await factory.CreateClient().SendAsync(request); - - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - } - - [Theory] - [MemberData(nameof(PrivilegedEndpoints))] - public async Task AdministratorCanAccessPrivilegedEndpoints(HttpMethod method, string path) - { - using var client = factory.CreateClient(); - using var homeRequest = CreateAdministratorRequest(HttpMethod.Get, "/"); - using var homeResponse = await client.SendAsync(homeRequest); - var home = await homeResponse.Content.ReadAsStringAsync(); - var antiforgeryToken = Regex.Match( - home, - "name=\"__RequestVerificationToken\" type=\"hidden\" value=\"([^\"]+)\"") - .Groups[1] - .Value; - - Assert.True( - homeResponse.IsSuccessStatusCode, - $"Expected the home page to succeed but received {homeResponse.StatusCode}: {home}"); - Assert.NotEmpty(antiforgeryToken); - - using var request = CreateAdministratorRequest(method, GetSuccessfulRequestPath(path)); - request.Headers.Add("RequestVerificationToken", antiforgeryToken); - if (method == HttpMethod.Post) - { - request.Content = CreateSuccessfulRequestContent(path); - } - - using var response = await client.SendAsync(request); - - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - private static HttpRequestMessage CreateAdministratorRequest(HttpMethod method, string path) - { - var request = new HttpRequestMessage(method, path); - request.Headers.Authorization = new AuthenticationHeaderValue( - TestAuthenticationHandler.SchemeName, - Startup.DatasourceAdministratorRole); - return request; - } - - private static string GetSuccessfulRequestPath(string path) - { - if (path.EndsWith("getdatasourcesingroup", StringComparison.Ordinal)) - { - return path + "?GroupId=00000000-0000-0000-0000-000000000001" - + "&DatasetId=00000000-0000-0000-0000-000000000002"; - } - - return path; - } - - private static FormUrlEncodedContent CreateSuccessfulRequestContent(string path) - { - var values = new Dictionary - { - ["GatewayId"] = "00000000-0000-0000-0000-000000000003", - ["CredentialType"] = Constants.KeyCredentials, - ["Credentials"] = "test-key", - ["PrivacyLevel"] = "None" - }; - - if (path.EndsWith("updatedatasource", StringComparison.Ordinal)) - { - values["DatasourceId"] = "00000000-0000-0000-0000-000000000004"; - } - else if (path.EndsWith("adddatasource", StringComparison.Ordinal)) - { - values["DatasourceType"] = "Sql"; - values["DatasourceName"] = "Test datasource"; - values["ConnectionDetails"] = "{\"server\":\"test\",\"database\":\"test\"}"; - } - - return new FormUrlEncodedContent(values); - } - } - - public class UnavailableIdentityProviderFactory : WebApplicationFactory - { - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - builder.ConfigureAppConfiguration((context, configuration) => - { - configuration.AddInMemoryCollection(new Dictionary - { - ["OperatorAzureAd:Instance"] = "https://127.0.0.1:1/", - ["OperatorAzureAd:TenantId"] = "00000000-0000-0000-0000-000000000000", - ["OperatorAzureAd:ClientId"] = "00000000-0000-0000-0000-000000000000", - ["OperatorAzureAd:ClientSecret"] = "test-secret", - ["OperatorAzureAd:CallbackPath"] = "/signin-oidc" - }); - }); - } - } - - public class DatasourceWebApplicationFactory : WebApplicationFactory - { - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - builder.ConfigureAppConfiguration((context, configuration) => - { - configuration.AddInMemoryCollection(new Dictionary - { - ["OperatorAzureAd:Instance"] = "https://login.microsoftonline.com/", - ["OperatorAzureAd:TenantId"] = "00000000-0000-0000-0000-000000000000", - ["OperatorAzureAd:ClientId"] = "00000000-0000-0000-0000-000000000000", - ["OperatorAzureAd:ClientSecret"] = "test-secret", - ["OperatorAzureAd:CallbackPath"] = "/signin-oidc", - ["AzureAd:AuthenticationMode"] = Constants.ServicePrincipal, - ["AzureAd:AuthorityUrl"] = "https://login.microsoftonline.com/organizations/", - ["AzureAd:ClientId"] = "00000000-0000-0000-0000-000000000000", - ["AzureAd:TenantId"] = "00000000-0000-0000-0000-000000000000", - ["AzureAd:PowerBiApiUrl"] = "https://api.powerbi.com/", - ["AzureAd:ScopeBase:0"] = "https://analysis.windows.net/powerbi/api/.default", - ["AzureAd:ClientSecret"] = "test-secret" - }); - }); - - builder.ConfigureTestServices(services => - { - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = TestAuthenticationHandler.SchemeName; - options.DefaultChallengeScheme = TestAuthenticationHandler.SchemeName; - options.DefaultForbidScheme = TestAuthenticationHandler.SchemeName; - options.DefaultScheme = TestAuthenticationHandler.SchemeName; - }).AddScheme( - TestAuthenticationHandler.SchemeName, - options => { }); - - services.RemoveAll(); - services.AddScoped(); - }); - } - } - - public class TestPowerBIService : PowerBIService - { - public TestPowerBIService() - : base(null) - { - } - - public override Datasources GetDatasourcesInGroup(Guid groupId, Guid datasetId) - { - return new Datasources(); - } - - public override Gateway GetGateway(Guid gatewayId) - { - return new Gateway(gatewayId) { Name = "Test gateway" }; - } - - public override CredentialDetails GetCredentialDetails( - Guid gatewayId, - string credentialType, - string[] credentialsArray, - string privacyLevel) - { - return new CredentialDetails( - new KeyCredentials("test-key"), - privacyLevel, - EncryptedConnection.NotEncrypted); - } - - public override void UpdateDatasource( - Guid gatewayId, - Guid datasourceId, - UpdateDatasourceRequest dataSourceRequest) - { - } - - public override GatewayDatasource AddDatasource( - Guid gatewayId, - PublishDatasourceToGatewayRequest publishDatasourceToGatewayRequest) - { - return new GatewayDatasource(); - } - } - - public class TestAuthenticationHandler : AuthenticationHandler - { - public const string SchemeName = "Test"; - - public TestAuthenticationHandler( - IOptionsMonitor options, - ILoggerFactory logger, - UrlEncoder encoder) - : base(options, logger, encoder) - { - } - - protected override Task HandleAuthenticateAsync() - { - if (!Request.Headers.TryGetValue("Authorization", out var authorization)) - { - return Task.FromResult(AuthenticateResult.NoResult()); - } - - var role = AuthenticationHeaderValue.Parse(authorization).Parameter; - var claims = new List - { - new Claim(ClaimTypes.NameIdentifier, "test-user"), - new Claim(ClaimTypes.Name, "Test User") - }; - - if (!string.IsNullOrWhiteSpace(role)) - { - claims.Add(new Claim(ClaimTypes.Role, role)); - } - - var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, SchemeName)); - var ticket = new AuthenticationTicket(principal, SchemeName); - return Task.FromResult(AuthenticateResult.Success(ticket)); - } - } -} diff --git a/.NET Core/EncryptCredentials/EncryptCredentials.Tests/EncryptCredentials.Tests.csproj b/.NET Core/EncryptCredentials/EncryptCredentials.Tests/EncryptCredentials.Tests.csproj deleted file mode 100644 index 2f73a295..00000000 --- a/.NET Core/EncryptCredentials/EncryptCredentials.Tests/EncryptCredentials.Tests.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - net8.0 - false - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - diff --git a/.NET Core/EncryptCredentials/EncryptCredentials.sln b/.NET Core/EncryptCredentials/EncryptCredentials.sln index 83b80352..718e7d3b 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials.sln +++ b/.NET Core/EncryptCredentials/EncryptCredentials.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 15.0.26124.0 MinimumVisualStudioVersion = 15.0.26124.0 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EncryptCredentials", "EncryptCredentials\EncryptCredentials.csproj", "{1A13B615-E49B-47EA-B23D-EE164230C682}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EncryptCredentials.Tests", "EncryptCredentials.Tests\EncryptCredentials.Tests.csproj", "{D87A2107-A5FB-4817-879D-77439CC12762}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -29,18 +27,6 @@ Global {1A13B615-E49B-47EA-B23D-EE164230C682}.Release|x64.Build.0 = Release|Any CPU {1A13B615-E49B-47EA-B23D-EE164230C682}.Release|x86.ActiveCfg = Release|Any CPU {1A13B615-E49B-47EA-B23D-EE164230C682}.Release|x86.Build.0 = Release|Any CPU - {D87A2107-A5FB-4817-879D-77439CC12762}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D87A2107-A5FB-4817-879D-77439CC12762}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D87A2107-A5FB-4817-879D-77439CC12762}.Debug|x64.ActiveCfg = Debug|Any CPU - {D87A2107-A5FB-4817-879D-77439CC12762}.Debug|x64.Build.0 = Debug|Any CPU - {D87A2107-A5FB-4817-879D-77439CC12762}.Debug|x86.ActiveCfg = Debug|Any CPU - {D87A2107-A5FB-4817-879D-77439CC12762}.Debug|x86.Build.0 = Debug|Any CPU - {D87A2107-A5FB-4817-879D-77439CC12762}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D87A2107-A5FB-4817-879D-77439CC12762}.Release|Any CPU.Build.0 = Release|Any CPU - {D87A2107-A5FB-4817-879D-77439CC12762}.Release|x64.ActiveCfg = Release|Any CPU - {D87A2107-A5FB-4817-879D-77439CC12762}.Release|x64.Build.0 = Release|Any CPU - {D87A2107-A5FB-4817-879D-77439CC12762}.Release|x86.ActiveCfg = Release|Any CPU - {D87A2107-A5FB-4817-879D-77439CC12762}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/.NET Core/EncryptCredentials/EncryptCredentials/Services/PowerBIService.cs b/.NET Core/EncryptCredentials/EncryptCredentials/Services/PowerBIService.cs index d9b7b020..e0f6741b 100644 --- a/.NET Core/EncryptCredentials/EncryptCredentials/Services/PowerBIService.cs +++ b/.NET Core/EncryptCredentials/EncryptCredentials/Services/PowerBIService.cs @@ -38,7 +38,7 @@ public PowerBIClient GetPowerBIClient() /// Power BI group Id /// Power BI dataset Id in corresponding Workspace /// Datasources present in corresponding Power BI Workspace - public virtual Datasources GetDatasourcesInGroup(Guid groupId, Guid datasetId) + public Datasources GetDatasourcesInGroup(Guid groupId, Guid datasetId) { PowerBIClient pbiClient = this.GetPowerBIClient(); @@ -52,7 +52,7 @@ public virtual Datasources GetDatasourcesInGroup(Guid groupId, Guid datasetId) /// /// Gateway Id of corresponding Dataset /// Corresponding gateway - public virtual Gateway GetGateway(Guid gatewayId) + public Gateway GetGateway(Guid gatewayId) { PowerBIClient pbiClient = this.GetPowerBIClient(); @@ -102,7 +102,7 @@ public CredentialsBase GetCredentials(string credentialType, string[] credential /// Credentials entered by the user /// Privacy level selected by the user /// Credentials details updating the datasource - public virtual CredentialDetails GetCredentialDetails(Guid gatewayId, string credentialType, string[] credentialsArray, string privacyLevel) + public CredentialDetails GetCredentialDetails(Guid gatewayId, string credentialType, string[] credentialsArray, string privacyLevel) { // Capture credentials based on credential type selected by the user @@ -150,7 +150,7 @@ public virtual CredentialDetails GetCredentialDetails(Guid gatewayId, string cre /// Gateway Id of corresponding dataset /// Datasource Id of corresponding gateway /// Request body for Update Datasource API - public virtual void UpdateDatasource(Guid gatewayId, Guid datasourceId, UpdateDatasourceRequest dataSourceRequest) + public void UpdateDatasource(Guid gatewayId, Guid datasourceId, UpdateDatasourceRequest dataSourceRequest) { PowerBIClient pbiClient = this.GetPowerBIClient(); @@ -164,7 +164,7 @@ public virtual void UpdateDatasource(Guid gatewayId, Guid datasourceId, UpdateDa /// /// Gateway Id of corresponding Dataset /// Request body for Add Datasource API - public virtual GatewayDatasource AddDatasource(Guid gatewayId, PublishDatasourceToGatewayRequest publishDatasourceToGatewayRequest) + public GatewayDatasource AddDatasource(Guid gatewayId, PublishDatasourceToGatewayRequest publishDatasourceToGatewayRequest) { PowerBIClient pbiClient = this.GetPowerBIClient();