From dd75cc1c7aa16337ff58139ba50c03b95414e3df Mon Sep 17 00:00:00 2001 From: ArgusMagnus Date: Mon, 10 Mar 2025 07:33:42 +0100 Subject: [PATCH 1/3] use keeper-sdk storage --- VaultCommander/VaultCommander.csproj | 10 +- .../Vaults/KeeperVault.KeeperStorage.cs | 518 ++---------------- 2 files changed, 38 insertions(+), 490 deletions(-) diff --git a/VaultCommander/VaultCommander.csproj b/VaultCommander/VaultCommander.csproj index d756410..fa2aafb 100644 --- a/VaultCommander/VaultCommander.csproj +++ b/VaultCommander/VaultCommander.csproj @@ -17,11 +17,11 @@ - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/VaultCommander/Vaults/KeeperVault.KeeperStorage.cs b/VaultCommander/Vaults/KeeperVault.KeeperStorage.cs index 2d78c0b..f6f082f 100644 --- a/VaultCommander/Vaults/KeeperVault.KeeperStorage.cs +++ b/VaultCommander/Vaults/KeeperVault.KeeperStorage.cs @@ -1,508 +1,56 @@ -using KeeperSecurity.Vault; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; +using KeeperSecurity.Storage; +using KeeperSecurity.Vault; +using Microsoft.Data.Sqlite; using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; +using System.Data; namespace VaultCommander.Vaults; sealed partial class KeeperVault { - sealed class KeeperStorage : DbContext, IKeeperStorage + sealed class KeeperStorage(Func getConnection, string? personalScopeUid) : IKeeperStorage, IDisposable { - private DbSet userStorage { get; init; } = null!; - private Lazy _userStorage; - - public string PersonalScopeUid - { - get => _userStorage.Value.PersonalScopeUid; - [MemberNotNull(nameof(_userStorage))] - set => _userStorage = new(() => userStorage.Find(value) ?? userStorage.Add(new() { PersonalScopeUid = value }).Entity); - } - - public long Revision - { - get => _userStorage.Value.Revision; - set - { - _userStorage.Value.Revision = value; - SaveChanges(); - } - } - - //public byte[]? PasswordHash=> _userStorage.PasswordHash is null ? null : ProtectedData.Unprotect(_userStorage.PasswordHash, null, DataProtectionScope.CurrentUser); - //public byte[]? ClientKey => _userStorage.ClientKey is null ? null : ProtectedData.Unprotect(_userStorage.ClientKey, null, DataProtectionScope.CurrentUser); - - //public void SetPasswordHashAndClientKey(byte[]? passwordHash, byte[]? clientKey) - //{ - // _userStorage.PasswordHash = passwordHash is null ? null : ProtectedData.Protect(passwordHash, null, DataProtectionScope.CurrentUser); - // _userStorage.ClientKey = clientKey is null ? null : ProtectedData.Protect(clientKey, null, DataProtectionScope.CurrentUser); - // SaveChanges(); - //} - - private DbSet records { get; init; } = null!; - public IEntityStorage Records { get; } - - private DbSet sharedFolders { get; init; } = null!; - public IEntityStorage SharedFolders { get; } - - private DbSet teams { get; init; } = null!; - public IEntityStorage Teams { get; } - - private DbSet nonSharedData { get; init; } = null!; - public IEntityStorage NonSharedData { get; } - - private DbSet recordKeys { get; init; } = null!; - public IPredicateStorage RecordKeys { get; } - - private DbSet sharedFolderKeys { get;init; } = null!; - public IPredicateStorage SharedFolderKeys { get; } - - private DbSet sharedFolderPermissions { get; init; } = null!; - public IPredicateStorage SharedFolderPermissions { get; } - - private DbSet folders { get; init; } = null!; - public IEntityStorage Folders { get; } - - private DbSet folderRecords { get; init; } = null!; - public IPredicateStorage FolderRecords { get; } - - private DbSet recordTypes { get; init; } = null!; - public IEntityStorage RecordTypes { get; } - - readonly string _dbFilename; - - public KeeperStorage(string? personalScopeUid, string dbFilename) - { - _dbFilename = dbFilename; - PersonalScopeUid = personalScopeUid ?? string.Empty; - Records = new EntityStorage(this, records); - SharedFolders = new EntityStorage(this, sharedFolders); - Teams = new EntityStorage(this, teams); - NonSharedData = new EntityStorage(this, nonSharedData); - RecordKeys = new PredicateStorage(this, recordKeys); - SharedFolderKeys = new PredicateStorage(this, sharedFolderKeys); - SharedFolderPermissions = new PredicateStorage(this, sharedFolderPermissions); - Folders = new EntityStorage(this, folders); - FolderRecords = new PredicateStorage(this, folderRecords); - RecordTypes = new EntityStorage(this, recordTypes); - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseSqlite($"Data Source={_dbFilename}"); - base.OnConfiguring(optionsBuilder); - } - - void IKeeperStorage.Clear() - { - void Clear(DbSet set) where T : class, IEntity - => set.RemoveRange(set.Where(x => x.PersonalScopeUid == PersonalScopeUid).ToList()); - - Clear(records); - Clear(sharedFolders); - Clear(teams); - Clear(nonSharedData); - Clear(recordKeys); - Clear(sharedFolderKeys); - Clear(sharedFolderPermissions); - Clear(folders); - Clear(folderRecords); - Clear(recordTypes); - SaveChanges(); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) + // dirty hack to allow to directly initialize properties with getConnection from path + sealed class ConnectionHolder { - base.OnModelCreating(modelBuilder); + public IDbConnection Connection { get; set; } = default!; - ConfigureEntityUid(modelBuilder); - ConfigureEntityUid(modelBuilder); - ConfigureEntityUid(modelBuilder); - ConfigureEntityUid(modelBuilder); - ConfigureEntityUidLink(modelBuilder); - ConfigureEntityUidLink(modelBuilder); - ConfigureEntityUidLink(modelBuilder); - ConfigureEntityUid(modelBuilder); - ConfigureEntityUidLink(modelBuilder); - ConfigureEntityUid(modelBuilder); - modelBuilder.Entity().HasKey(x => x.PersonalScopeUid); - - static EntityTypeBuilder ConfigureEntity(ModelBuilder modelBuilder) where T : class, I, IEntity - { - var typeBuilder = modelBuilder.Entity(); - var properties = typeof(I).GetProperties().Select(x => x.Name).Append(nameof(IEntity.PersonalScopeUid)).ToHashSet(); - foreach (var columnName in typeof(T).GetProperties().Where(x => !properties.Contains(x.Name))) - typeBuilder.Ignore(columnName.Name); - return typeBuilder; - } - - static EntityTypeBuilder ConfigureEntityUid(ModelBuilder modelBuilder) where T : class, I, IEntity, IUid - { - var typeBulder = ConfigureEntity(modelBuilder); - typeBulder.HasKey(x => new { x.PersonalScopeUid, x.Uid }); - return typeBulder; - } - - static EntityTypeBuilder ConfigureEntityUidLink(ModelBuilder modelBuilder) where T : class, I, IEntity, IUidLink - { - var typeBulder = ConfigureEntity(modelBuilder); - typeBulder.HasKey(x => new { x.PersonalScopeUid, x.SubjectUid, x.ObjectUid }); - return typeBulder; - } - } - - sealed class UserStorage - { - public string PersonalScopeUid { get; set; } = null!; - public long Revision { get; set; } - //public byte[]? PasswordHash { get; set; } - //public byte[]? ClientKey { get; set; } + [ThreadStatic] + public static ConnectionHolder? Init; } - interface IEntity - { - public string PersonalScopeUid { get; set; } - } + readonly IDbConnection? _connection; - interface IEntity : IEntity + public KeeperStorage(string path, string? personalScopeUid) + : this((ConnectionHolder.Init = new()) is var x ? () => x.Connection : null!, personalScopeUid) { - public void CopyFrom(T other); + ConnectionHolder.Init!.Connection = _connection = new SqliteConnection(); + ConnectionHolder.Init = null; } - sealed class EntityStorage : IEntityStorage - where I : IUid - where T : class, I, IEntity, new() - { - readonly KeeperStorage _dbContext; - readonly DbSet _set; - - public EntityStorage(KeeperStorage dbContext, DbSet set) - { - _dbContext = dbContext; - _set = set; - } - - public void DeleteUids(IEnumerable uids) - { - var remove = _set.Where(x => x.PersonalScopeUid == _dbContext.PersonalScopeUid && uids.Contains(x.Uid)).ToList(); - _set.RemoveRange(remove); - _dbContext.SaveChanges(); - } - - public IEnumerable GetAll() - { - return _set.Where(x => x.PersonalScopeUid == _dbContext.PersonalScopeUid); - } + public void Dispose() => _connection?.Dispose(); - public I GetEntity(string uid) - { - return _set.Find(_dbContext.PersonalScopeUid, uid)!; - } + public string PersonalScopeUid { get; } = personalScopeUid ?? ""; - public void PutEntities(IEnumerable entities) - { - foreach (var value in entities) - { - if (_set.Find(_dbContext.PersonalScopeUid, value.Uid) is T entity) - entity.CopyFrom(value); - else - { - entity = new(); - entity.CopyFrom(value); - entity.PersonalScopeUid = _dbContext.PersonalScopeUid; - _set.Add(entity); - } - } - _dbContext.SaveChanges(); - } - } + public IRecordStorage VaultSettings { get; } = new SqliteRecordStorage(getConnection); + public IEntityStorage Records { get; } = new SqliteEntityStorage(getConnection); + public IEntityStorage SharedFolders { get; } = new SqliteEntityStorage(getConnection); + public IEntityStorage Teams { get; } = new SqliteEntityStorage(getConnection); + public IEntityStorage NonSharedData { get; } = new SqliteEntityStorage(getConnection); + public ILinkStorage RecordKeys { get; } = new SqliteLinkStorage(getConnection); + public ILinkStorage SharedFolderKeys { get; } = new SqliteLinkStorage(getConnection); + public ILinkStorage SharedFolderPermissions { get; } = new SqliteLinkStorage(getConnection); + public IEntityStorage Folders { get; } = new SqliteEntityStorage(getConnection); + public ILinkStorage FolderRecords { get; } = new SqliteLinkStorage(getConnection); + public IEntityStorage RecordTypes { get; } = new SqliteEntityStorage(getConnection); + public ILinkStorage UserEmails { get; } = new SqliteLinkStorage(getConnection); - sealed class PredicateStorage : IPredicateStorage - where I : IUidLink - where T : class, I, IEntity, new() + public void Clear() { - readonly KeeperStorage _dbContext; - readonly DbSet _set; - - public PredicateStorage(KeeperStorage dbContext, DbSet set) - { - _dbContext = dbContext; - _set = set; - } - - public void DeleteLinks(IEnumerable links) - { - foreach (var link in links) - { - if (_set.Find(_dbContext.PersonalScopeUid, link.SubjectUid, link.ObjectUid) is T entity) - _set.Remove(entity); - } - _dbContext.SaveChanges(); - } - - public void DeleteLinksForObjects(IEnumerable objectUids) - { - var remove = _set.Where(x => x.PersonalScopeUid == _dbContext.PersonalScopeUid && objectUids.Contains(x.ObjectUid)).ToList(); - _set.RemoveRange(remove); - _dbContext.SaveChanges(); - } - - public void DeleteLinksForSubjects(IEnumerable subjectUids) - { - var remove = _set.Where(x => x.PersonalScopeUid == _dbContext.PersonalScopeUid && subjectUids.Contains(x.SubjectUid)).ToList(); - _set.RemoveRange(remove); - _dbContext.SaveChanges(); - } - - public IEnumerable GetAllLinks() - { - return _set.Where(x => x.PersonalScopeUid == _dbContext.PersonalScopeUid); - } - - public IEnumerable GetLinksForObject(string objectUid) - { - return _set.Where(x => x.PersonalScopeUid == _dbContext.PersonalScopeUid && x.ObjectUid == objectUid); - } - - public IEnumerable GetLinksForSubject(string subjectUid) - { - return _set.Where(x => x.PersonalScopeUid == _dbContext.PersonalScopeUid && x.SubjectUid == subjectUid); - } - - public void PutLinks(IEnumerable entities) - { - foreach (var value in entities) - { - if (_set.Find(_dbContext.PersonalScopeUid, value.SubjectUid, value.ObjectUid) is T entity) - entity.CopyFrom(value); - else - { - entity = new(); - entity.CopyFrom(value); - entity.PersonalScopeUid = _dbContext.PersonalScopeUid; - _set.Add(entity); - } - } - _dbContext.SaveChanges(); - } - } - - sealed class RecordEntity : IStorageRecord, IEntity - { - public string PersonalScopeUid { get; set; } = null!; - public long Revision { get; private set; } - public int Version { get; private set; } - public long ClientModifiedTime { get; private set; } - public string? Data { get; set; } - public string? Extra { get; private set; } - public string? Udata { get; private set; } - public bool Shared { get; private set; } - public bool Owner { get; set; } - public string Uid { get; private set; } = null!; - string IStorageRecord.RecordUid => Uid; - - public void CopyFrom(IStorageRecord other) - { - Revision = other.Revision; - Version = other.Version; - ClientModifiedTime = other.ClientModifiedTime; - Data = other.Data; - Extra = other.Extra; - Udata = other.Udata; - Shared = other.Shared; - Owner = other.Owner; - Uid = other.Uid; - } - } - - sealed class SharedFolderEntity : ISharedFolder, IEntity - { - public string PersonalScopeUid { get; set; } = null!; - public long Revision { get; private set; } - public string? Name { get; private set; } - public bool DefaultManageRecords { get; private set; } - public bool DefaultManageUsers { get; private set; } - public bool DefaultCanEdit { get; private set; } - public bool DefaultCanShare { get; private set; } - public string Uid { get; private set; } = null!; - string ISharedFolder.SharedFolderUid => Uid; - - public void CopyFrom(ISharedFolder other) - { - Revision = other.Revision; - Name = other.Name; - DefaultManageRecords = other.DefaultManageRecords; - DefaultManageUsers = other.DefaultManageUsers; - DefaultCanEdit = other.DefaultCanEdit; - DefaultCanShare = other.DefaultCanShare; - Uid = other.Uid; - } - } - - sealed class EnterpriseTeamEntity : IEnterpriseTeam, IEntity - { - public string PersonalScopeUid { get; set; } = null!; - public string? Name { get; private set; } - public string? TeamKey { get; private set; } - public int KeyType { get; private set; } - public string? TeamPrivateKey { get; private set; } - public bool RestrictEdit { get; private set; } - public bool RestrictShare { get; private set; } - public bool RestrictView { get; private set; } - public string Uid { get; private set; } = null!; - string IEnterpriseTeam.TeamUid => Uid; - - public void CopyFrom(IEnterpriseTeam other) - { - Name = other.Name; - TeamKey = other.TeamKey; - KeyType = other.KeyType; - TeamPrivateKey = other.TeamPrivateKey; - RestrictEdit = other.RestrictEdit; - RestrictShare = other.RestrictShare; - RestrictView = other.RestrictView; - Uid = other.Uid; - } - } - - sealed class NonSharedDataEntity : INonSharedData, IEntity - { - public string PersonalScopeUid { get; set; } = null!; - public string? Data { get; set; } - public string Uid { get; private set; } = null!; - string INonSharedData.RecordUid => Uid; - - public void CopyFrom(INonSharedData other) - { - Data = other.Data; - Uid = other.Uid; - } - } - - sealed class RecordMetadataEntity : IRecordMetadata, IEntity - { - public string PersonalScopeUid { get; set; } = null!; - public string? RecordKey { get; private set; } - public int RecordKeyType { get; private set; } - public bool CanShare { get; set; } - public bool CanEdit { get; set; } - public string SubjectUid { get; private set; } = null!; - public string ObjectUid { get; private set; } = null!; - string IRecordMetadata.RecordUid => SubjectUid; - string IRecordMetadata.SharedFolderUid => ObjectUid; - - public void CopyFrom(IRecordMetadata other) - { - RecordKey = other.RecordKey; - RecordKeyType = other.RecordKeyType; - CanShare = other.CanShare; - CanEdit = other.CanEdit; - SubjectUid = other.SubjectUid; - ObjectUid = other.ObjectUid; - } - } - - sealed class SharedFolderKeyEntity : ISharedFolderKey, IEntity - { - public string PersonalScopeUid { get; set; } = null!; - public int KeyType { get; private set; } - public string? SharedFolderKey { get; private set; } - public string SubjectUid { get; private set; } = null!; - public string ObjectUid { get; private set; } = null!; - string ISharedFolderKey.SharedFolderUid => SubjectUid!; - string ISharedFolderKey.TeamUid => ObjectUid!; - - public void CopyFrom(ISharedFolderKey other) - { - KeyType = other.KeyType; - SharedFolderKey = other.SharedFolderKey; - SubjectUid = other.SubjectUid; - ObjectUid = other.ObjectUid; - } - } - - sealed class SharedFolderPermissionEntity : ISharedFolderPermission, IEntity - { - public string PersonalScopeUid { get; set; } = null!; - public int UserType { get; private set; } - public bool ManageRecords { get; private set; } - public bool ManageUsers { get; private set; } - public string SubjectUid { get; private set; } = null!; - public string ObjectUid { get; private set; } = null!; - string ISharedFolderPermission.SharedFolderUid => SubjectUid; - string ISharedFolderPermission.UserId => ObjectUid; - - public void CopyFrom(ISharedFolderPermission other) - { - UserType = other.UserType; - ManageRecords = other.ManageRecords; - ManageUsers = other.ManageUsers; - SubjectUid = other.SubjectUid; - ObjectUid = other.ObjectUid; - } - } - - sealed class FolderEntity : IFolder, IEntity - { - public string PersonalScopeUid { get; set; } = null!; - public string? ParentUid { get; private set; } - public string? FolderType { get; private set; } - public string? FolderKey { get; private set; } - public string? SharedFolderUid { get; private set; } - public long Revision { get; private set; } - public string? Data { get; private set; } - public string Uid { get; private set; } = null!; - string IFolder.FolderUid => Uid!; - - public void CopyFrom(IFolder other) - { - ParentUid = other.ParentUid; - FolderType = other.FolderType; - FolderKey = other.FolderKey; - SharedFolderUid = other.SharedFolderUid; - Revision = other.Revision; - Data = other.Data; - Uid = other.Uid; - } - } - - sealed class FolderRecordLinkEntity : IFolderRecordLink, IEntity - { - public string PersonalScopeUid { get; set; } = null!; - public string SubjectUid { get; private set; } = null!; - public string ObjectUid { get; private set; } = null!; - string IFolderRecordLink.FolderUid => SubjectUid; - string IFolderRecordLink.RecordUid => ObjectUid; - - public void CopyFrom(IFolderRecordLink other) - { - SubjectUid = other.SubjectUid; - ObjectUid = other.ObjectUid; - } - } - - sealed class RecordTypeEntity : IRecordType, IEntity - { - public string PersonalScopeUid { get; set; } = null!; - - public int Id { get; private set; } - - public RecordTypeScope Scope { get; private set; } - - public string? Content { get; private set; } - - public string Uid { get; private set; } = null!; - - public void CopyFrom(IRecordType other) - { - Id = other.Id; - Scope = other.Scope; - Content = other.Content; - Uid = other.Uid; - } + foreach (SqliteStorage storage in (IEnumerable)[ + VaultSettings, Records, SharedFolders, Teams,NonSharedData, RecordKeys, SharedFolderKeys, SharedFolderPermissions, Folders, FolderRecords, RecordTypes, UserEmails]) + storage.DeleteAll(); } } } From 0007700caea231f63b4ae974e3577bbe73255ed4 Mon Sep 17 00:00:00 2001 From: ArgusMagnus Date: Mon, 10 Mar 2025 15:41:13 +0100 Subject: [PATCH 2/3] update to Keeper SDK 1.1.0 --- .../20250310143909_KeeperSdk110.Designer.cs | 350 +++++++++ .../Migrations/20250310143909_KeeperSdk110.cs | 298 +++++++ .../Migrations/KeeperStorageModelSnapshot.cs | 347 +++++++++ VaultCommander/VaultCommander.csproj | 6 +- .../Vaults/KeeperVault.KeeperStorage.cs | 725 +++++++++++++++++- VaultCommander/Vaults/KeeperVault.cs | 256 +++---- 6 files changed, 1820 insertions(+), 162 deletions(-) create mode 100644 VaultCommander/Migrations/20250310143909_KeeperSdk110.Designer.cs create mode 100644 VaultCommander/Migrations/20250310143909_KeeperSdk110.cs create mode 100644 VaultCommander/Migrations/KeeperStorageModelSnapshot.cs diff --git a/VaultCommander/Migrations/20250310143909_KeeperSdk110.Designer.cs b/VaultCommander/Migrations/20250310143909_KeeperSdk110.Designer.cs new file mode 100644 index 0000000..63f1cb9 --- /dev/null +++ b/VaultCommander/Migrations/20250310143909_KeeperSdk110.Designer.cs @@ -0,0 +1,350 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using VaultCommander.Vaults; + +#nullable disable + +namespace VaultCommander.Migrations +{ + [DbContext(typeof(KeeperVault.KeeperStorage))] + [Migration("20250310143909_KeeperSdk110")] + partial class KeeperSdk110 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.13"); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+EnterpriseTeamEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("KeyType") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RestrictEdit") + .HasColumnType("INTEGER"); + + b.Property("RestrictShare") + .HasColumnType("INTEGER"); + + b.Property("RestrictView") + .HasColumnType("INTEGER"); + + b.Property("TeamEcPrivateKey") + .HasColumnType("TEXT"); + + b.Property("TeamKey") + .HasColumnType("TEXT"); + + b.Property("TeamRsaPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("PersonalScopeUid", "Uid"); + + b.ToTable("teams"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+FolderEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("FolderKey") + .HasColumnType("TEXT"); + + b.Property("FolderType") + .HasColumnType("TEXT"); + + b.Property("ParentUid") + .HasColumnType("TEXT"); + + b.Property("Revision") + .HasColumnType("INTEGER"); + + b.Property("SharedFolderUid") + .HasColumnType("TEXT"); + + b.HasKey("PersonalScopeUid", "Uid"); + + b.ToTable("folders"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+FolderRecordLinkEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("SubjectUid") + .HasColumnType("TEXT"); + + b.Property("ObjectUid") + .HasColumnType("TEXT"); + + b.HasKey("PersonalScopeUid", "SubjectUid", "ObjectUid"); + + b.ToTable("folderRecords"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+NonSharedDataEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.HasKey("PersonalScopeUid", "Uid"); + + b.ToTable("nonSharedData"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+RecordEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("ClientModifiedTime") + .HasColumnType("INTEGER"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Extra") + .HasColumnType("TEXT"); + + b.Property("Revision") + .HasColumnType("INTEGER"); + + b.Property("Shared") + .HasColumnType("INTEGER"); + + b.Property("Udata") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("INTEGER"); + + b.HasKey("PersonalScopeUid", "Uid"); + + b.ToTable("records"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+RecordMetadataEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("SubjectUid") + .HasColumnType("TEXT"); + + b.Property("ObjectUid") + .HasColumnType("TEXT"); + + b.Property("CanEdit") + .HasColumnType("INTEGER"); + + b.Property("CanShare") + .HasColumnType("INTEGER"); + + b.Property("Expiration") + .HasColumnType("INTEGER"); + + b.Property("Owner") + .HasColumnType("INTEGER"); + + b.Property("OwnerAccountUid") + .HasColumnType("TEXT"); + + b.Property("RecordKey") + .HasColumnType("TEXT"); + + b.Property("RecordKeyType") + .HasColumnType("INTEGER"); + + b.HasKey("PersonalScopeUid", "SubjectUid", "ObjectUid"); + + b.ToTable("recordKeys"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+RecordTypeEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Content") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RecordTypeId") + .HasColumnType("INTEGER"); + + b.Property("Scope") + .HasColumnType("INTEGER"); + + b.HasKey("PersonalScopeUid", "Uid"); + + b.ToTable("recordTypes"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+SharedFolderEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DefaultCanEdit") + .HasColumnType("INTEGER"); + + b.Property("DefaultCanShare") + .HasColumnType("INTEGER"); + + b.Property("DefaultManageRecords") + .HasColumnType("INTEGER"); + + b.Property("DefaultManageUsers") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OwnerAccountUid") + .HasColumnType("TEXT"); + + b.Property("Revision") + .HasColumnType("INTEGER"); + + b.HasKey("PersonalScopeUid", "Uid"); + + b.ToTable("sharedFolders"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+SharedFolderKeyEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("SubjectUid") + .HasColumnType("TEXT"); + + b.Property("ObjectUid") + .HasColumnType("TEXT"); + + b.Property("KeyType") + .HasColumnType("INTEGER"); + + b.Property("SharedFolderKey") + .HasColumnType("TEXT"); + + b.HasKey("PersonalScopeUid", "SubjectUid", "ObjectUid"); + + b.ToTable("sharedFolderKeys"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+SharedFolderPermissionEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("SubjectUid") + .HasColumnType("TEXT"); + + b.Property("ObjectUid") + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("INTEGER"); + + b.Property("ManageRecords") + .HasColumnType("INTEGER"); + + b.Property("ManageUsers") + .HasColumnType("INTEGER"); + + b.Property("UserType") + .HasColumnType("INTEGER"); + + b.HasKey("PersonalScopeUid", "SubjectUid", "ObjectUid"); + + b.ToTable("sharedFolderPermissions"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+StorageUserEmailEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("SubjectUid") + .HasColumnType("TEXT"); + + b.Property("ObjectUid") + .HasColumnType("TEXT"); + + b.HasKey("PersonalScopeUid", "SubjectUid", "ObjectUid"); + + b.ToTable("userEmails"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+UserStorage", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("Revision") + .HasColumnType("INTEGER"); + + b.HasKey("PersonalScopeUid"); + + b.ToTable("userStorage"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+VaultSettingsEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("SyncDownToken") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("PersonalScopeUid"); + + b.ToTable("vaultSettings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/VaultCommander/Migrations/20250310143909_KeeperSdk110.cs b/VaultCommander/Migrations/20250310143909_KeeperSdk110.cs new file mode 100644 index 0000000..675b68b --- /dev/null +++ b/VaultCommander/Migrations/20250310143909_KeeperSdk110.cs @@ -0,0 +1,298 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace VaultCommander.Migrations +{ + /// + public partial class KeeperSdk110 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "folderRecords"); + + migrationBuilder.DropTable( + name: "folders"); + + migrationBuilder.DropTable( + name: "nonSharedData"); + + migrationBuilder.DropTable( + name: "recordKeys"); + + migrationBuilder.DropTable( + name: "records"); + + migrationBuilder.DropTable( + name: "recordTypes"); + + migrationBuilder.DropTable( + name: "sharedFolderKeys"); + + migrationBuilder.DropTable( + name: "sharedFolderPermissions"); + + migrationBuilder.DropTable( + name: "sharedFolders"); + + migrationBuilder.DropTable( + name: "teams"); + + migrationBuilder.DropTable( + name: "userStorage"); + + migrationBuilder.CreateTable( + name: "folderRecords", + columns: table => new + { + PersonalScopeUid = table.Column(type: "TEXT", nullable: false), + SubjectUid = table.Column(type: "TEXT", nullable: false), + ObjectUid = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_folderRecords", x => new { x.PersonalScopeUid, x.SubjectUid, x.ObjectUid }); + }); + + migrationBuilder.CreateTable( + name: "folders", + columns: table => new + { + PersonalScopeUid = table.Column(type: "TEXT", nullable: false), + Uid = table.Column(type: "TEXT", nullable: false), + ParentUid = table.Column(type: "TEXT", nullable: true), + FolderType = table.Column(type: "TEXT", nullable: true), + FolderKey = table.Column(type: "TEXT", nullable: true), + SharedFolderUid = table.Column(type: "TEXT", nullable: true), + Revision = table.Column(type: "INTEGER", nullable: false), + Data = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_folders", x => new { x.PersonalScopeUid, x.Uid }); + }); + + migrationBuilder.CreateTable( + name: "nonSharedData", + columns: table => new + { + PersonalScopeUid = table.Column(type: "TEXT", nullable: false), + Uid = table.Column(type: "TEXT", nullable: false), + Data = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_nonSharedData", x => new { x.PersonalScopeUid, x.Uid }); + }); + + migrationBuilder.CreateTable( + name: "recordKeys", + columns: table => new + { + PersonalScopeUid = table.Column(type: "TEXT", nullable: false), + SubjectUid = table.Column(type: "TEXT", nullable: false), + ObjectUid = table.Column(type: "TEXT", nullable: false), + RecordKey = table.Column(type: "TEXT", nullable: true), + RecordKeyType = table.Column(type: "INTEGER", nullable: false), + CanShare = table.Column(type: "INTEGER", nullable: false), + CanEdit = table.Column(type: "INTEGER", nullable: false), + Owner = table.Column(type: "INTEGER", nullable: false), + OwnerAccountUid = table.Column(type: "TEXT", nullable: true), + Expiration = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_recordKeys", x => new { x.PersonalScopeUid, x.SubjectUid, x.ObjectUid }); + }); + + migrationBuilder.CreateTable( + name: "records", + columns: table => new + { + PersonalScopeUid = table.Column(type: "TEXT", nullable: false), + Uid = table.Column(type: "TEXT", nullable: false), + Revision = table.Column(type: "INTEGER", nullable: false), + Version = table.Column(type: "INTEGER", nullable: false), + ClientModifiedTime = table.Column(type: "INTEGER", nullable: false), + Data = table.Column(type: "TEXT", nullable: true), + Extra = table.Column(type: "TEXT", nullable: true), + Udata = table.Column(type: "TEXT", nullable: true), + Shared = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_records", x => new { x.PersonalScopeUid, x.Uid }); + }); + + migrationBuilder.CreateTable( + name: "recordTypes", + columns: table => new + { + PersonalScopeUid = table.Column(type: "TEXT", nullable: false), + Uid = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: true), + RecordTypeId = table.Column(type: "INTEGER", nullable: false), + Scope = table.Column(type: "INTEGER", nullable: false), + Content = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_recordTypes", x => new { x.PersonalScopeUid, x.Uid }); + }); + + migrationBuilder.CreateTable( + name: "sharedFolderKeys", + columns: table => new + { + PersonalScopeUid = table.Column(type: "TEXT", nullable: false), + SubjectUid = table.Column(type: "TEXT", nullable: false), + ObjectUid = table.Column(type: "TEXT", nullable: false), + KeyType = table.Column(type: "INTEGER", nullable: false), + SharedFolderKey = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_sharedFolderKeys", x => new { x.PersonalScopeUid, x.SubjectUid, x.ObjectUid }); + }); + + migrationBuilder.CreateTable( + name: "sharedFolderPermissions", + columns: table => new + { + PersonalScopeUid = table.Column(type: "TEXT", nullable: false), + SubjectUid = table.Column(type: "TEXT", nullable: false), + ObjectUid = table.Column(type: "TEXT", nullable: false), + UserType = table.Column(type: "INTEGER", nullable: false), + ManageRecords = table.Column(type: "INTEGER", nullable: false), + ManageUsers = table.Column(type: "INTEGER", nullable: false), + Expiration = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_sharedFolderPermissions", x => new { x.PersonalScopeUid, x.SubjectUid, x.ObjectUid }); + }); + + migrationBuilder.CreateTable( + name: "sharedFolders", + columns: table => new + { + PersonalScopeUid = table.Column(type: "TEXT", nullable: false), + Uid = table.Column(type: "TEXT", nullable: false), + Revision = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", nullable: true), + Data = table.Column(type: "TEXT", nullable: true), + DefaultManageRecords = table.Column(type: "INTEGER", nullable: false), + DefaultManageUsers = table.Column(type: "INTEGER", nullable: false), + DefaultCanEdit = table.Column(type: "INTEGER", nullable: false), + DefaultCanShare = table.Column(type: "INTEGER", nullable: false), + OwnerAccountUid = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_sharedFolders", x => new { x.PersonalScopeUid, x.Uid }); + }); + + migrationBuilder.CreateTable( + name: "teams", + columns: table => new + { + PersonalScopeUid = table.Column(type: "TEXT", nullable: false), + Uid = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: true), + TeamKey = table.Column(type: "TEXT", nullable: true), + KeyType = table.Column(type: "INTEGER", nullable: false), + TeamRsaPrivateKey = table.Column(type: "TEXT", nullable: true), + TeamEcPrivateKey = table.Column(type: "TEXT", nullable: true), + RestrictEdit = table.Column(type: "INTEGER", nullable: false), + RestrictShare = table.Column(type: "INTEGER", nullable: false), + RestrictView = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_teams", x => new { x.PersonalScopeUid, x.Uid }); + }); + + migrationBuilder.CreateTable( + name: "userEmails", + columns: table => new + { + PersonalScopeUid = table.Column(type: "TEXT", nullable: false), + SubjectUid = table.Column(type: "TEXT", nullable: false), + ObjectUid = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_userEmails", x => new { x.PersonalScopeUid, x.SubjectUid, x.ObjectUid }); + }); + + migrationBuilder.CreateTable( + name: "userStorage", + columns: table => new + { + PersonalScopeUid = table.Column(type: "TEXT", nullable: false), + Revision = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_userStorage", x => x.PersonalScopeUid); + }); + + migrationBuilder.CreateTable( + name: "vaultSettings", + columns: table => new + { + PersonalScopeUid = table.Column(type: "TEXT", nullable: false), + SyncDownToken = table.Column(type: "BLOB", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_vaultSettings", x => x.PersonalScopeUid); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "folderRecords"); + + migrationBuilder.DropTable( + name: "folders"); + + migrationBuilder.DropTable( + name: "nonSharedData"); + + migrationBuilder.DropTable( + name: "recordKeys"); + + migrationBuilder.DropTable( + name: "records"); + + migrationBuilder.DropTable( + name: "recordTypes"); + + migrationBuilder.DropTable( + name: "sharedFolderKeys"); + + migrationBuilder.DropTable( + name: "sharedFolderPermissions"); + + migrationBuilder.DropTable( + name: "sharedFolders"); + + migrationBuilder.DropTable( + name: "teams"); + + migrationBuilder.DropTable( + name: "userEmails"); + + migrationBuilder.DropTable( + name: "userStorage"); + + migrationBuilder.DropTable( + name: "vaultSettings"); + } + } +} diff --git a/VaultCommander/Migrations/KeeperStorageModelSnapshot.cs b/VaultCommander/Migrations/KeeperStorageModelSnapshot.cs new file mode 100644 index 0000000..9ab0a30 --- /dev/null +++ b/VaultCommander/Migrations/KeeperStorageModelSnapshot.cs @@ -0,0 +1,347 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using VaultCommander.Vaults; + +#nullable disable + +namespace VaultCommander.Migrations +{ + [DbContext(typeof(KeeperVault.KeeperStorage))] + partial class KeeperStorageModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.13"); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+EnterpriseTeamEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("KeyType") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RestrictEdit") + .HasColumnType("INTEGER"); + + b.Property("RestrictShare") + .HasColumnType("INTEGER"); + + b.Property("RestrictView") + .HasColumnType("INTEGER"); + + b.Property("TeamEcPrivateKey") + .HasColumnType("TEXT"); + + b.Property("TeamKey") + .HasColumnType("TEXT"); + + b.Property("TeamRsaPrivateKey") + .HasColumnType("TEXT"); + + b.HasKey("PersonalScopeUid", "Uid"); + + b.ToTable("teams"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+FolderEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("FolderKey") + .HasColumnType("TEXT"); + + b.Property("FolderType") + .HasColumnType("TEXT"); + + b.Property("ParentUid") + .HasColumnType("TEXT"); + + b.Property("Revision") + .HasColumnType("INTEGER"); + + b.Property("SharedFolderUid") + .HasColumnType("TEXT"); + + b.HasKey("PersonalScopeUid", "Uid"); + + b.ToTable("folders"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+FolderRecordLinkEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("SubjectUid") + .HasColumnType("TEXT"); + + b.Property("ObjectUid") + .HasColumnType("TEXT"); + + b.HasKey("PersonalScopeUid", "SubjectUid", "ObjectUid"); + + b.ToTable("folderRecords"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+NonSharedDataEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.HasKey("PersonalScopeUid", "Uid"); + + b.ToTable("nonSharedData"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+RecordEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("ClientModifiedTime") + .HasColumnType("INTEGER"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Extra") + .HasColumnType("TEXT"); + + b.Property("Revision") + .HasColumnType("INTEGER"); + + b.Property("Shared") + .HasColumnType("INTEGER"); + + b.Property("Udata") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("INTEGER"); + + b.HasKey("PersonalScopeUid", "Uid"); + + b.ToTable("records"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+RecordMetadataEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("SubjectUid") + .HasColumnType("TEXT"); + + b.Property("ObjectUid") + .HasColumnType("TEXT"); + + b.Property("CanEdit") + .HasColumnType("INTEGER"); + + b.Property("CanShare") + .HasColumnType("INTEGER"); + + b.Property("Expiration") + .HasColumnType("INTEGER"); + + b.Property("Owner") + .HasColumnType("INTEGER"); + + b.Property("OwnerAccountUid") + .HasColumnType("TEXT"); + + b.Property("RecordKey") + .HasColumnType("TEXT"); + + b.Property("RecordKeyType") + .HasColumnType("INTEGER"); + + b.HasKey("PersonalScopeUid", "SubjectUid", "ObjectUid"); + + b.ToTable("recordKeys"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+RecordTypeEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Content") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RecordTypeId") + .HasColumnType("INTEGER"); + + b.Property("Scope") + .HasColumnType("INTEGER"); + + b.HasKey("PersonalScopeUid", "Uid"); + + b.ToTable("recordTypes"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+SharedFolderEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DefaultCanEdit") + .HasColumnType("INTEGER"); + + b.Property("DefaultCanShare") + .HasColumnType("INTEGER"); + + b.Property("DefaultManageRecords") + .HasColumnType("INTEGER"); + + b.Property("DefaultManageUsers") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OwnerAccountUid") + .HasColumnType("TEXT"); + + b.Property("Revision") + .HasColumnType("INTEGER"); + + b.HasKey("PersonalScopeUid", "Uid"); + + b.ToTable("sharedFolders"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+SharedFolderKeyEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("SubjectUid") + .HasColumnType("TEXT"); + + b.Property("ObjectUid") + .HasColumnType("TEXT"); + + b.Property("KeyType") + .HasColumnType("INTEGER"); + + b.Property("SharedFolderKey") + .HasColumnType("TEXT"); + + b.HasKey("PersonalScopeUid", "SubjectUid", "ObjectUid"); + + b.ToTable("sharedFolderKeys"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+SharedFolderPermissionEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("SubjectUid") + .HasColumnType("TEXT"); + + b.Property("ObjectUid") + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("INTEGER"); + + b.Property("ManageRecords") + .HasColumnType("INTEGER"); + + b.Property("ManageUsers") + .HasColumnType("INTEGER"); + + b.Property("UserType") + .HasColumnType("INTEGER"); + + b.HasKey("PersonalScopeUid", "SubjectUid", "ObjectUid"); + + b.ToTable("sharedFolderPermissions"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+StorageUserEmailEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("SubjectUid") + .HasColumnType("TEXT"); + + b.Property("ObjectUid") + .HasColumnType("TEXT"); + + b.HasKey("PersonalScopeUid", "SubjectUid", "ObjectUid"); + + b.ToTable("userEmails"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+UserStorage", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("Revision") + .HasColumnType("INTEGER"); + + b.HasKey("PersonalScopeUid"); + + b.ToTable("userStorage"); + }); + + modelBuilder.Entity("VaultCommander.Vaults.KeeperVault+KeeperStorage+VaultSettingsEntity", b => + { + b.Property("PersonalScopeUid") + .HasColumnType("TEXT"); + + b.Property("SyncDownToken") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("PersonalScopeUid"); + + b.ToTable("vaultSettings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/VaultCommander/VaultCommander.csproj b/VaultCommander/VaultCommander.csproj index fa2aafb..bad6995 100644 --- a/VaultCommander/VaultCommander.csproj +++ b/VaultCommander/VaultCommander.csproj @@ -19,7 +19,11 @@ - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + all diff --git a/VaultCommander/Vaults/KeeperVault.KeeperStorage.cs b/VaultCommander/Vaults/KeeperVault.KeeperStorage.cs index f6f082f..fe13cbd 100644 --- a/VaultCommander/Vaults/KeeperVault.KeeperStorage.cs +++ b/VaultCommander/Vaults/KeeperVault.KeeperStorage.cs @@ -1,56 +1,719 @@ -using KeeperSecurity.Storage; +using Google.Protobuf.WellKnownTypes; +using KeeperSecurity.Storage; using KeeperSecurity.Vault; -using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Records; using System; using System.Collections.Generic; using System.Data; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Security.Cryptography; +using System.Xml.Linq; namespace VaultCommander.Vaults; sealed partial class KeeperVault { - sealed class KeeperStorage(Func getConnection, string? personalScopeUid) : IKeeperStorage, IDisposable + internal sealed class KeeperStorage : DbContext, IKeeperStorage { - // dirty hack to allow to directly initialize properties with getConnection from path - sealed class ConnectionHolder + private DbSet userStorage { get; init; } = null!; + private Lazy _userStorage; + + public string PersonalScopeUid + { + get => _userStorage.Value.PersonalScopeUid; + [MemberNotNull(nameof(_userStorage))] + set => _userStorage = new(() => userStorage.Find(value) ?? userStorage.Add(new() { PersonalScopeUid = value }).Entity); + } + + public long Revision + { + get => _userStorage.Value.Revision; + set + { + _userStorage.Value.Revision = value; + SaveChanges(); + } + } + + //public byte[]? PasswordHash=> _userStorage.PasswordHash is null ? null : ProtectedData.Unprotect(_userStorage.PasswordHash, null, DataProtectionScope.CurrentUser); + //public byte[]? ClientKey => _userStorage.ClientKey is null ? null : ProtectedData.Unprotect(_userStorage.ClientKey, null, DataProtectionScope.CurrentUser); + + //public void SetPasswordHashAndClientKey(byte[]? passwordHash, byte[]? clientKey) + //{ + // _userStorage.PasswordHash = passwordHash is null ? null : ProtectedData.Protect(passwordHash, null, DataProtectionScope.CurrentUser); + // _userStorage.ClientKey = clientKey is null ? null : ProtectedData.Protect(clientKey, null, DataProtectionScope.CurrentUser); + // SaveChanges(); + //} + + private DbSet records { get; init; } = null!; + public IEntityStorage Records { get; } + + private DbSet sharedFolders { get; init; } = null!; + public IEntityStorage SharedFolders { get; } + + private DbSet teams { get; init; } = null!; + public IEntityStorage Teams { get; } + + private DbSet nonSharedData { get; init; } = null!; + public IEntityStorage NonSharedData { get; } + + private DbSet recordKeys { get; init; } = null!; + public ILinkStorage RecordKeys { get; } + + private DbSet sharedFolderKeys { get; init; } = null!; + public ILinkStorage SharedFolderKeys { get; } + + private DbSet sharedFolderPermissions { get; init; } = null!; + public ILinkStorage SharedFolderPermissions { get; } + + private DbSet folders { get; init; } = null!; + public IEntityStorage Folders { get; } + + private DbSet folderRecords { get; init; } = null!; + public ILinkStorage FolderRecords { get; } + + private DbSet recordTypes { get; init; } = null!; + public IEntityStorage RecordTypes { get; } + + private DbSet vaultSettings { get; init; } = null!; + public IRecordStorage VaultSettings { get; } + + private DbSet userEmails { get; init; } = null!; + public ILinkStorage UserEmails { get; } + + readonly string _dbFilename; + + public KeeperStorage(string? personalScopeUid, string dbFilename) { - public IDbConnection Connection { get; set; } = default!; + _dbFilename = dbFilename; + PersonalScopeUid = personalScopeUid ?? string.Empty; + Records = new EntityStorage(this, records); + SharedFolders = new EntityStorage(this, sharedFolders); + Teams = new EntityStorage(this, teams); + NonSharedData = new EntityStorage(this, nonSharedData); + RecordKeys = new PredicateStorage(this, recordKeys); + SharedFolderKeys = new PredicateStorage(this, sharedFolderKeys); + SharedFolderPermissions = new PredicateStorage(this, sharedFolderPermissions); + Folders = new EntityStorage(this, folders); + FolderRecords = new PredicateStorage(this, folderRecords); + RecordTypes = new EntityStorage(this, recordTypes); + VaultSettings = new RecordStorage(this, vaultSettings); + UserEmails = new PredicateStorage(this, userEmails); + } - [ThreadStatic] - public static ConnectionHolder? Init; + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlite($"Data Source={_dbFilename}"); + base.OnConfiguring(optionsBuilder); } - readonly IDbConnection? _connection; + void IKeeperStorage.Clear() + { + void Clear(DbSet set) where T : class, IEntity + => set.RemoveRange(set.Where(x => x.PersonalScopeUid == PersonalScopeUid).ToList()); + + Clear(records); + Clear(sharedFolders); + Clear(teams); + Clear(nonSharedData); + Clear(recordKeys); + Clear(sharedFolderKeys); + Clear(sharedFolderPermissions); + Clear(folders); + Clear(folderRecords); + Clear(recordTypes); + SaveChanges(); + } - public KeeperStorage(string path, string? personalScopeUid) - : this((ConnectionHolder.Init = new()) is var x ? () => x.Connection : null!, personalScopeUid) + protected override void OnModelCreating(ModelBuilder modelBuilder) { - ConnectionHolder.Init!.Connection = _connection = new SqliteConnection(); - ConnectionHolder.Init = null; + base.OnModelCreating(modelBuilder); + + ConfigureEntityUid(modelBuilder); + ConfigureEntityUid(modelBuilder); + ConfigureEntityUid(modelBuilder); + ConfigureEntityUid(modelBuilder); + ConfigureEntityUidLink(modelBuilder); + ConfigureEntityUidLink(modelBuilder); + ConfigureEntityUidLink(modelBuilder); + ConfigureEntityUid(modelBuilder); + ConfigureEntityUidLink(modelBuilder); + ConfigureEntityUid(modelBuilder); + ConfigureEntity(modelBuilder).HasKey(x => x.PersonalScopeUid); + ConfigureEntityUidLink(modelBuilder); + modelBuilder.Entity().HasKey(x => x.PersonalScopeUid); + + static EntityTypeBuilder ConfigureEntity(ModelBuilder modelBuilder) where T : class, I, IEntity + { + var typeBuilder = modelBuilder.Entity(); + var properties = typeof(I).GetProperties().Select(x => x.Name).Append(nameof(IEntity.PersonalScopeUid)).ToHashSet(); + foreach (var columnName in typeof(T).GetProperties().Where(x => !properties.Contains(x.Name))) + typeBuilder.Ignore(columnName.Name); + return typeBuilder; + } + + static EntityTypeBuilder ConfigureEntityUid(ModelBuilder modelBuilder) where T : class, I, IEntity, IUid + { + var typeBulder = ConfigureEntity(modelBuilder); + typeBulder.HasKey(x => new { x.PersonalScopeUid, x.Uid }); + return typeBulder; + } + + static EntityTypeBuilder ConfigureEntityUidLink(ModelBuilder modelBuilder) where T : class, I, IEntity, IUidLink + { + var typeBulder = ConfigureEntity(modelBuilder); + typeBulder.HasKey(x => new { x.PersonalScopeUid, x.SubjectUid, x.ObjectUid }); + return typeBulder; + } } - public void Dispose() => _connection?.Dispose(); + sealed class UserStorage + { + public string PersonalScopeUid { get; set; } = null!; + public long Revision { get; set; } + //public byte[]? PasswordHash { get; set; } + //public byte[]? ClientKey { get; set; } + } - public string PersonalScopeUid { get; } = personalScopeUid ?? ""; + interface IEntity + { + public string PersonalScopeUid { get; set; } + } - public IRecordStorage VaultSettings { get; } = new SqliteRecordStorage(getConnection); - public IEntityStorage Records { get; } = new SqliteEntityStorage(getConnection); - public IEntityStorage SharedFolders { get; } = new SqliteEntityStorage(getConnection); - public IEntityStorage Teams { get; } = new SqliteEntityStorage(getConnection); - public IEntityStorage NonSharedData { get; } = new SqliteEntityStorage(getConnection); - public ILinkStorage RecordKeys { get; } = new SqliteLinkStorage(getConnection); - public ILinkStorage SharedFolderKeys { get; } = new SqliteLinkStorage(getConnection); - public ILinkStorage SharedFolderPermissions { get; } = new SqliteLinkStorage(getConnection); - public IEntityStorage Folders { get; } = new SqliteEntityStorage(getConnection); - public ILinkStorage FolderRecords { get; } = new SqliteLinkStorage(getConnection); - public IEntityStorage RecordTypes { get; } = new SqliteEntityStorage(getConnection); - public ILinkStorage UserEmails { get; } = new SqliteLinkStorage(getConnection); + interface IEntity : IEntity + { + public void CopyFrom(T other); + } - public void Clear() + sealed class EntityStorage : IEntityStorage + where I : IUid + where T : class, I, IEntity, new() { - foreach (SqliteStorage storage in (IEnumerable)[ - VaultSettings, Records, SharedFolders, Teams,NonSharedData, RecordKeys, SharedFolderKeys, SharedFolderPermissions, Folders, FolderRecords, RecordTypes, UserEmails]) - storage.DeleteAll(); + readonly KeeperStorage _dbContext; + readonly DbSet _set; + + public EntityStorage(KeeperStorage dbContext, DbSet set) + { + _dbContext = dbContext; + _set = set; + } + + void IEntityStorage.DeleteUids(IEnumerable uids) + { + var remove = _set.Where(x => x.PersonalScopeUid == _dbContext.PersonalScopeUid && uids.Contains(x.Uid)).ToList(); + _set.RemoveRange(remove); + _dbContext.SaveChanges(); + } + + IEnumerable IEntityStorage.GetAll() + { + return _set.Where(x => x.PersonalScopeUid == _dbContext.PersonalScopeUid); + } + + I IEntityStorage.GetEntity(string uid) + { + return _set.Find(_dbContext.PersonalScopeUid, uid)!; + } + + void IEntityStorage.PutEntities(IEnumerable entities) + { + foreach (var value in entities) + { + if (_set.Find(_dbContext.PersonalScopeUid, value.Uid) is T entity) + entity.CopyFrom(value); + else + { + entity = new(); + entity.CopyFrom(value); + entity.PersonalScopeUid = _dbContext.PersonalScopeUid; + _set.Add(entity); + } + } + _dbContext.SaveChanges(); + } } + + sealed class PredicateStorage : ILinkStorage + where I : IUidLink + where T : class, I, IEntity, new() + { + readonly KeeperStorage _dbContext; + readonly DbSet _set; + + public PredicateStorage(KeeperStorage dbContext, DbSet set) + { + _dbContext = dbContext; + _set = set; + } + + public void DeleteLinks(IEnumerable links) + { + foreach (var link in links) + { + if (_set.Find(_dbContext.PersonalScopeUid, link.SubjectUid, link.ObjectUid) is T entity) + _set.Remove(entity); + } + _dbContext.SaveChanges(); + } + + public void DeleteLinksForObjects(IEnumerable objectUids) + { + var remove = _set.Where(x => x.PersonalScopeUid == _dbContext.PersonalScopeUid && objectUids.Contains(x.ObjectUid)).ToList(); + _set.RemoveRange(remove); + _dbContext.SaveChanges(); + } + + public void DeleteLinksForSubjects(IEnumerable subjectUids) + { + var remove = _set.Where(x => x.PersonalScopeUid == _dbContext.PersonalScopeUid && subjectUids.Contains(x.SubjectUid)).ToList(); + _set.RemoveRange(remove); + _dbContext.SaveChanges(); + } + + public IEnumerable GetAllLinks() + { + return _set.Where(x => x.PersonalScopeUid == _dbContext.PersonalScopeUid); + } + + public IEnumerable GetLinksForObject(string objectUid) + { + return _set.Where(x => x.PersonalScopeUid == _dbContext.PersonalScopeUid && x.ObjectUid == objectUid); + } + + public IEnumerable GetLinksForSubject(string subjectUid) + { + return _set.Where(x => x.PersonalScopeUid == _dbContext.PersonalScopeUid && x.SubjectUid == subjectUid); + } + +#pragma warning disable CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). + public I? GetLink(IUidLink link) +#pragma warning restore CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). + { + return _set.Where(x => x.PersonalScopeUid == _dbContext.PersonalScopeUid && x.SubjectUid == link.SubjectUid && x.ObjectUid == link.ObjectUid).FirstOrDefault(); + } + + public void PutLinks(IEnumerable entities) + { + foreach (var value in entities) + { + if (_set.Find(_dbContext.PersonalScopeUid, value.SubjectUid, value.ObjectUid) is T entity) + entity.CopyFrom(value); + else + { + entity = new(); + entity.CopyFrom(value); + entity.PersonalScopeUid = _dbContext.PersonalScopeUid; + _set.Add(entity); + } + } + _dbContext.SaveChanges(); + } + } + + sealed class RecordStorage(KeeperStorage dbContext, DbSet set) : IRecordStorage + where T : class, I, IEntity, new() + { + readonly KeeperStorage _dbContext = dbContext; + readonly DbSet _set = set; + + void IRecordStorage.Delete() + { + var remove = _set.ToList(); + _set.RemoveRange(remove); + _dbContext.SaveChanges(); + } + + I IRecordStorage.Load() + { + return _set.SingleOrDefault()!; + } + + void IRecordStorage.Store(I record) + { + if (_set.Find(_dbContext.PersonalScopeUid) is T entity) + entity.CopyFrom(record); + else + { + entity = new(); + entity.CopyFrom(record); + entity.PersonalScopeUid = _dbContext.PersonalScopeUid; + _set.Add(entity); + } + } + } + + sealed class RecordEntity : IStorageRecord, IEntity + { + public string Uid { get; private set; } = default!; + string IStorageRecord.RecordUid => Uid; + + public long Revision { get; private set; } + long IStorageRecord.Revision => Revision; + + public int Version { get; private set; } + int IStorageRecord.Version => Version; + + public long ClientModifiedTime { get; private set; } + long IStorageRecord.ClientModifiedTime => ClientModifiedTime; + + public string? Data { get; private set; } + string IStorageRecord.Data => Data!; + + public string? Extra { get; private set; } + string IStorageRecord.Extra => Extra!; + + public string? Udata { get; private set; } + string IStorageRecord.Udata => Udata!; + + public bool Shared { get; private set; } + bool IStorageRecord.Shared { get => Shared; set => Shared = value; } + + public string PersonalScopeUid { get; set; } = default!; + + void IEntity.CopyFrom(IStorageRecord other) + { + Uid = other.Uid; + Revision = other.Revision; + Version = other.Version; + ClientModifiedTime = other.ClientModifiedTime; + Data = other.Data; + Extra = other.Extra; + Udata = other.Udata; + Shared = other.Shared; + } + } + + sealed class SharedFolderEntity : IStorageSharedFolder, IEntity + { + public string Uid { get; private set; } = default!; + string IStorageSharedFolder.SharedFolderUid => Uid; + + public long Revision { get; private set; } + long IStorageSharedFolder.Revision => Revision; + + public string? Name { get; private set; } + string IStorageSharedFolder.Name => Name!; + + public string? Data { get; private set; } + string IStorageSharedFolder.Data => Data!; + + public bool DefaultManageRecords { get; private set; } + bool IStorageSharedFolder.DefaultManageRecords => DefaultManageRecords; + + public bool DefaultManageUsers { get; private set; } + bool IStorageSharedFolder.DefaultManageUsers => DefaultManageUsers; + + public bool DefaultCanEdit { get; private set; } + bool IStorageSharedFolder.DefaultCanEdit => DefaultCanEdit; + + public bool DefaultCanShare { get; private set; } + bool IStorageSharedFolder.DefaultCanShare => DefaultCanShare; + + public string? OwnerAccountUid { get; private set; } + string IStorageSharedFolder.OwnerAccountUid => OwnerAccountUid!; + + public string PersonalScopeUid { get; set; } = default!; + + void IEntity.CopyFrom(IStorageSharedFolder other) + { + Uid = other.Uid; + Revision = other.Revision; + Name = other.Name; + DefaultManageRecords = other.DefaultManageRecords; + DefaultManageUsers = other.DefaultManageUsers; + DefaultCanEdit = other.DefaultCanEdit; + DefaultCanShare = other.DefaultCanShare; + Data = other.Data; + OwnerAccountUid = other.OwnerAccountUid; + } + } + + sealed class EnterpriseTeamEntity : IStorageTeam, IEntity + { + public string Uid { get; private set; } = default!; + string IStorageTeam.TeamUid => Uid; + + public string? Name { get; private set; } = default!; + string IStorageTeam.Name => Name; + + public string? TeamKey { get; private set; } + string IStorageTeam.TeamKey => TeamKey!; + + public int KeyType { get; private set; } + int IStorageTeam.KeyType => KeyType; + + public string? TeamRsaPrivateKey { get; private set; } + string IStorageTeam.TeamRsaPrivateKey => TeamRsaPrivateKey!; + + public string? TeamEcPrivateKey { get; private set; } + string IStorageTeam.TeamEcPrivateKey => TeamEcPrivateKey!; + + public bool RestrictEdit { get; private set; } + bool IStorageTeam.RestrictEdit => RestrictEdit; + + public bool RestrictShare { get; private set; } + bool IStorageTeam.RestrictShare => RestrictShare; + + public bool RestrictView { get; private set; } + bool IStorageTeam.RestrictView => RestrictView; + + public string PersonalScopeUid { get; set; } = default!; + + void IEntity.CopyFrom(IStorageTeam other) + { + Uid = other.Uid; + Name = other.Name; + TeamKey = other.TeamKey; + KeyType = other.KeyType; + TeamRsaPrivateKey = other.TeamRsaPrivateKey; + TeamEcPrivateKey = other.TeamEcPrivateKey; + RestrictEdit = other.RestrictEdit; + RestrictShare = other.RestrictShare; + RestrictView = other.RestrictView; + } + } + + sealed class NonSharedDataEntity : IStorageNonSharedData, IEntity + { + public string Uid { get; private set; } = default!; + string IStorageNonSharedData.RecordUid => Uid; + + public string? Data { get; private set; } + string IStorageNonSharedData.Data { get => Data!; set => Data = value; } + + public string PersonalScopeUid { get; set; } = default!; + + void IEntity.CopyFrom(IStorageNonSharedData other) + { + Uid = other.Uid; + Data = other.Data; + } + } + + sealed class RecordMetadataEntity : IStorageRecordKey, IEntity + { + public string SubjectUid { get; private set; } = default!; + string IStorageRecordKey.RecordUid => SubjectUid; + + public string ObjectUid { get; private set; } = default!; + string IStorageRecordKey.SharedFolderUid => ObjectUid; + + public string? RecordKey { get; private set; } + string IStorageRecordKey.RecordKey => RecordKey!; + + public int RecordKeyType { get; private set; } + int IStorageRecordKey.RecordKeyType => RecordKeyType; + + public bool CanShare { get; private set; } + bool IStorageRecordKey.CanShare => CanShare; + + public bool CanEdit { get; private set; } + bool IStorageRecordKey.CanEdit => CanEdit; + + public bool Owner { get; private set; } + bool IStorageRecordKey.Owner => Owner; + + public string? OwnerAccountUid { get; private set; } + string IStorageRecordKey.OwnerAccountUid => OwnerAccountUid!; + + public long Expiration { get; private set; } + long IStorageRecordKey.Expiration => Expiration; + + public string PersonalScopeUid { get; set; } = default!; + + void IEntity.CopyFrom(IStorageRecordKey other) + { + RecordKey = other.RecordKey; + RecordKeyType = other.RecordKeyType; + Owner = other.Owner; + OwnerAccountUid = other.OwnerAccountUid; + Expiration = other.Expiration; + CanShare = other.CanShare; + CanEdit = other.CanEdit; + SubjectUid = other.SubjectUid; + ObjectUid = other.ObjectUid; + } + } + + sealed class SharedFolderKeyEntity : IStorageSharedFolderKey, IEntity + { + public string SubjectUid { get; private set; } = default!; + string IStorageSharedFolderKey.SharedFolderUid => SubjectUid!; + + public string ObjectUid { get; private set; } = default!; + string IStorageSharedFolderKey.TeamUid => ObjectUid; + + public int KeyType { get; private set; } + int IStorageSharedFolderKey.KeyType => KeyType; + + public string? SharedFolderKey { get; private set; } + string IStorageSharedFolderKey.SharedFolderKey => SharedFolderKey!; + + public string PersonalScopeUid { get; set; } = null!; + + void IEntity.CopyFrom(IStorageSharedFolderKey other) + { + KeyType = other.KeyType; + SharedFolderKey = other.SharedFolderKey; + SubjectUid = other.SubjectUid; + ObjectUid = other.ObjectUid; + } + } + + sealed class SharedFolderPermissionEntity : ISharedFolderPermission, IEntity + { + public string SubjectUid { get; private set; } = default!; + string ISharedFolderPermission.SharedFolderUid => SubjectUid; + + public string ObjectUid { get; private set; } = default!; + string ISharedFolderPermission.UserId => ObjectUid; + + public int UserType { get; private set; } + int ISharedFolderPermission.UserType => UserType; + + public bool ManageRecords { get; private set; } + bool ISharedFolderPermission.ManageRecords => ManageRecords; + + public bool ManageUsers { get; private set; } + bool ISharedFolderPermission.ManageUsers => ManageUsers; + + public long Expiration { get; private set; } + long ISharedFolderPermission.Expiration => Expiration; + + public string PersonalScopeUid { get; set; } = default!; + + void IEntity.CopyFrom(ISharedFolderPermission other) + { + UserType = other.UserType; + ManageRecords = other.ManageRecords; + ManageUsers = other.ManageUsers; + Expiration = other.Expiration; + SubjectUid = other.SubjectUid; + ObjectUid = other.ObjectUid; + } + } + + sealed class FolderEntity : IStorageFolder, IEntity + { + public string Uid { get; private set; } = default!; + string IStorageFolder.FolderUid => Uid; + + public string? ParentUid { get; private set; } + string IStorageFolder.ParentUid => ParentUid!; + + public string? FolderType { get; private set; } + string IStorageFolder.FolderType => FolderType!; + + public string? FolderKey { get; private set; } + string IStorageFolder.FolderKey => FolderKey!; + + public string? SharedFolderUid { get; private set; } + string IStorageFolder.SharedFolderUid => SharedFolderUid!; + + public long Revision { get; private set; } + long IStorageFolder.Revision => Revision; + + public string? Data { get; private set; } + string IStorageFolder.Data => Data!; + + public string PersonalScopeUid { get; set; } = default!; + + void IEntity.CopyFrom(IStorageFolder other) + { + Uid = other.Uid; + ParentUid = other.ParentUid; + FolderType = other.FolderType; + FolderKey = other.FolderKey; + SharedFolderUid = other.SharedFolderUid; + Revision = other.Revision; + Data = other.Data; + } + } + + sealed class FolderRecordLinkEntity : IStorageFolderRecord, IEntity + { + public string SubjectUid { get; private set; } = default!; + string IStorageFolderRecord.FolderUid => SubjectUid; + + public string ObjectUid { get; private set; } = default!; + string IStorageFolderRecord.RecordUid => ObjectUid; + + public string PersonalScopeUid { get; set; } = default!; + + void IEntity.CopyFrom(IStorageFolderRecord other) + { + SubjectUid = other.SubjectUid; + ObjectUid = other.ObjectUid; + } + } + + sealed class RecordTypeEntity : IStorageRecordType, IEntity + { + public string Uid { get; private set; } = default!; + + public string? Name { get; private set; } + string IStorageRecordType.Name => Name!; + + public int RecordTypeId { get; private set; } + int IStorageRecordType.RecordTypeId => RecordTypeId; + + public int Scope { get; private set; } + int IStorageRecordType.Scope => Scope; + + public string? Content { get; private set; } + string IStorageRecordType.Content => Content!; + + public string PersonalScopeUid { get; set; } = default!; + + void IEntity.CopyFrom(IStorageRecordType other) + { + RecordTypeId = other.RecordTypeId; + Scope = other.Scope; + Content = other.Content; + Name = other.Name; + Uid = other.Uid; + } + } + + sealed class VaultSettingsEntity : IVaultSettings, IEntity + { + public byte[] SyncDownToken { get; private set; } = default!; + byte[] IVaultSettings.SyncDownToken => SyncDownToken; + + public string PersonalScopeUid { get; private set; } = default!; + string IEntity.PersonalScopeUid { get => PersonalScopeUid; set => PersonalScopeUid = value; } + + void IEntity.CopyFrom(IVaultSettings other) + { + SyncDownToken = other.SyncDownToken; + } + } + + sealed class StorageUserEmailEntity : IStorageUserEmail, IEntity + { + public string SubjectUid { get; private set; } = default!; + public string ObjectUid { get; private set; } = default!; + public string PersonalScopeUid { get; set; } = default!; + string IStorageUserEmail.Email => SubjectUid; + string IStorageUserEmail.AccountUid => ObjectUid; + + void IEntity.CopyFrom(IStorageUserEmail other) + { + SubjectUid = other.SubjectUid; + ObjectUid = other.ObjectUid; + } + } + } + +#if DEBUG + sealed class DesignTimeDbContextFactory : IDesignTimeDbContextFactory + { + public KeeperStorage CreateDbContext(string[] args) + => new(null, ""); } +#endif } diff --git a/VaultCommander/Vaults/KeeperVault.cs b/VaultCommander/Vaults/KeeperVault.cs index 7aeaa07..c5be1a1 100644 --- a/VaultCommander/Vaults/KeeperVault.cs +++ b/VaultCommander/Vaults/KeeperVault.cs @@ -1,6 +1,6 @@ using Enterprise; using KeeperSecurity.Authentication; -using KeeperSecurity.Authentication.Async; +using KeeperSecurity.Authentication.Sync; using KeeperSecurity.Configuration; using KeeperSecurity.Utils; using KeeperSecurity.Vault; @@ -16,9 +16,11 @@ using System.Text; using System.Text.Json; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using System.Windows; using VaultCommander.Commands; +using static Org.BouncyCastle.Math.EC.ECCurve; namespace VaultCommander.Vaults; @@ -31,7 +33,7 @@ sealed partial class KeeperVault : IVault, IAsyncDisposable public string UriFieldName => nameof(VaultCommander); readonly string _dataDirectory; - readonly Auth _auth; + readonly AuthSync _auth; readonly KeeperStorage _storage; readonly Lazy> _vault; @@ -39,16 +41,16 @@ sealed partial class KeeperVault : IVault, IAsyncDisposable { _dataDirectory = Path.Combine(dataDirectoryRoot, VaultName); Directory.CreateDirectory(_dataDirectory); - _auth = new(new AuthUi(_dataDirectory), new JsonConfigurationStorage(new JsonConfigurationCache(new JsonConfigurationFileLoader(Path.Combine(_dataDirectory, "storage.json"))) + _auth = new(new JsonConfigurationStorage(Path.Combine(_dataDirectory, "storage.json")) { ConfigurationProtection = new ConfigurationProtectionFactory() - })) - { + }) + { ResumeSession = true }; _auth.Endpoint.DeviceName = $"{Environment.MachineName}_{nameof(VaultCommander)}"; + _storage = new(_auth.Storage.Get().LastLogin, Path.Combine(_dataDirectory, "storage.sqlite")); _vault = new(VaultFactory); - _storage = new(_auth.Storage.LastLogin, Path.Combine(_dataDirectory, "storage.sqlite")); } public async ValueTask DisposeAsync() @@ -80,32 +82,35 @@ private async Task VaultFactory() public Task GetStatus() { var status = Status.Unauthenticated; + var storage = _auth.Storage.Get(); if (_auth.IsAuthenticated()) status = Status.Unlocked; - else if (!string.IsNullOrEmpty(_auth.Storage.LastLogin)) + else if (!string.IsNullOrEmpty(storage.LastLogin)) status = Status.Locked; - return Task.FromResult(new(_auth.Storage.LastServer, null, _auth.Storage.LastLogin, null, status)); + return Task.FromResult(new(storage.LastServer, null, storage.LastLogin, null, status)); } public async Task Initialize() { - await _storage.Database.EnsureCreatedAsync().ConfigureAwait(false); - await _storage.Database.MigrateAsync(); + //await _storage.Database.EnsureCreatedAsync().ConfigureAwait(false); + await _storage.Database.MigrateAsync().ConfigureAwait(false); return await GetStatus(); } public async Task Login() { - var (user, _) = await Application.Current.Dispatcher.InvokeAsync(() => PasswordDialog.Show(Application.Current.MainWindow, _auth.Storage.LastLogin, emailOnly: true)).Task.ConfigureAwait(false); + var storage = _auth.Storage.Get(); + var (user, _) = await Application.Current.Dispatcher.InvokeAsync(() => PasswordDialog.Show(Application.Current.MainWindow, storage.LastLogin, emailOnly: true)).Task.ConfigureAwait(false); if (!string.IsNullOrEmpty(user)) { - var ui = (AuthUi)_auth.Ui; - ui.Reset(); - using (ui.ProgressBox = await ProgressBox.Show()) + using (var progressBox = await ProgressBox.Show()) + using (var uiCallback = new AuthSyncUiCallback(_auth, progressBox, _dataDirectory)) { - ui.ProgressBox.DetailText = "Anmelden..."; - ui.ProgressBox.DetailProgress = double.NaN; + _auth.UiCallback = uiCallback; + progressBox.DetailText = "Anmelden..."; + progressBox.DetailProgress = double.NaN; await _auth.Login(user, []); + await uiCallback.Wait(); _storage.PersonalScopeUid = _auth.Username; } } @@ -150,7 +155,9 @@ public Task Logout() if (item is null && data.Uid == uid) item = ToRecord(data); - var hasCommandField = data.Custom.Any(x => validCommandSchemes.Any(y => x.Value?.StartsWith(y, StringComparison.OrdinalIgnoreCase) is true)); + var hasCommandField = data.Custom + .OfType>() + .Any(x => validCommandSchemes.Any(y => x.TypedValue?.StartsWith(y, StringComparison.OrdinalIgnoreCase) is true)); if (!hasCommandField) continue; @@ -178,7 +185,7 @@ static IEnumerable TransformFields(IEnumerable fields, foreach (var field in fields) { yield return new( - field.Type switch + field.FieldName switch { "login" => nameof(IArgumentsUsername.Username), _ => string.IsNullOrEmpty(field.FieldLabel) ? field.FieldName : field.FieldLabel @@ -186,11 +193,12 @@ static IEnumerable TransformFields(IEnumerable fields, field switch { TypedField host => string.IsNullOrEmpty(host.TypedValue.Port) ? host.TypedValue.HostName : $"{host.TypedValue.HostName}:{host.TypedValue.Port}", - _ => field.Value + ISerializeTypedField sertf => sertf.ExportTypedField(), + _ => field.ObjectValue?.ToString() }); - if (includeTotp && field.Type == "oneTimeCode" && !string.IsNullOrEmpty(field.Value)) - yield return new(nameof(IArgumentsTotp.Totp), CryptoUtils.GetTotpCode(field.Value).Item1); + if (includeTotp && field.FieldName == "oneTimeCode" && field is TypedField stf && !string.IsNullOrEmpty(stf.TypedValue)) + yield return new(nameof(IArgumentsTotp.Totp), CryptoUtils.GetTotpCode(stf.TypedValue).Item1); } } } @@ -217,132 +225,120 @@ public string Obscure(string data) } } - sealed class AuthUi(string dataDir) : IAuthUI, IAuthSsoUI + sealed class AuthSyncUiCallback(AuthSync auth, ProgressBox.IViewModel progressBox, string dataDir) : IAuthSyncCallback, IDisposable { + readonly AuthSync _auth = auth; + readonly ProgressBox.IViewModel _progressBox = progressBox; readonly string _dataDir = dataDir; - public ProgressBox.IViewModel? ProgressBox { get; set; } - - public async Task WaitForDeviceApproval(IDeviceApprovalChannelInfo[] channels, CancellationToken token) - { - var usedChannels = channels.OfType().ToList(); - if (!usedChannels.Any()) - return false; + readonly TaskCompletionSource _tcs = new(); + readonly CancellationTokenSource _cts = new(); - if (ProgressBox is not null) - { - ProgressBox.DetailText = $"Anfrage ({string.Join(", ", usedChannels.Select(x => x.Channel))}) wurde versandt. Auf Genehmigung warten..."; - ProgressBox.DetailProgress = double.NaN; - } - await Task.WhenAll(usedChannels.Select(x => Task.Run(() => x.InvokeDeviceApprovalPushAction()))).ConfigureAwait(false); - return true; - } + public Task Wait() => _tcs.Task.WaitAsync(_cts.Token); - public async Task WaitForTwoFactorCode(ITwoFactorChannelInfo[] channels, CancellationToken token) + async void IAuthSyncCallback.OnNextStep() { - var codeChannel = channels.OfType().FirstOrDefault(); - if (codeChannel is null) - return false; - - if (codeChannel is ITwoFactorPushInfo pushInfo) + switch (_auth.Step) { - foreach (var action in pushInfo.SupportedActions) - await pushInfo.InvokeTwoFactorPushAction(action); - } - - codeChannel.Duration = TwoFactorDuration.Forever; - var (_, pw) = await Application.Current.Dispatcher.InvokeAsync(() => PasswordDialog.Show(Application.Current.MainWindow, string.Join(' ', codeChannel.ApplicationName, codeChannel.PhoneNumber))).Task.ConfigureAwait(false); - if (pw is null) - return false; - try { await codeChannel.InvokeTwoFactorCodeAction(pw.GetAsClearText()); } - catch (KeeperApiException) { } - return true; - } + default: + _tcs.TrySetResult(); + _cts.Cancel(); + break; - public async Task WaitForUserPassword(IPasswordInfo info, CancellationToken token) - { - var (_, pw) = await Application.Current.Dispatcher.InvokeAsync(() => PasswordDialog.Show(Application.Current.MainWindow, info.Username)).Task.ConfigureAwait(false); - if (pw is null) - return false; - await info.InvokePasswordActionDelegate(pw.GetAsClearText()); - return true; - } - - public void SsoLogoutUrl(string url) - { - Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true }); - } + case ReadyToLoginStep: + break; - public async Task WaitForSsoToken(ISsoTokenActionInfo actionInfo, CancellationToken cancellationToken) - { - var tcs = new TaskCompletionSource(); - await Application.Current.Dispatcher.InvokeAsync(() => - { - var webView = new WebView2(); - var window = new Window { Content = webView, Width = 500, Height = 800 }; - window.Loaded += async (_, _) => - { - var webViewOptions = new CoreWebView2EnvironmentOptions { AllowSingleSignOnUsingOSPrimaryAccount = true }; - var env = await CoreWebView2Environment.CreateAsync(null, _dataDir, webViewOptions); - await webView.EnsureCoreWebView2Async(env); - webView.CoreWebView2.DocumentTitleChanged += (_, _) => window.Title = webView.CoreWebView2.DocumentTitle; - webView.Source = new(actionInfo.SsoLoginUrl); - }; - window.Closed += (_, _) => tcs.TrySetResult(null); - - async void OnNavigationCompleted(object? sender, CoreWebView2NavigationCompletedEventArgs args) - { - var token = JsonSerializer.Deserialize(await webView.ExecuteScriptAsync("token")); - if (!string.IsNullOrEmpty(token) && tcs.TrySetResult(token)) + case DeviceApprovalStep { Channels: { Length: > 0 } } deviceApprovalStep: { - webView.NavigationCompleted -= OnNavigationCompleted; - window.Close(); + _progressBox.DetailText = $"Anfrage ({string.Join(", ", deviceApprovalStep.Channels)}) wurde versandt. Auf Genehmigung warten..."; + _progressBox.DetailProgress = double.NaN; + foreach (var channel in deviceApprovalStep.Channels) + await deviceApprovalStep.SendPush(channel).ConfigureAwait(false); } + break; - } - webView.NavigationCompleted += OnNavigationCompleted; - - using (cancellationToken.Register(window.Close)) - { - if (!cancellationToken.IsCancellationRequested) - window.ShowDialog(); - } - }); - - var token = await tcs.Task; - if (string.IsNullOrEmpty(token)) - return false; - await actionInfo.InvokeSsoTokenAction(token); - return true; - } - - bool _dataKeyRequested = false; - - internal void Reset() => _dataKeyRequested = false; + case TwoFactorStep { Channels: { Length: > 0 } } twoFactorStep: + { + twoFactorStep.Duration = TwoFactorDuration.Forever; + + foreach (var action in twoFactorStep.Channels.SelectMany(twoFactorStep.GetChannelPushActions)) + await twoFactorStep.SendPush(action).ConfigureAwait(false); + + foreach (var channel in twoFactorStep.Channels.Where(twoFactorStep.IsCodeChannel)) + { + var phoneNumber = twoFactorStep.GetPhoneNumber(channel); + var (_, pw) = await Application.Current.Dispatcher.InvokeAsync(() => PasswordDialog.Show(Application.Current.MainWindow, string.Join(' ', channel, phoneNumber))).Task; + if (pw is null) + continue; + + try { await twoFactorStep.SendCode(channel, pw.GetAsClearText()); } + catch (KeeperApiException) { } + break; + } + } + break; - public async Task WaitForDataKey(IDataKeyChannelInfo[] channels, CancellationToken token) - { - if (_dataKeyRequested) - return true; - var ssoChannel = channels.FirstOrDefault(x => x.Channel is DataKeyShareChannel.KeeperPush); - if (ssoChannel is null) - return false; + case PasswordStep passwordStep: + { + var (_, pw) = await Application.Current.Dispatcher.InvokeAsync(() => PasswordDialog.Show(Application.Current.MainWindow, _auth.Username)).Task.ConfigureAwait(false); + if (pw is not null) + await passwordStep.VerifyPassword(pw.GetAsClearText()); + } + break; - if (ProgressBox is not null) - { - ProgressBox.DetailText = $"Anfrage ({ssoChannel.Channel.SsoDataKeyShareChannelText()}) wurde versandt. Auf Genehmigung warten..."; - ProgressBox.DetailProgress = double.NaN; - } + case SsoTokenStep ssoTokenStep: + { + var tcs = new TaskCompletionSource(); + await Application.Current.Dispatcher.InvokeAsync(() => + { + var webView = new WebView2(); + var window = new Window { Content = webView, Width = 500, Height = 800 }; + window.Loaded += async (_, _) => + { + var webViewOptions = new CoreWebView2EnvironmentOptions { AllowSingleSignOnUsingOSPrimaryAccount = true }; + var env = await CoreWebView2Environment.CreateAsync(null, _dataDir, webViewOptions); + await webView.EnsureCoreWebView2Async(env); + webView.CoreWebView2.DocumentTitleChanged += (_, _) => window.Title = webView.CoreWebView2.DocumentTitle; + webView.Source = new(ssoTokenStep.SsoLoginUrl); + }; + window.Closed += (_, _) => tcs.TrySetResult(null); + + async void OnNavigationCompleted(object? sender, CoreWebView2NavigationCompletedEventArgs args) + { + var token = JsonSerializer.Deserialize(await webView.ExecuteScriptAsync("token")); + if (!string.IsNullOrEmpty(token) && tcs.TrySetResult(token)) + { + webView.NavigationCompleted -= OnNavigationCompleted; + window.Close(); + } + } + webView.NavigationCompleted += OnNavigationCompleted; + + using (_cts.Token.Register(window.Close)) + { + if (!_cts.IsCancellationRequested) + window.ShowDialog(); + } + }).Task.ConfigureAwait(false); + + var token = await tcs.Task; + if (!string.IsNullOrEmpty(token)) + await ssoTokenStep.SetSsoToken(token); + } + break; - //var tcs = new TaskCompletionSource(); - //await using (token.Register(tcs.SetResult)) - { - await ssoChannel.InvokeGetDataKeyAction().ConfigureAwait(false); - _dataKeyRequested = true; - //await tcs.Task; + case SsoDataKeyStep { Channels: { Length: > 0 } } ssoDataKeyStep: + { + _progressBox.DetailText = $"SSO Anfrage wurde versandt. Auf Genehmigung warten..."; + _progressBox.DetailProgress = double.NaN; + foreach (var channel in ssoDataKeyStep.Channels) + await ssoDataKeyStep.RequestDataKey(channel).ConfigureAwait(false); + } + break; } - return true; } + + public void Dispose() => _cts.Cancel(); } sealed class VaultUi : IVaultUi From 9b95c6ff14cc82962910a7cff15a257a219140d9 Mon Sep 17 00:00:00 2001 From: ArgusMagnus Date: Mon, 10 Mar 2025 15:43:08 +0100 Subject: [PATCH 3/3] fix warning --- VaultCommander/Vaults/KeeperVault.KeeperStorage.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VaultCommander/Vaults/KeeperVault.KeeperStorage.cs b/VaultCommander/Vaults/KeeperVault.KeeperStorage.cs index fe13cbd..f1e2f5b 100644 --- a/VaultCommander/Vaults/KeeperVault.KeeperStorage.cs +++ b/VaultCommander/Vaults/KeeperVault.KeeperStorage.cs @@ -437,8 +437,8 @@ sealed class EnterpriseTeamEntity : IStorageTeam, IEntity public string Uid { get; private set; } = default!; string IStorageTeam.TeamUid => Uid; - public string? Name { get; private set; } = default!; - string IStorageTeam.Name => Name; + public string? Name { get; private set; } + string IStorageTeam.Name => Name!; public string? TeamKey { get; private set; } string IStorageTeam.TeamKey => TeamKey!;