Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Bravo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<Copyright>SQLBI Corporation</Copyright>
<RootNamespace>Sqlbi.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
<Nullable>enable</Nullable>
<LangVersion>14.0</LangVersion>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
Expand Down
67 changes: 35 additions & 32 deletions src/Controllers/AuthenticationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,9 @@
{
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Sqlbi.Bravo.Infrastructure;
using Sqlbi.Bravo.Infrastructure.Configuration;
using Sqlbi.Bravo.Infrastructure.Models.PBICloud;
using Sqlbi.Bravo.Infrastructure.Services.PowerBI;
using Sqlbi.Bravo.Models;
using Sqlbi.Bravo.Models.Authentication;
using Sqlbi.Bravo.Services;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mime;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// Authentication controller
Expand All @@ -21,56 +13,67 @@
[Route("auth/[action]")]
[ApiController]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))]
public class AuthenticationController : ControllerBase
public sealed class AuthenticationController(
IPBICloudAuthenticationService pbicloudAuthenticationService,
IPBICloudService pbicloudService,
IAuthenticationService authenticationService) : ControllerBase
{
private readonly IAuthenticationService _authenticationService;
private readonly IPBICloudService _pbicloudService;

public AuthenticationController(IAuthenticationService authenticationService, IPBICloudService pbicloudService)
{
_authenticationService = authenticationService;
_pbicloudService = pbicloudService;
}
private readonly IAuthenticationService _authenticationService = authenticationService;
private readonly IPBICloudAuthenticationService _pbicloudAuthenticationService = pbicloudAuthenticationService;
private readonly IPBICloudService _pbicloudService = pbicloudService;

/// <summary>
/// TODO
/// Returns the list of available PowerBI cloud environments for the specified email account.
/// </summary>
/// <response code="200">Status200OK - Success</response>
[HttpGet]
[ActionName("powerbi/GetEnvironments")]
[ActionName("GetEnvironments")]
[Produces(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<IPBICloudEnvironment>))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GetEnvironmentsResponse))]
[ProducesDefaultResponseType]
public async Task<IActionResult> GetPBICloudEnvironmentsAsync(string userPrincipalName, CancellationToken cancellationToken)
public async Task<IActionResult> GetEnvironmentsAsync(
[FromQuery] GetEnvironmentsRequest request,
CancellationToken cancellationToken)
{
var environments = await _authenticationService.GetPBICloudEnvironmentsAsync(userPrincipalName, cancellationToken);
return Ok(environments);
var environments = await _pbicloudAuthenticationService.GetEnvironmentsAsync(
request.Email,
cancellationToken);

var response = new GetEnvironmentsResponse(environments);
return Ok(response);
}

/// <summary>
/// Attempts to authenticate and acquire an access token for the account to access the PowerBI cloud services
/// </summary>
/// <response code="200">Status200OK - Success</response>
[HttpPost]
[ActionName("powerbi/SignIn")]
[ActionName("SignIn")]
[Produces(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IBravoAccount))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(SignInResponse))]
[ProducesDefaultResponseType]
public async Task<IActionResult> PBICloudSignInAsync(PBICloudAuthenticationRequest request, CancellationToken cancellationToken)
public async Task<IActionResult> SignInAsync(
SignInRequest request,
CancellationToken cancellationToken)
{
await _authenticationService.PBICloudSignInAsync(request.UserPrincipalName!, request.Environment!, cancellationToken);
return Ok(_authenticationService.PBICloudAuthentication.Account);
await _authenticationService.PBICloudSignInAsync(
request.Email,
request.Environment.ToModel(),
cancellationToken);

var response = new SignInResponse(_authenticationService.PBICloudAuthentication.Account);
return Ok(response);
}

/// <summary>
/// Clear the token cache for all the accounts
/// </summary>
/// <response code="200">Status200OK - Success</response>
[HttpGet]
[ActionName("powerbi/SignOut")]
[ActionName("SignOut")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<IActionResult> PBICloudSignOutAsync(CancellationToken cancellationToken)
public async Task<IActionResult> SignOutAsync(CancellationToken cancellationToken)
{
await _authenticationService.PBICloudSignOutAsync(cancellationToken);
return Ok();
Expand All @@ -89,7 +92,7 @@ public async Task<IActionResult> PBICloudSignOutAsync(CancellationToken cancella
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesDefaultResponseType]
public async Task<IActionResult> GetPBICloudAccountAvatarAsync(CancellationToken cancellationToken)
public async Task<IActionResult> GetUserAvatarAsync(CancellationToken cancellationToken)
{
if (await _authenticationService.IsPBICloudSignInRequiredAsync(cancellationToken))
return Unauthorized();
Expand Down
1 change: 0 additions & 1 deletion src/Infrastructure/AppEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ internal static class AppEnvironment
// NBSP char instead of whitespace - Latvian/lv
"\u00A0\u2014 Power BI Desktop",
};
public static readonly TimeSpan MSALSignInTimeout = TimeSpan.FromMinutes(5);
public static readonly Color ThemeColorDark = ColorTranslator.FromHtml("#202020");
public static readonly Color ThemeColorLight = ColorTranslator.FromHtml("#F3F3F3");
public static readonly DaxLineBreakStyle FormatDaxLineBreakDefault = DaxLineBreakStyle.InitialLineBreak;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud
{
using System.Text.Json.Serialization;

[DebuggerDisplay("{Name}")]
internal sealed class CloudEnvironmentClientContract
{
[JsonPropertyName("name")]
public string Name { get; set; } = null!;

[JsonPropertyName("appId")]
public string AppId { get; set; } = null!;

[JsonPropertyName("redirectUri")]
public string RedirectUri { get; set; } = null!;
}

internal static class CloudEnvironmentClientContractExtension
{
public static bool IsPowerBIDesktop(this CloudEnvironmentClientContract client)
=> client.Name.Equals("powerbi-desktop", StringComparison.Ordinal);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud
{
using System.Text.Json.Serialization;

[DebuggerDisplay("{CloudName}")]
internal sealed class CloudEnvironmentContract
{
[JsonPropertyName("cloudName")]
public string CloudName { get; set; } = null!;

[JsonPropertyName("clients")]
public CloudEnvironmentClientContract[] Clients { get; set; } = null!;

[JsonPropertyName("services")]
public CloudEnvironmentServiceContract[] Services { get; set; } = null!;
}

internal static class CloudEnvironmentContractExtension
{
public static string GetDescription(this CloudEnvironmentContract environment) => environment.CloudName switch
{
"GlobalCloud" => "Power BI",
"ChinaCloud" => "Power BI operated by 21Vianet in China",
"USGovCloud" => "Power BI for US Government", // gcc
"USGovDoDL4Cloud" => "Power BI for US Government (L4)", // gcc_high
"USGovDoDL5Cloud" => "Power BI for US Government (L5)", // gcc_dod
_ => environment.CloudName,
};

public static bool IsMicrosoftInternalCloud(this CloudEnvironmentContract environment)
=> s_microsoftInternalClouds.Contains(environment.CloudName);

private readonly static HashSet<string> s_microsoftInternalClouds = new(StringComparer.OrdinalIgnoreCase)
{
"OneBox",
"DAILY",
"Int3",
"PpeCloud", // edog
"DXT"
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud
{
using System.Text.Json.Serialization;

// Sample response from the discover API:
// Invoke-RestMethod -Method POST -Uri "https://api.powerbi.com/powerbi/globalservice/v202003/environments/discover?client=powerbi-msolap" | ConvertTo-Json -Depth 10

internal sealed class CloudEnvironmentResponseContract
{
[JsonPropertyName("environments")]
public CloudEnvironmentContract[] Environments { get; set; } = null!;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace Sqlbi.Bravo.Infrastructure.Contracts.PBICloud
{
using System.Text.Json.Serialization;

[DebuggerDisplay("{Name}")]
internal sealed class CloudEnvironmentServiceContract
{
[JsonPropertyName("name")]
public string Name { get; set; } = null!;

[JsonPropertyName("endpoint")]
public string Endpoint { get; set; } = null!;

[JsonPropertyName("resourceId")]
public string ResourceId { get; set; } = null!;

//[JsonPropertyName("allowedDomains")]
//public string[] AllowedDomains { get; set; } = null!;
}

internal static class CloudEnvironmentServiceContractExtension
{
public static bool IsAad(this CloudEnvironmentServiceContract service)
=> service.Name.Equals("aad", StringComparison.Ordinal);

public static bool IsPowerBIBackend(this CloudEnvironmentServiceContract service)
=> service.Name.Equals("powerbi-backend", StringComparison.Ordinal);
}
}
11 changes: 0 additions & 11 deletions src/Infrastructure/Contracts/PBICloud/GlobalService.cs

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
{
using System.Text.Json.Serialization;

public class TenantCluster
internal sealed class TenantClusterContract
{
[JsonPropertyName("FixedClusterUri")]
public string? FixedClusterUri { get; set; }
public string FixedClusterUri { get; set; } = null!;

//public string? PrivateLinkFixedClusterUri { get; set; }

Expand All @@ -17,4 +17,4 @@ public class TenantCluster

//public string? TenantId { get; set; }
}
}
}
17 changes: 17 additions & 0 deletions src/Infrastructure/Contracts/PBIConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Sqlbi.Bravo.Infrastructure.Contracts
{
internal static class PBIConstants
{
public static class Endpoints
{
public static readonly Uri GlobalCloudPowerBIUri = new("https://api.powerbi.com", UriKind.Absolute);
}

public static class Registry
{
public const string PowerBIDiscoveryUrlValueName = "PowerBIDiscoveryUrl";
public const string PowerBISubkeyName = @"SOFTWARE\Microsoft\Microsoft Power BI\";
public const string PowerBIPolicySubkeyName = @"SOFTWARE\Policies\Microsoft\Microsoft Power BI\";
}
}
}
28 changes: 28 additions & 0 deletions src/Infrastructure/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Microsoft.Extensions.DependencyInjection;
using Sqlbi.Bravo.Infrastructure.Services.PowerBI;

namespace Sqlbi.Bravo.Infrastructure.Extensions
{
internal static class ServiceCollectionExtensions
{
//public static void AddSingletonIfNotRegistered<TService, TImplementation>(this IServiceCollection services)
// where TService : class
// where TImplementation : class, TService
//{
// if (!services.Any(sd => sd.ServiceType == typeof(TService)))
// {
// services.AddSingleton<TService, TImplementation>();
// }
//}

public static IServiceCollection AddPBICloudServices(this IServiceCollection services)
{
services.AddSingleton<IPBILocalConfigurationReader, PBILocalConfigurationReader>();
services.AddSingleton<IPBICloudConfigurationService, PBICloudConfigurationService>();
services.AddSingleton<IPBICloudAuthenticationService, PBICloudAuthenticationService>();
services.AddSingleton<IPBICloudService, PBICloudService>();

return services;
}
}
}
Loading
Loading