/* * Copyright (c) 2025 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using System.Net; using System.Net.Http; using System.Security; using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; using ProtonVPN.Api.Contracts; using ProtonVPN.Api.Contracts.Auth; using ProtonVPN.Client.Logic.Auth.Contracts.Models; using ProtonVPN.Client.Logic.Auth.Srp.Contracts; using ProtonVPN.Client.Logic.Connection.Contracts.GuestHole; using ProtonVPN.Client.Settings.Contracts; using ProtonVPN.OperatingSystems.WebAuthn.Contracts; namespace ProtonVPN.Client.Logic.Auth.Tests; [TestClass] public class SrpAuthenticatorTest { private const string API_AUTH_ERROR = "auth failed"; private const string USERNAME = "username"; private readonly SecureString _password = new NetworkCredential("", "password").SecurePassword; private IApiClient _apiClient; private ISettings _settings; private IGuestHoleManager _guestHoleManager; private IUnauthSessionManager _unauthSessionManager; private IWebAuthnAuthenticator _webAuthnApi; private ISrpProofGenerator _srpProofGenerator; [TestInitialize] public void Initialize() { _apiClient = Substitute.For(); _settings = Substitute.For(); _guestHoleManager = Substitute.For(); _unauthSessionManager = Substitute.For(); _webAuthnApi = Substitute.For(); _srpProofGenerator = Substitute.For(); } [TestCleanup] public void Cleanup() { _apiClient = null; _settings = null; _guestHoleManager = null; _unauthSessionManager = null; _webAuthnApi = null; _srpProofGenerator = null; } [TestMethod] public async Task AuthShouldFailWhenApiResponseContainsNoSaltAsync() { // Arrange _apiClient.GetAuthInfoResponse(Arg.Any()) .Returns(ApiResponseResult.Ok(new HttpResponseMessage(), GetAuthInfoResponseWithEmptySalt())); SrpAuthenticator sut = GetSrpAuthenticator(); // Act AuthResult result = await sut.LoginUserAsync(USERNAME, _password, CancellationToken.None); // Assert result.Success.Should().BeFalse(); result.Failure.Should().BeTrue(); result.Error.Should().Contain("Incorrect login credentials"); } [TestMethod] public async Task AuthShouldFailWhenAuthInfoRequestFailsAsync() { // Arrange _apiClient.GetAuthInfoResponse(Arg.Any()) .Returns(ApiResponseResult.Fail(new HttpResponseMessage(), API_AUTH_ERROR)); SrpAuthenticator sut = GetSrpAuthenticator(); // Act AuthResult result = await sut.LoginUserAsync(USERNAME, _password, CancellationToken.None); // Assert result.Should().BeEquivalentTo(AuthResult.Fail(API_AUTH_ERROR)); } private AuthInfoResponse GetAuthInfoResponseWithEmptySalt() { AuthInfoResponse response = GetSuccessAuthInfoResponse(); response.Salt = null; return response; } private AuthInfoResponse GetSuccessAuthInfoResponse() { return new() { Code = ResponseCodes.OK_RESPONSE, Details = new(), Error = null, Modulus = "modulus", Salt = "salt", ServerEphemeral = "serverEphemeral", SrpSession = "session", Version = 4, }; } private SrpAuthenticator GetSrpAuthenticator() { return new(_apiClient, _settings, _unauthSessionManager, _guestHoleManager, _webAuthnApi, _srpProofGenerator); } }