tests: vault tests 3 (#2373)
This commit is contained in:
@@ -119,6 +119,7 @@ import {
|
||||
DeleteContact,
|
||||
VaultLockService,
|
||||
RemoveItemsFromMemory,
|
||||
ReencryptTypeAItems,
|
||||
} from '@standardnotes/services'
|
||||
import { ItemManager } from '../../Services/Items/ItemManager'
|
||||
import { PayloadManager } from '../../Services/Payloads/PayloadManager'
|
||||
@@ -202,6 +203,10 @@ export class Dependencies {
|
||||
}
|
||||
|
||||
private registerUseCaseMakers() {
|
||||
this.factory.set(TYPES.ReencryptTypeAItems, () => {
|
||||
return new ReencryptTypeAItems(this.get(TYPES.ItemManager), this.get(TYPES.MutatorService))
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.ImportDataUseCase, () => {
|
||||
return new ImportDataUseCase(
|
||||
this.get(TYPES.ItemManager),
|
||||
@@ -616,10 +621,9 @@ export class Dependencies {
|
||||
return new RootKeyManager(
|
||||
this.get(TYPES.DeviceInterface),
|
||||
this.get(TYPES.DiskStorageService),
|
||||
this.get(TYPES.ItemManager),
|
||||
this.get(TYPES.MutatorService),
|
||||
this.get(TYPES.EncryptionOperators),
|
||||
this.options.identifier,
|
||||
this.get(TYPES.ReencryptTypeAItems),
|
||||
this.get(TYPES.InternalEventBus),
|
||||
)
|
||||
})
|
||||
@@ -1086,6 +1090,7 @@ export class Dependencies {
|
||||
this.get(TYPES.ChallengeService),
|
||||
this.get(TYPES.ProtectionService),
|
||||
this.get(TYPES.UserApiService),
|
||||
this.get(TYPES.ReencryptTypeAItems),
|
||||
this.get(TYPES.InternalEventBus),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -151,6 +151,7 @@ export const TYPES = {
|
||||
DecryptBackupFile: Symbol.for('DecryptBackupFile'),
|
||||
IsVaultOwner: Symbol.for('IsVaultOwner'),
|
||||
RemoveItemsFromMemory: Symbol.for('RemoveItemsFromMemory'),
|
||||
ReencryptTypeAItems: Symbol.for('ReencryptTypeAItems'),
|
||||
|
||||
// Mappers
|
||||
SessionStorageMapper: Symbol.for('SessionStorageMapper'),
|
||||
|
||||
@@ -14,7 +14,6 @@ export class Migration2_202_1 extends Migration {
|
||||
this.registerStageHandler(ApplicationStage.FullSyncCompleted_13, async () => {
|
||||
await this.migrateComponentDataToUserPreferences()
|
||||
await this.migrateActiveComponentsToUserPreferences()
|
||||
await this.deleteComponentsWhichAreNativeFeatures()
|
||||
|
||||
this.markDone()
|
||||
})
|
||||
@@ -70,29 +69,4 @@ export class Migration2_202_1 extends Migration {
|
||||
await this.services.preferences.setValueDetached(PrefKey.ActiveThemes, Uuids(activeThemes))
|
||||
await this.services.preferences.setValueDetached(PrefKey.ActiveComponents, Uuids(activeComponents))
|
||||
}
|
||||
|
||||
private async deleteComponentsWhichAreNativeFeatures(): Promise<void> {
|
||||
const componentsToDelete = [
|
||||
...this.services.itemManager.getItems<ComponentInterface>(ContentType.TYPES.Component),
|
||||
...this.services.itemManager.getItems<ComponentInterface>(ContentType.TYPES.Theme),
|
||||
].filter((candidate) => {
|
||||
const nativeFeature = FindNativeFeature(candidate.identifier)
|
||||
if (!nativeFeature) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isDeprecatedAndThusShouldNotDeleteComponentSinceUserHasItRetained = nativeFeature.deprecated
|
||||
if (isDeprecatedAndThusShouldNotDeleteComponentSinceUserHasItRetained) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if (componentsToDelete.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.services.mutator.setItemsToBeDeleted(componentsToDelete)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,13 +286,11 @@ export class PayloadManager extends AbstractService implements PayloadManagerInt
|
||||
/**
|
||||
* Imports an array of payloads from an external source (such as a backup file)
|
||||
* and marks the items as dirty.
|
||||
* @returns Resulting items
|
||||
*/
|
||||
public async importPayloads(payloads: DecryptedPayloadInterface[], historyMap: HistoryMap): Promise<string[]> {
|
||||
public async importPayloads(payloads: FullyFormedPayloadInterface[], historyMap: HistoryMap): Promise<string[]> {
|
||||
const sourcedPayloads = payloads.map((p) => p.copy(undefined, PayloadSource.FileImport))
|
||||
|
||||
const delta = new DeltaFileImport(this.getMasterCollection(), sourcedPayloads, historyMap)
|
||||
|
||||
const emit = delta.result()
|
||||
|
||||
await this.emitDeltaEmit(emit)
|
||||
|
||||
@@ -6,6 +6,7 @@ export const VaultTests = {
|
||||
'vaults/pkc.test.js',
|
||||
'vaults/contacts.test.js',
|
||||
'vaults/crypto.test.js',
|
||||
'vaults/importing.test.js',
|
||||
'vaults/asymmetric-messages.test.js',
|
||||
'vaults/keypair-change.test.js',
|
||||
'vaults/signatures.test.js',
|
||||
@@ -16,7 +17,7 @@ export const VaultTests = {
|
||||
'vaults/conflicts.test.js',
|
||||
'vaults/deletion.test.js',
|
||||
'vaults/permissions.test.js',
|
||||
'vaults/key_rotation.test.js',
|
||||
'vaults/key-rotation.test.js',
|
||||
'vaults/files.test.js',
|
||||
],
|
||||
}
|
||||
|
||||
@@ -755,7 +755,7 @@ describe('keys', function () {
|
||||
currentServerPassword: currentRootKey.serverPassword,
|
||||
newRootKey,
|
||||
})
|
||||
await this.application.encryption.reencryptApplicableItemsAfterUserRootKeyChange()
|
||||
await this.application.dependencies.get(TYPES.ReencryptTypeAItems).execute()
|
||||
/** Note: this may result in a deadlock if features_service syncs and results in an error */
|
||||
await this.application.sync.sync({ awaitAll: true })
|
||||
|
||||
|
||||
@@ -369,6 +369,17 @@ export class AppContext {
|
||||
})
|
||||
}
|
||||
|
||||
spyOnFunctionResult(object, functionName) {
|
||||
return new Promise((resolve) => {
|
||||
sinon.stub(object, functionName).callsFake(async (params) => {
|
||||
object[functionName].restore()
|
||||
const result = await object[functionName](params)
|
||||
resolve(result)
|
||||
return result
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
resolveWhenAsymmetricMessageProcessingCompletes() {
|
||||
return this.resolveWhenAsyncFunctionCompletes(this.asymmetric, 'handleRemoteReceivedAsymmetricMessages')
|
||||
}
|
||||
|
||||
@@ -121,71 +121,4 @@ describe('migrations', () => {
|
||||
|
||||
await Factory.safeDeinit(application)
|
||||
})
|
||||
|
||||
describe('2.202.1', () => {
|
||||
let application
|
||||
|
||||
beforeEach(async () => {
|
||||
application = await Factory.createAppWithRandNamespace()
|
||||
|
||||
await application.prepareForLaunch({
|
||||
receiveChallenge: () => {},
|
||||
})
|
||||
await application.launch(true)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await Factory.safeDeinit(application)
|
||||
})
|
||||
|
||||
it('remove components that are available as native features', async function () {
|
||||
const editor = CreateDecryptedItemFromPayload(
|
||||
new DecryptedPayload({
|
||||
uuid: '123',
|
||||
content_type: ContentType.TYPES.Component,
|
||||
content: FillItemContent({
|
||||
package_info: {
|
||||
identifier: NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
)
|
||||
await application.mutator.insertItem(editor)
|
||||
await application.sync.sync()
|
||||
|
||||
expect(application.items.getItems(ContentType.TYPES.Component).length).to.equal(1)
|
||||
|
||||
/** Run migration */
|
||||
const migration = new Migration2_202_1(application.migrations.services)
|
||||
await migration.handleStage(ApplicationStage.FullSyncCompleted_13)
|
||||
await application.sync.sync()
|
||||
|
||||
expect(application.items.getItems(ContentType.TYPES.Component).length).to.equal(0)
|
||||
})
|
||||
|
||||
it('do not remove components that are available as native features but deprecated', async function () {
|
||||
const editor = CreateDecryptedItemFromPayload(
|
||||
new DecryptedPayload({
|
||||
uuid: '123',
|
||||
content_type: ContentType.TYPES.Component,
|
||||
content: FillItemContent({
|
||||
package_info: {
|
||||
identifier: NativeFeatureIdentifier.TYPES.DeprecatedBoldEditor,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
)
|
||||
await application.mutator.insertItem(editor)
|
||||
await application.sync.sync()
|
||||
|
||||
expect(application.items.getItems(ContentType.TYPES.Component).length).to.equal(1)
|
||||
|
||||
/** Run migration */
|
||||
const migration = new Migration2_202_1(application.migrations.services)
|
||||
await migration.handleStage(ApplicationStage.FullSyncCompleted_13)
|
||||
await application.sync.sync()
|
||||
|
||||
expect(application.items.getItems(ContentType.TYPES.Component).length).to.equal(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -882,8 +882,4 @@ describe('importing', function () {
|
||||
expect(application.items.referencesForItem(importedTag).length).to.equal(1)
|
||||
expect(application.items.itemsReferencingItem(importedNote).length).to.equal(1)
|
||||
})
|
||||
|
||||
it('should decrypt backup file which contains a vaulted note without a synced key system root key', async () => {
|
||||
console.error('TODO: Implement this test')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('contacts', function () {
|
||||
await deinitContactContext()
|
||||
})
|
||||
|
||||
it('should be able to refresh a contact using a collaborationID that includes full chain of previouos public keys', async () => {
|
||||
it('should be able to refresh a contact using a collaborationID that includes full chain of previous public keys', async () => {
|
||||
console.error('TODO: implement test')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -35,12 +35,25 @@ describe('shared vault crypto', function () {
|
||||
expect(recreatedContext.encryption.getSigningKeyPair()).to.not.be.undefined
|
||||
})
|
||||
|
||||
it('changing user password should re-encrypt all key system root keys', async () => {
|
||||
console.error('TODO: implement')
|
||||
})
|
||||
it('changing user password should re-encrypt all key system root keys and contacts with new user root key', async () => {
|
||||
await Collaboration.createPrivateVault(context)
|
||||
const spy = context.spyOnFunctionResult(context.application.sync, 'payloadsByPreparingForServer')
|
||||
await context.changePassword('new_password')
|
||||
|
||||
it('changing user password should re-encrypt all trusted contacts', async () => {
|
||||
console.error('TODO: implement')
|
||||
const payloads = await spy
|
||||
const keyPayloads = payloads.filter(
|
||||
(payload) =>
|
||||
payload.content_type === ContentType.TYPES.KeySystemRootKey ||
|
||||
payload.content_type === ContentType.TYPES.TrustedContact,
|
||||
)
|
||||
expect(keyPayloads.length).to.equal(2)
|
||||
|
||||
for (const payload of payloads) {
|
||||
const keyParams = context.encryption.getEmbeddedPayloadAuthenticatedData(new EncryptedPayload(payload)).kp
|
||||
|
||||
const userKeyParams = context.encryption.getRootKeyParams().content
|
||||
expect(keyParams).to.eql(userKeyParams)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
58
packages/snjs/mocha/vaults/importing.test.js
Normal file
58
packages/snjs/mocha/vaults/importing.test.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import * as Factory from '../lib/factory.js'
|
||||
import * as Collaboration from '../lib/Collaboration.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe.skip('vault importing', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
|
||||
await context.launch()
|
||||
await context.register()
|
||||
})
|
||||
|
||||
it('should import vaulted items with synced root key', async () => {
|
||||
console.error('TODO: implement')
|
||||
})
|
||||
|
||||
it('should import vaulted items with non-present root key', async () => {
|
||||
const vault = await context.vaults.createUserInputtedPasswordVault({
|
||||
name: 'test vault',
|
||||
userInputtedPassword: 'test password',
|
||||
storagePreference: KeySystemRootKeyStorageMode.Ephemeral,
|
||||
})
|
||||
|
||||
const note = await context.createSyncedNote('foo', 'bar')
|
||||
await Collaboration.moveItemToVault(context, vault, note)
|
||||
|
||||
const backupData = await context.application.createEncryptedBackupFileForAutomatedDesktopBackups()
|
||||
|
||||
const otherContext = await Factory.createAppContextWithRealCrypto()
|
||||
await otherContext.launch()
|
||||
|
||||
await otherContext.application.importData(backupData)
|
||||
|
||||
const expectedImportedItems = ['vault-items-key', 'note']
|
||||
const invalidItems = otherContext.items.invalidItems
|
||||
expect(invalidItems.length).to.equal(expectedImportedItems.length)
|
||||
|
||||
const encryptedItem = invalidItems[0]
|
||||
expect(encryptedItem.key_system_identifier).to.equal(vault.systemIdentifier)
|
||||
expect(encryptedItem.errorDecrypting).to.be.true
|
||||
expect(encryptedItem.uuid).to.equal(note.uuid)
|
||||
|
||||
await otherContext.deinit()
|
||||
})
|
||||
})
|
||||
@@ -4,7 +4,7 @@ import * as Collaboration from '../lib/Collaboration.js'
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
describe('shared vault key rotation', function () {
|
||||
describe('vault key rotation', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
@@ -29,17 +29,66 @@ describe('shared vault key rotation', function () {
|
||||
|
||||
contactContext.lockSyncing()
|
||||
|
||||
const spy = sinon.spy(context.keys, 'queueVaultItemsKeysForReencryption')
|
||||
const callSpy = sinon.spy(context.keys, 'queueVaultItemsKeysForReencryption')
|
||||
const syncSpy = context.spyOnFunctionResult(context.application.sync, 'payloadsByPreparingForServer')
|
||||
|
||||
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await context.vaults.rotateVaultRootKey(sharedVault)
|
||||
await promise
|
||||
await syncSpy
|
||||
|
||||
expect(spy.callCount).to.equal(1)
|
||||
expect(callSpy.callCount).to.equal(1)
|
||||
|
||||
const payloads = await syncSpy
|
||||
const keyPayloads = payloads.filter((payload) => payload.content_type === ContentType.TYPES.KeySystemItemsKey)
|
||||
expect(keyPayloads.length).to.equal(2)
|
||||
|
||||
const vaultRootKey = context.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)
|
||||
|
||||
for (const payload of keyPayloads) {
|
||||
const keyParams = context.encryption.getEmbeddedPayloadAuthenticatedData(new EncryptedPayload(payload)).kp
|
||||
expect(keyParams).to.eql(vaultRootKey.keyParams)
|
||||
}
|
||||
|
||||
deinitContactContext()
|
||||
})
|
||||
|
||||
it('should update value of local storage mode key', async () => {
|
||||
const vault = await context.vaults.createUserInputtedPasswordVault({
|
||||
name: 'test vault',
|
||||
userInputtedPassword: 'test password',
|
||||
storagePreference: KeySystemRootKeyStorageMode.Local,
|
||||
})
|
||||
|
||||
const beforeKey = context.keys.getRootKeyFromStorageForVault(vault.systemIdentifier)
|
||||
|
||||
await context.vaults.rotateVaultRootKey(vault, 'test password')
|
||||
|
||||
const afterKey = context.keys.getRootKeyFromStorageForVault(vault.systemIdentifier)
|
||||
|
||||
expect(afterKey.keyParams.creationTimestamp).to.be.greaterThan(beforeKey.keyParams.creationTimestamp)
|
||||
expect(afterKey.key).to.not.equal(beforeKey.key)
|
||||
expect(afterKey.itemsKey).to.not.equal(beforeKey.itemsKey)
|
||||
})
|
||||
|
||||
it('should update value of mem storage mode key', async () => {
|
||||
const vault = await context.vaults.createUserInputtedPasswordVault({
|
||||
name: 'test vault',
|
||||
userInputtedPassword: 'test password',
|
||||
storagePreference: KeySystemRootKeyStorageMode.Ephemeral,
|
||||
})
|
||||
|
||||
const beforeKey = context.keys.getMemCachedRootKey(vault.systemIdentifier)
|
||||
|
||||
await context.vaults.rotateVaultRootKey(vault, 'test password')
|
||||
|
||||
const afterKey = context.keys.getMemCachedRootKey(vault.systemIdentifier)
|
||||
|
||||
expect(afterKey.keyParams.creationTimestamp).to.be.greaterThan(beforeKey.keyParams.creationTimestamp)
|
||||
expect(afterKey.key).to.not.equal(beforeKey.key)
|
||||
expect(afterKey.itemsKey).to.not.equal(beforeKey.itemsKey)
|
||||
})
|
||||
|
||||
it("rotating a vault's key should send an asymmetric message to all members", async () => {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
@@ -104,10 +104,27 @@ describe('shared vaults', function () {
|
||||
})
|
||||
|
||||
it('should convert a vault to a shared vault', async () => {
|
||||
console.error('TODO')
|
||||
})
|
||||
const privateVault = await context.vaults.createRandomizedVault({
|
||||
name: 'My Private Vault',
|
||||
})
|
||||
|
||||
it('should send metadata change message when changing name or description', async () => {
|
||||
console.error('TODO')
|
||||
const note = await context.createSyncedNote('foo', 'bar')
|
||||
await context.vaults.moveItemToVault(privateVault, note)
|
||||
|
||||
const sharedVault = await context.sharedVaults.convertVaultToSharedVault(privateVault)
|
||||
|
||||
const { thirdPartyContext, deinitThirdPartyContext } = await Collaboration.inviteNewPartyToSharedVault(
|
||||
context,
|
||||
sharedVault,
|
||||
)
|
||||
|
||||
await Collaboration.acceptAllInvites(thirdPartyContext)
|
||||
|
||||
const contextNote = thirdPartyContext.items.findItem(note.uuid)
|
||||
expect(contextNote).to.not.be.undefined
|
||||
expect(contextNote.title).to.equal('foo')
|
||||
expect(contextNote.text).to.equal(note.text)
|
||||
|
||||
await deinitThirdPartyContext()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user