diff --git a/src/App/Pages/Accounts/LoginSsoPage.xaml.cs b/src/App/Pages/Accounts/LoginSsoPage.xaml.cs index 5b7878e55..3871dc8e5 100644 --- a/src/App/Pages/Accounts/LoginSsoPage.xaml.cs +++ b/src/App/Pages/Accounts/LoginSsoPage.xaml.cs @@ -69,12 +69,12 @@ namespace Bit.App.Pages } } - private async void LogIn_Clicked(object sender, EventArgs e) + private void LogIn_Clicked(object sender, EventArgs e) { if (DoOnce()) { CopyAppOptions(); - await _vm.LogInAsync(); + _vm.LogInCommand.Execute(null); } } diff --git a/src/App/Pages/Accounts/LoginSsoPageViewModel.cs b/src/App/Pages/Accounts/LoginSsoPageViewModel.cs index 61dad0114..616e3ad40 100644 --- a/src/App/Pages/Accounts/LoginSsoPageViewModel.cs +++ b/src/App/Pages/Accounts/LoginSsoPageViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using System.Windows.Input; using Bit.App.Abstractions; using Bit.App.Resources; using Bit.App.Utilities; @@ -8,13 +9,15 @@ using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Domain; using Bit.Core.Utilities; +using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.Essentials; -using Xamarin.Forms; namespace Bit.App.Pages { public class LoginSsoPageViewModel : BaseViewModel { + private const string REDIRECT_URI = "bitwarden://sso-callback"; + private readonly IDeviceActionService _deviceActionService; private readonly IAuthService _authService; private readonly ISyncService _syncService; @@ -23,6 +26,7 @@ namespace Bit.App.Pages private readonly ICryptoFunctionService _cryptoFunctionService; private readonly IPlatformUtilsService _platformUtilsService; private readonly IStateService _stateService; + private readonly ILogger _logger; private string _orgIdentifier; @@ -37,9 +41,11 @@ namespace Bit.App.Pages _cryptoFunctionService = ServiceContainer.Resolve("cryptoFunctionService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _stateService = ServiceContainer.Resolve("stateService"); + _logger = ServiceContainer.Resolve("logger"); + PageTitle = AppResources.Bitwarden; - LogInCommand = new Command(async () => await LogInAsync()); + LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false); } public string OrgIdentifier @@ -48,7 +54,7 @@ namespace Bit.App.Pages set => SetProperty(ref _orgIdentifier, value); } - public Command LogInCommand { get; } + public ICommand LogInCommand { get; } public Action StartTwoFactorAction { get; set; } public Action StartSetPasswordAction { get; set; } public Action SsoAuthSuccessAction { get; set; } @@ -65,81 +71,91 @@ namespace Bit.App.Pages public async Task LogInAsync() { - if (Connectivity.NetworkAccess == NetworkAccess.None) - { - await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage, - AppResources.InternetConnectionRequiredTitle); - return; - } - if (string.IsNullOrWhiteSpace(OrgIdentifier)) - { - await _platformUtilsService.ShowDialogAsync( - string.Format(AppResources.ValidationFieldRequired, AppResources.OrgIdentifier), - AppResources.AnErrorHasOccurred, - AppResources.Ok); - return; - } - - await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn); - string ssoToken; - try { + if (Connectivity.NetworkAccess == NetworkAccess.None) + { + await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage, + AppResources.InternetConnectionRequiredTitle); + return; + } + if (string.IsNullOrWhiteSpace(OrgIdentifier)) + { + await _platformUtilsService.ShowDialogAsync( + string.Format(AppResources.ValidationFieldRequired, AppResources.OrgIdentifier), + AppResources.AnErrorHasOccurred, + AppResources.Ok); + return; + } + + await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn); + var response = await _apiService.PreValidateSso(OrgIdentifier); - ssoToken = response.Token; + + if (string.IsNullOrWhiteSpace(response?.Token)) + { + _logger.Error(response is null ? "Login SSO Error: response is null" : "Login SSO Error: response.Token is null or whitespace"); + await _deviceActionService.HideLoadingAsync(); + await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError); + return; + } + + var ssoToken = response.Token; + + + var passwordOptions = new PasswordGenerationOptions(true); + passwordOptions.Length = 64; + + var codeVerifier = await _passwordGenerationService.GeneratePasswordAsync(passwordOptions); + var codeVerifierHash = await _cryptoFunctionService.HashAsync(codeVerifier, CryptoHashAlgorithm.Sha256); + var codeChallenge = CoreHelpers.Base64UrlEncode(codeVerifierHash); + + var state = await _passwordGenerationService.GeneratePasswordAsync(passwordOptions); + + var url = _apiService.IdentityBaseUrl + "/connect/authorize?" + + "client_id=" + _platformUtilsService.GetClientType().GetString() + "&" + + "redirect_uri=" + Uri.EscapeDataString(REDIRECT_URI) + "&" + + "response_type=code&scope=api%20offline_access&" + + "state=" + state + "&code_challenge=" + codeChallenge + "&" + + "code_challenge_method=S256&response_mode=query&" + + "domain_hint=" + Uri.EscapeDataString(OrgIdentifier) + "&" + + "ssoToken=" + Uri.EscapeDataString(ssoToken); + + WebAuthenticatorResult authResult = null; + + authResult = await WebAuthenticator.AuthenticateAsync(new Uri(url), + new Uri(REDIRECT_URI)); + + + var code = GetResultCode(authResult, state); + if (!string.IsNullOrEmpty(code)) + { + await LogIn(code, codeVerifier, OrgIdentifier); + } + else + { + await _deviceActionService.HideLoadingAsync(); + await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError, + AppResources.AnErrorHasOccurred); + } } catch (ApiException e) { + _logger.Exception(e); await _deviceActionService.HideLoadingAsync(); - await _platformUtilsService.ShowDialogAsync( - (e?.Error != null ? e.Error.GetSingleMessage() : AppResources.LoginSsoError), + await _platformUtilsService.ShowDialogAsync(e?.Error?.GetSingleMessage() ?? AppResources.LoginSsoError, AppResources.AnErrorHasOccurred); - return; - } - - var passwordOptions = new PasswordGenerationOptions(true); - passwordOptions.Length = 64; - - var codeVerifier = await _passwordGenerationService.GeneratePasswordAsync(passwordOptions); - var codeVerifierHash = await _cryptoFunctionService.HashAsync(codeVerifier, CryptoHashAlgorithm.Sha256); - var codeChallenge = CoreHelpers.Base64UrlEncode(codeVerifierHash); - - var state = await _passwordGenerationService.GeneratePasswordAsync(passwordOptions); - - var redirectUri = "bitwarden://sso-callback"; - - var url = _apiService.IdentityBaseUrl + "/connect/authorize?" + - "client_id=" + _platformUtilsService.GetClientType().GetString() + "&" + - "redirect_uri=" + Uri.EscapeDataString(redirectUri) + "&" + - "response_type=code&scope=api%20offline_access&" + - "state=" + state + "&code_challenge=" + codeChallenge + "&" + - "code_challenge_method=S256&response_mode=query&" + - "domain_hint=" + Uri.EscapeDataString(OrgIdentifier) + "&" + - "ssoToken=" + Uri.EscapeDataString(ssoToken); - - WebAuthenticatorResult authResult = null; - try - { - authResult = await WebAuthenticator.AuthenticateAsync(new Uri(url), - new Uri(redirectUri)); } catch (TaskCanceledException) { // user canceled await _deviceActionService.HideLoadingAsync(); - return; } - - var code = GetResultCode(authResult, state); - if (!string.IsNullOrEmpty(code)) - { - await LogIn(code, codeVerifier, redirectUri, OrgIdentifier); - } - else + catch (Exception ex) { + _logger.Exception(ex); await _deviceActionService.HideLoadingAsync(); - await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError, - AppResources.AnErrorHasOccurred); + await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage, AppResources.AnErrorHasOccurred); } } @@ -158,11 +174,11 @@ namespace Bit.App.Pages return code; } - private async Task LogIn(string code, string codeVerifier, string redirectUri, string orgId) + private async Task LogIn(string code, string codeVerifier, string orgId) { try { - var response = await _authService.LogInSsoAsync(code, codeVerifier, redirectUri, orgId); + var response = await _authService.LogInSsoAsync(code, codeVerifier, REDIRECT_URI, orgId); await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier); await _deviceActionService.HideLoadingAsync();