Skip to content

Commit

Permalink
ProtonPass: download all vaults (fixes #128)
Browse files Browse the repository at this point in the history
  • Loading branch information
detunized committed Jul 15, 2024
1 parent 992de89 commit eef20b0
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 70 deletions.
78 changes: 40 additions & 38 deletions example/ProtonPass/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,46 +117,48 @@ public static void Main(string[] args)

try
{
var vault = Vault.Open(config["username"],
config["password"],
new AsyncTextUi(),
new AsyncPlainStorage()).GetAwaiter().GetResult();

Console.WriteLine("Vault: {0}\n" +
"Description: {1}\n" +
"Accounts: ({2})\n",
vault.Name,
vault.Description,
vault.Accounts.Length);

for (var i = 0; i < vault.Accounts.Length; ++i)
var vaults = Vault.OpenAll(config["username"],
config["password"],
new AsyncTextUi(),
new AsyncPlainStorage()).GetAwaiter().GetResult();

foreach (var vault in vaults)
{
var account = vault.Accounts[i];

Console.WriteLine("\n" +
"{0}:\n" +
" id: {1}\n" +
" name: {2}\n" +
" email: {3}\n" +
" username: {4}\n" +
" password: {5}\n" +
" totp: {6}\n" +
" note: {7}\n" +
" urls: ({8})",
i + 1,
account.Id,
account.Name,
account.Email,
account.Username,
account.Password,
account.Totp,
account.Note,
account.Urls.Length);

for (var j = 0; j < account.Urls.Length; ++j)
Console.WriteLine(" {0}: {1}", j + 1, account.Urls[j]);
Console.WriteLine("Vault: {0}\n" +
"Description: {1}\n" +
"Accounts: ({2})\n",
vault.Name,
vault.Description,
vault.Accounts.Length);

for (var i = 0; i < vault.Accounts.Length; ++i)
{
var account = vault.Accounts[i];

Console.WriteLine("\n" +
"{0}:\n" +
" id: {1}\n" +
" name: {2}\n" +
" email: {3}\n" +
" username: {4}\n" +
" password: {5}\n" +
" totp: {6}\n" +
" note: {7}\n" +
" urls: ({8})",
i + 1,
account.Id,
account.Name,
account.Email,
account.Username,
account.Password,
account.Totp,
account.Note,
account.Urls.Length);

for (var j = 0; j < account.Urls.Length; ++j)
Console.WriteLine(" {0}: {1}", j + 1, account.Urls[j]);
}
}

}
catch (BaseException e)
{
Expand Down
23 changes: 12 additions & 11 deletions src/ProtonPass/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ namespace PasswordManagerAccess.ProtonPass
internal static class Client
{
// TODO: Refactor this function once done with the logic!
public static async Task<Vault> Open(string username,
string password,
IAsyncUi ui,
IAsyncSecureStorage storage,
RestAsync.Config config,
CancellationToken cancellationToken)
public static async Task<Vault[]> OpenAll(string username,
string password,
IAsyncUi ui,
IAsyncSecureStorage storage,
RestAsync.Config config,
CancellationToken cancellationToken)
{
var rest = RestAsync.Create(BaseUrl, config);
rest.AddOrUpdateDefaultHeader("X-Pm-Appversion", AppVersion);
Expand Down Expand Up @@ -75,7 +75,7 @@ public static async Task<Vault> Open(string username,
{
try
{
return await DownloadVault(password, rest, cancellationToken).ConfigureAwait(false);
return await DownloadAllVault(password, rest, cancellationToken).ConfigureAwait(false);
}
catch (TokenExpiredException)
{
Expand Down Expand Up @@ -298,7 +298,7 @@ internal static async Task StoreHumanVerificationToken(string? tokenType, string
return response.Data!;
}

internal static async Task<Vault> DownloadVault(string password, RestClient rest, CancellationToken cancellationToken)
internal static async Task<Vault[]> DownloadAllVault(string password, RestClient rest, CancellationToken cancellationToken)
{
// 1. Get the key salts
// At this point we're very likely to fail, so we do this first. It seems that when an access token is a bit old and is still good
Expand All @@ -318,10 +318,11 @@ internal static async Task<Vault> DownloadVault(string password, RestClient rest
if (vaultShares.Length == 0)
throw new InternalErrorException("Expected at least one share");

var vaultShare = vaultShares[0];
// 5. Initiate the parallel downloads
var downloads = vaultShares.Select(share => DownloadVaultContent(share, primaryKey, keyPassphrase, rest, cancellationToken)).ToArray();

// 4. Download and decrypt the vault content
return await DownloadVaultContent(vaultShare, primaryKey, keyPassphrase, rest, cancellationToken);
// 6. Wait for all the downloads to finish
return await Task.WhenAll(downloads).ConfigureAwait(false);
}

private static async Task<Vault> DownloadVaultContent(Model.Share vaultShare,
Expand Down
26 changes: 13 additions & 13 deletions src/ProtonPass/Vault.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,27 @@ public class Vault
public Account[] Accounts { get; internal set; } = Array.Empty<Account>();

// TODO: Consider removing the = default on the cancellation token
public static async Task<Vault> Open(string username,
string password,
IAsyncUi ui,
IAsyncSecureStorage storage,
CancellationToken cancellationToken = default)
public static async Task<Vault[]> OpenAll(string username,
string password,
IAsyncUi ui,
IAsyncSecureStorage storage,
CancellationToken cancellationToken = default)
{
return await Open(username, password, ui, storage, new RestAsync.Config(), cancellationToken);
return await OpenAll(username, password, ui, storage, new RestAsync.Config(), cancellationToken);
}

//
// Internal
//

internal static async Task<Vault> Open(string username,
string password,
IAsyncUi ui,
IAsyncSecureStorage storage,
RestAsync.Config config,
CancellationToken cancellationToken)
internal static async Task<Vault[]> OpenAll(string username,
string password,
IAsyncUi ui,
IAsyncSecureStorage storage,
RestAsync.Config config,
CancellationToken cancellationToken)
{
return await Client.Open(username, password, ui, storage, config, cancellationToken);
return await Client.OpenAll(username, password, ui, storage, config, cancellationToken);
}
}
}
2 changes: 1 addition & 1 deletion test/ProtonPass/ClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public async void Open_returns_a_vault_with_a_valid_access_token()
.JsonText(GetFixture("sessions")));

// Act
await Swallow(() => Client.Open("username", "password", GetAsyncUi(), GetAsyncStorage(), mockHttp.ToConfig(), MakeToken()));
await Swallow(() => Client.OpenAll("username", "password", GetAsyncUi(), GetAsyncStorage(), mockHttp.ToConfig(), MakeToken()));

// Assert
mockHttp.VerifyAll();
Expand Down
14 changes: 7 additions & 7 deletions test/ProtonPass/VaultTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ public class VaultTest: TestBase
[Fact(Skip = "TODO: need to add a flow of requests")]
public async void Open_returns_a_vault()
{
var vault = await Vault.Open("username",
"password",
null!,
null!,
new RestFlow(),
new CancellationTokenSource().Token);
vault.Should().NotBeNull();
var vaults = await Vault.OpenAll("username",
"password",
null!,
null!,
new RestFlow(),
new CancellationTokenSource().Token);
vaults.Should().NotBeEmpty();
}
}
}

0 comments on commit eef20b0

Please sign in to comment.