/*
* 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);
}
}