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
21 changes: 15 additions & 6 deletions src/Controllers/AuthenticationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,31 @@ public async Task<IActionResult> GetEnvironmentsAsync(
/// Attempts to authenticate and acquire an access token for the account to access the PowerBI cloud services
/// </summary>
/// <response code="200">Status200OK - Success</response>
/// <response code="204">Status204NoContent - Sign-in was canceled by the user</response>
[HttpPost]
[ActionName("SignIn")]
[Produces(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(SignInResponse))]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesDefaultResponseType]
public async Task<IActionResult> SignInAsync(
SignInRequest request,
CancellationToken cancellationToken)
{
var session = await _authenticationService.SignInAsync(
request.Email,
request.Environment.ToModel(),
cancellationToken);
try
{
var session = await _authenticationService.SignInAsync(
request.Email,
request.Environment.ToModel(),
cancellationToken);

var response = new SignInResponse(session.AuthenticationResult);
return Ok(response);
var response = new SignInResponse(session.AuthenticationResult);
return Ok(response);
}
catch (OperationCanceledException)
{
return NoContent();
}
}

/// <summary>
Expand Down
12 changes: 3 additions & 9 deletions src/Infrastructure/AppExceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ public enum BravoProblem
None = 0,

/// <summary>
/// A <see cref="OperationCanceledException"/> or <see cref="TaskCanceledException"/> was thrown, an HTTP request was aborted or the user cancelled a long-running operation.
/// This BravoProblem is a placehoder since the response message is never sent back to the user/UI due to the aborted request.
/// An <see cref="OperationCanceledException"/> was thrown (request aborted or user-cancelled operation).
/// Response message is not sent to the user/UI due to the aborted request.
/// </summary>
[JsonPropertyName("OperationCancelled")]
OperationCancelled = 1,
Expand Down Expand Up @@ -172,15 +172,9 @@ public enum BravoProblem
[JsonPropertyName("SignInMsalExceptionOccurred")]
SignInMsalExceptionOccurred = 400,

/// <summary>
/// Sign-in request was canceled because the configured timeout period elapsed prior to completion of the operation
/// </summary>
[JsonPropertyName("SignInMsalTimeoutExpired")]
SignInMsalTimeoutExpired = 401,

/// <summary>
/// An error occurred while importing the VPAX file
/// </summary>
/// </summary>
[JsonPropertyName("VpaxFileImportError")]
VpaxFileImportError = 500,

Expand Down
14 changes: 14 additions & 0 deletions src/Infrastructure/Extensions/MsalExceptionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Sqlbi.Bravo.Infrastructure.Extensions
{
using Microsoft.Identity.Client;

internal static class MsalExceptionExtensions
{
/// <summary>
/// Returns <see langword="true"/> if the exception represents the user canceling
/// an interactive authentication prompt (e.g. closing the sign-in window).
/// </summary>
public static bool IsAuthenticationCanceled(this MsalException exception)
=> exception.ErrorCode == MsalError.AuthenticationCanceledError;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace Sqlbi.Bravo.Infrastructure.PowerBI.Cloud.Authentication
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Desktop;
using Sqlbi.Bravo.Infrastructure.Configuration;
using Sqlbi.Bravo.Infrastructure.Extensions;
using Sqlbi.Bravo.Infrastructure.Helpers;
using Sqlbi.Bravo.Infrastructure.PowerBI.Cloud;
using Msal = Microsoft.Identity.Client;
Expand Down Expand Up @@ -89,13 +90,30 @@ public async Task ClearTokenCacheAsync(CloudEnvironment environment)
.WithPrompt(prompt)
.WithClaims(claims);

using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

if (useEmbeddedBrowser)
{
var windowHandle = ProcessHelper.GetCurrentProcessMainWindowHandle();
builder.WithParentActivityOrWindow(windowHandle);
}
else // use system browser
{
// The system browser is a separate, untracked OS process: there is no way to detect the user closing
// it, so a hard ceiling is the only safeguard against an indefinitely pending sign-in.
cancellationTokenSource.CancelAfter(TimeSpan.FromMinutes(2));
}

return await builder.ExecuteAsync(cancellationToken).ConfigureAwait(false);
try
{
return await builder.ExecuteAsync(cancellationTokenSource.Token).ConfigureAwait(false);
}
catch (MsalException ex) when (ex.IsAuthenticationCanceled())
{
// The user canceled the sign-in prompt, either by closing the embedded browser or the system browser.
// Normalize the exception to OperationCanceledException, like the other cancellation paths
throw new OperationCanceledException("Authentication was canceled by the user.", ex);
}
}

private static IPublicClientApplication CreatePublicClient(CloudEnvironment environment)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,8 @@ public async Task<AuthenticatedSession> SignInAsync(string email, CloudEnvironme
if (AppEnvironment.IsDiagnosticLevelVerbose)
AppEnvironment.AddDiagnostics(DiagnosticMessageType.Json, name: $"{nameof(CloudAuthenticationService)}.{nameof(SignInAsync)}", JsonSerializer.Serialize(environment));

using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cancellationTokenSource.CancelAfter(TimeSpan.FromMinutes(2));

var authenticationResult = await _cloudAuthenticationClient.AcquireTokenAsync(environment, email, cancellationTokenSource.Token);
var clusterUri = await _cloudConfigurationService.ResolveTenantClusterUriAsync(environment, authenticationResult.AccessToken, cancellationTokenSource.Token);
var authenticationResult = await _cloudAuthenticationClient.AcquireTokenAsync(environment, email, cancellationToken);
var clusterUri = await _cloudConfigurationService.ResolveTenantClusterUriAsync(environment, authenticationResult.AccessToken, cancellationToken);

var newEnvironment = environment with { ClusterUri = clusterUri };
var newSession = new AuthenticatedSession(authenticationResult, newEnvironment);
Expand Down
2 changes: 1 addition & 1 deletion src/Scripts/controllers/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ export class Host extends Dispatchable {
const logSettings: ApiLogSettings = {};

return (<Promise<HostSignInResponse>>this.apiCall("auth/SignIn", request || {}, { method: "POST" }, false, logSettings))
.then(response => response.account);
.then(response => response?.account);
}

/*signIn(email?: string) {
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/exceptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export enum AppProblem {
ConnectionUnsupported = 200,
UserSettingsSaveError = 300,
SignInMsalExceptionOccurred = 400,
SignInMsalTimeoutExpired = 401,
VpaxFileImportError = 500,
VpaxFileExportError = 501,
NetworkError = 600,
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/i18n/cz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ const locale: Locale = {
[_.errorReportsEmptyListing]: "Nejsou k dispozici žádné neotevřené chyby.",
[_.errorRetry]: "Opakovat",
[_.errorSignInMsalExceptionOccurred]: "Neočekávaná chyba v rámci přihlášení.",
[_.errorSignInMsalTimeoutExpired]: "Požadavek na přihlášení byl zrušen, protože časový limit vypršel před dokončením operace.",
[_.errorTemplateAlreadyExists]: "Další šablona se stejnou cestou již existuje: <br><b>{name}</b>",
[_.errorTimeout]: "Časový limit požadavku vypršel.",
[_.errorTitle]: "Whoops...",
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/i18n/da.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ const locale: Locale = {
[_.errorReportsEmptyListing]: "Ingen uåbnede rapporter tilgængelige.",
[_.errorRetry]: "Forsøg igen",
[_.errorSignInMsalExceptionOccurred]: "Ukendt fejl opstod i forbindelse med lo- ind.",
[_.errorSignInMsalTimeoutExpired]: "Log-ind blev afbrudt fordi timeout perioden udløb inden processen blev gennemført.",
[_.errorTemplateAlreadyExists]: "En anden skabelon med den samme sti findes allerede: <br><b>{name}</b>",
[_.errorTimeout]: "Forespørgselstimeout.",
[_.errorTitle]: "Ups...",
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ const locale: Locale = {
[_.errorReportsEmptyListing]: "Keine ungeöffneten Berichte verfügbar.",
[_.errorRetry]: "Wiederholen",
[_.errorSignInMsalExceptionOccurred]: "Unerwarteter Fehler in der Anmeldeanforderung.",
[_.errorSignInMsalTimeoutExpired]: "Die Anmeldeanforderung wurde abgebrochen, weil die Zeitüberschreitung abgelaufen ist, bevor der Vorgang abgeschlossen werden konnte.",
[_.errorTemplateAlreadyExists]: "Eine weitere Vorlage mit dem gleichen Weg gibt es bereits: <br><b>{name}</b>",
[_.errorTimeout]: "Zeitüberschreitung der Anfrage.",
[_.errorTitle]: "Ups...",
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ const locale: Locale = {
[_.errorReportsEmptyListing]: "No unopened reports available.",
[_.errorRetry]: "Retry",
[_.errorSignInMsalExceptionOccurred]: "Unexpected error in the sign-in request.",
[_.errorSignInMsalTimeoutExpired]: "The sign-in request was canceled because the timeout period expired before the operation was completed.",
[_.errorTemplateAlreadyExists]: "Another template with the same path/name already exists: <br><b>{name}</b>",
[_.errorTimeout]: "Request timeout.",
[_.errorTitle]: "Whoops...",
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/i18n/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ const locale: Locale = {
[_.errorReportsEmptyListing]: "No hay informes sin abrir disponibles.",
[_.errorRetry]: "Reintentar",
[_.errorSignInMsalExceptionOccurred]: "Error inesperado en la solicitud de inicio de sesión.",
[_.errorSignInMsalTimeoutExpired]: "La solicitud de inicio de sesión se canceló porque el tiempo de espera expiró antes de que se completara la operación.",
[_.errorTemplateAlreadyExists]: "¡Ya existe otra plantilla con el mismo camino: <br><b>{name}</b>",
[_.errorTimeout]: "Su solicitud ha expirado",
[_.errorTitle]: "Ups…",
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/i18n/fa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ const locale: Locale = {
[_.errorReportsEmptyListing]: "هیچ گزارش باز نشده ای موجود نیست.",
[_.errorRetry]: "تلاش مجدد",
[_.errorSignInMsalExceptionOccurred]: "خطای غیرمنتظره در درخواست ورود به سیستم.",
[_.errorSignInMsalTimeoutExpired]: "درخواست ورود به سیستم لغو شد زیرا دوره وقفه قبل از تکمیل عملیات منقضی شده بود.",
[_.errorTemplateAlreadyExists]: "الگوی دیگری با همان مسیر/نام از قبل وجود دارد: <br><b>{name}</b>",
[_.errorTimeout]: "زمان پایان درخواست.",
[_.errorTitle]: "اوه...",
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ const locale: Locale = {
[_.errorReportsEmptyListing]: "Aucun rapport disponible.",
[_.errorRetry]: "Réessayer",
[_.errorSignInMsalExceptionOccurred]: "Erreur inattendue lors de l'authentification.",
[_.errorSignInMsalTimeoutExpired]: "La demande d'authentification a été annulée suite à l'expiration du timeout.",
[_.errorTemplateAlreadyExists]: "Un autre modèle avec le même chemin existe déjà: <br><b>{name}</b>",
[_.errorTimeout]: "Timeout sur la demande.",
[_.errorTitle]: "Oops...",
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/i18n/gr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ const locale: Locale = {
[_.errorReportsEmptyListing]: "Δεν υπάρχουν διαθέσιμες ανοικτές αναφορές.",
[_.errorRetry]: "Εκ νέου προσπάθεια",
[_.errorSignInMsalExceptionOccurred]: "Αναπάντεχο σφάλμα κατά την προσπάθεια σύνδεσης.",
[_.errorSignInMsalTimeoutExpired]: "Η προσπάθεια σύνδεσης διακόπηκε επειδή πέρασε ο χρόνος εξαιτίας λήξης του χρόνου που έχει οριστεί για την ολοκλήρωση της(timeout).",
[_.errorTemplateAlreadyExists]: "Ένα άλλο πρότυπο με το ίδιο μονοπάτι υπάρχει ήδη: <br><b>{name}</b>",
[_.errorTimeout]: "Λήξη χρόνου(timeout).",
[_.errorTitle]: "Όυπςςς",
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/i18n/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ const locale: Locale = {
[_.errorReportsEmptyListing]: "Nessun altro report aperto.",
[_.errorRetry]: "Riprova",
[_.errorSignInMsalExceptionOccurred]: "Errore inaspettato durante l'accesso.",
[_.errorSignInMsalTimeoutExpired]: "La richiesta di accesso è stata annullata perché è passato troppo tempo prima che l'operazione venisse completata.",
[_.errorTemplateAlreadyExists]: "Un altro template con lo stesso percorso/nome esiste già: <br><b>{name}</b>",
[_.errorTimeout]: "Timeout.",
[_.errorTitle]: "Oops...",
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/i18n/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ const locale: Locale = {
[_.errorReportsEmptyListing]: "Geen ongeopende rapporten beschikbaar.",
[_.errorRetry]: "Opnieuw proberen",
[_.errorSignInMsalExceptionOccurred]: "Onverwachte fout in het inlogverzoek.",
[_.errorSignInMsalTimeoutExpired]: "Het inlogverzoek is geannuleerd vanwege een onderbreking die optrad voordat het verzoek voltooid kon worden.",
[_.errorTemplateAlreadyExists]: "Er bestaat al een ander sjabloon met hetzelfde pad: <br><b>{name}</b>",
[_.errorTimeout]: "Verzoek onderbroken.",
[_.errorTitle]: "Oeps...",
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/i18n/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ const locale: Locale = {
[_.errorReportsEmptyListing]: "Brak dostępnych, nieotwartych raportów.",
[_.errorRetry]: "Spróbuj ponownie",
[_.errorSignInMsalExceptionOccurred]: "Nieoczekiwany błąd w trakcie logowania.",
[_.errorSignInMsalTimeoutExpired]: "Logowanie zostało anulowane ponieważ czas opracji przekroczył dozwolony.",
[_.errorTemplateAlreadyExists]: "Kolejny szablon z tą samą ścieżką już istnieje: <br><b>{name}</b>",
[_.errorTimeout]: "Limit czasu żądania.",
[_.errorTitle]: "Whoops...",
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/i18n/pt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ const locale: Locale = {
[_.errorReportsEmptyListing]: "Nenhum relatório por abrir disponível.",
[_.errorRetry]: "Tente novamente",
[_.errorSignInMsalExceptionOccurred]: "Erro inesperado no pedido de início de sessão.",
[_.errorSignInMsalTimeoutExpired]: "O pedido de início de sessão foi cancelado porque o tempo limite foi excedido antes da operação estar terminada.",
[_.errorTemplateAlreadyExists]: "Outro modelo com o mesmo caminho já existe: <br><b>{name}</b>",
[_.errorTimeout]: "Tempo limite excedido.",
[_.errorTitle]: "Opa...",
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/i18n/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ const locale: Locale = {
[_.errorReportsEmptyListing]: "No unopened reports available.",
[_.errorRetry]: "Повторить",
[_.errorSignInMsalExceptionOccurred]: "Непредвиденная ошибка в запросе на вход.",
[_.errorSignInMsalTimeoutExpired]: "Запрос на вход был отменен, так как время ожидания истекло до завершения операции",
[_.errorTemplateAlreadyExists]: "Еще один шаблон с тем же путем уже существует: <br><b>{name}</b>",
[_.errorTimeout]: "Истекло время ожидания запроса",
[_.errorTitle]: "Ой...",
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/i18n/tr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ const locale: Locale = {
[_.errorReportsEmptyListing]: "Açılmamış rapor yok.",
[_.errorRetry]: "Yeniden Dene",
[_.errorSignInMsalExceptionOccurred]: "Oturum açma isteğinde beklenmeyen hata.",
[_.errorSignInMsalTimeoutExpired]: "İşlem tamamlanmadan önce zaman aşımı süresi sona erdiği için oturum açma isteği iptal edildi.",
[_.errorTemplateAlreadyExists]: "Aynı yola sahip başka bir şablon zaten var: <br><b>{name}</b>",
[_.errorTimeout]: "İstek zaman aşımı.",
[_.errorTitle]: "Hata...",
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/i18n/uk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ const locale: Locale = {
[_.errorReportsEmptyListing]: "Невідкритих звітів немає.",
[_.errorRetry]: "Спробувати повторно",
[_.errorSignInMsalExceptionOccurred]: "Неочікувана помилка в запиті на вхід.",
[_.errorSignInMsalTimeoutExpired]: "Запит на вхід було скасовано, оскільки час очікування закінчився до завершення операції.",
[_.errorTemplateAlreadyExists]: "Інший шаблон з таким самим шляхом/назвою вже існує: <br><b>{name}</b>",
[_.errorTimeout]: "Запросити перерву",
[_.errorTitle]: "Упс...",
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ const locale: Locale = {
[_.errorReportsEmptyListing]: "没找到未打开的报告",
[_.errorRetry]: "再试一次",
[_.errorSignInMsalExceptionOccurred]: "登录请求出现异常",
[_.errorSignInMsalTimeoutExpired]: "操作超时, 登录请求被取消",
[_.errorTemplateAlreadyExists]: "另一个具有相同路径的模板已经存在: <br><b>{name}</b>",
[_.errorTimeout]: "请求超时",
[_.errorTitle]: "Whoops...",
Expand Down
1 change: 0 additions & 1 deletion src/Scripts/model/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ export enum strings {
errorReportsEmptyListing,
errorRetry,
errorSignInMsalExceptionOccurred,
errorSignInMsalTimeoutExpired,
errorTemplateAlreadyExists,
errorTimeout,
errorTitle,
Expand Down
5 changes: 4 additions & 1 deletion src/Scripts/view/powerbi-signin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,17 @@ export class PowerBISignin extends Dialog {
onAction(action: string, resolve: any, reject: any) {
if (action == "signin") {
auth.signIn(this.data)
.then(() => {
.then(() => {
super.onAction(action, resolve, reject);
})
.catch(ignore => {
this.signInButton.toggleAttr("disabled", false);
this.element.classList.remove("wait");
});
} else {
if (action == "cancel" && this.element.classList.contains("wait"))
host.apiAbortByAction("auth/SignIn");

super.onAction(action, resolve, reject);
}
}
Expand Down
Loading
Loading