Skip to content

Commit

Permalink
feat!: allow to query tenant records using label (#1647)
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Glastra <[email protected]>
  • Loading branch information
TimoGlastra authored Feb 5, 2024
1 parent c5c5b85 commit bb065c0
Show file tree
Hide file tree
Showing 22 changed files with 1,013 additions and 16 deletions.
11 changes: 11 additions & 0 deletions packages/askar/src/storage/__tests__/AskarStorageService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ describe('AskarStorageService', () => {
someOtherBoolean: false,
someStringValue: 'string',
anArrayValue: ['foo', 'bar'],
anArrayValueWhereValuesContainColon: ['foo:bar:test', 'https://google.com'],
// booleans are stored as '1' and '0' so we store the string values '1' and '0' as 'n__1' and 'n__0'
someStringNumberValue: '1',
anotherStringNumberValue: '0',
Expand All @@ -72,6 +73,8 @@ describe('AskarStorageService', () => {
someStringValue: 'string',
'anArrayValue:foo': '1',
'anArrayValue:bar': '1',
'anArrayValueWhereValuesContainColon:foo:bar:test': '1',
'anArrayValueWhereValuesContainColon:https://google.com': '1',
someStringNumberValue: 'n__1',
anotherStringNumberValue: 'n__0',
})
Expand All @@ -88,6 +91,13 @@ describe('AskarStorageService', () => {
someBoolean: '1',
someOtherBoolean: '0',
someStringValue: 'string',
// Before 0.5.0, there was a bug where array values that contained a : would be incorrectly
// parsed back into a record as we would split on ':' and thus only the first part would be included
// in the record as the tag value. If the record was never re-saved it would work well, as well as if the
// record tag was generated dynamically before save (as then the incorrectly transformed back value would be
// overwritten again on save).
'anArrayValueWhereValuesContainColon:foo:bar:test': '1',
'anArrayValueWhereValuesContainColon:https://google.com': '1',
'anArrayValue:foo': '1',
'anArrayValue:bar': '1',
// booleans are stored as '1' and '0' so we store the string values '1' and '0' as 'n__1' and 'n__0'
Expand All @@ -104,6 +114,7 @@ describe('AskarStorageService', () => {
someOtherBoolean: false,
someStringValue: 'string',
anArrayValue: expect.arrayContaining(['bar', 'foo']),
anArrayValueWhereValuesContainColon: expect.arrayContaining(['foo:bar:test', 'https://google.com']),
someStringNumberValue: '1',
anotherStringNumberValue: '0',
})
Expand Down
3 changes: 2 additions & 1 deletion packages/askar/src/storage/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ export function transformToRecordTagValues(tags: Record<string, unknown>): TagsB
// If the value is a boolean string ('1' or '0')
// use the boolean val
if (value === '1' && key?.includes(':')) {
const [tagName, tagValue] = key.split(':')
const [tagName, ...tagValues] = key.split(':')
const tagValue = tagValues.join(':')

const transformedValue = transformedTags[tagName]

Expand Down
72 changes: 69 additions & 3 deletions packages/core/src/storage/migration/__tests__/0.3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ describe('UpdateAssistant | v0.3.1 - v0.4', () => {
},
}

expect(await updateAssistant.isUpToDate()).toBe(false)
expect(await updateAssistant.isUpToDate('0.4')).toBe(false)
expect(await updateAssistant.getNeededUpdates('0.4')).toEqual([
{
fromVersion: '0.3.1',
Expand All @@ -72,9 +72,75 @@ describe('UpdateAssistant | v0.3.1 - v0.4', () => {
},
])

await updateAssistant.update()
await updateAssistant.update('0.4')

expect(await updateAssistant.isUpToDate()).toBe(true)
expect(await updateAssistant.isUpToDate('0.4')).toBe(true)
expect(await updateAssistant.getNeededUpdates('0.4')).toEqual([])

expect(storageService.contextCorrelationIdToRecords[agent.context.contextCorrelationId].records).toMatchSnapshot()

await agent.shutdown()
await agent.wallet.delete()

uuidSpy.mockReset()
})

it(`should correctly update 'claimFormat' tag to w3c records`, async () => {
// We need to mock the uuid generation to make sure we generate consistent uuids for the new records created.
let uuidCounter = 1
const uuidSpy = jest.spyOn(uuid, 'uuid').mockImplementation(() => `${uuidCounter++}-4e4f-41d9-94c4-f49351b811f1`)

const aliceW3cCredentialRecordsString = readFileSync(
path.join(__dirname, '__fixtures__/alice-2-w3c-credential-records-0.3.json'),
'utf8'
)

const dependencyManager = new DependencyManager()
const storageService = new InMemoryStorageService()
dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService)
// If we register the AskarModule it will register the storage service, but we use in memory storage here
dependencyManager.registerContextScoped(InjectionSymbols.Wallet, RegisteredAskarTestWallet)

const agent = new Agent(
{
config: {
label: 'Test Agent',
walletConfig,
},
dependencies: agentDependencies,
},
dependencyManager
)

const updateAssistant = new UpdateAssistant(agent, {
v0_1ToV0_2: {
mediationRoleUpdateStrategy: 'doNotChange',
},
})

await updateAssistant.initialize()

// Set storage after initialization. This mimics as if this wallet
// is opened as an existing wallet instead of a new wallet
storageService.contextCorrelationIdToRecords = {
default: {
records: JSON.parse(aliceW3cCredentialRecordsString),
creationDate: new Date(),
},
}

expect(await updateAssistant.isUpToDate('0.4')).toBe(false)
expect(await updateAssistant.getNeededUpdates('0.4')).toEqual([
{
fromVersion: '0.3.1',
toVersion: '0.4',
doUpdate: expect.any(Function),
},
])

await updateAssistant.update('0.4')

expect(await updateAssistant.isUpToDate('0.4')).toBe(true)
expect(await updateAssistant.getNeededUpdates('0.4')).toEqual([])

expect(storageService.contextCorrelationIdToRecords[agent.context.contextCorrelationId].records).toMatchSnapshot()
Expand Down
94 changes: 94 additions & 0 deletions packages/core/src/storage/migration/__tests__/0.4.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { readFileSync } from 'fs'
import path from 'path'

import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService'
import { RegisteredAskarTestWallet } from '../../../../../askar/tests/helpers'
import { agentDependencies } from '../../../../tests/helpers'
import { Agent } from '../../../agent/Agent'
import { InjectionSymbols } from '../../../constants'
import { W3cCredentialsModule } from '../../../modules/vc'
import { customDocumentLoader } from '../../../modules/vc/data-integrity/__tests__/documentLoader'
import { DependencyManager } from '../../../plugins'
import * as uuid from '../../../utils/uuid'
import { UpdateAssistant } from '../UpdateAssistant'

const backupDate = new Date('2024-02-05T22:50:20.522Z')
jest.useFakeTimers().setSystemTime(backupDate)

const walletConfig = {
id: `Wallet: 0.5 Update`,
key: `Key: 0.5 Update`,
}

describe('UpdateAssistant | v0.4 - v0.5', () => {
it(`should correctly add 'type' tag to w3c records`, async () => {
// We need to mock the uuid generation to make sure we generate consistent uuids for the new records created.
let uuidCounter = 1
const uuidSpy = jest.spyOn(uuid, 'uuid').mockImplementation(() => `${uuidCounter++}-4e4f-41d9-94c4-f49351b811f1`)

const aliceW3cCredentialRecordsString = readFileSync(
path.join(__dirname, '__fixtures__/alice-2-w3c-credential-records-0.4.json'),
'utf8'
)

const dependencyManager = new DependencyManager()
const storageService = new InMemoryStorageService()
dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService)
// If we register the AskarModule it will register the storage service, but we use in memory storage here
dependencyManager.registerContextScoped(InjectionSymbols.Wallet, RegisteredAskarTestWallet)

const agent = new Agent(
{
config: {
label: 'Test Agent',
walletConfig,
},
dependencies: agentDependencies,
modules: {
w3cCredentials: new W3cCredentialsModule({
documentLoader: customDocumentLoader,
}),
},
},
dependencyManager
)

const updateAssistant = new UpdateAssistant(agent, {
v0_1ToV0_2: {
mediationRoleUpdateStrategy: 'doNotChange',
},
})

await updateAssistant.initialize()

// Set storage after initialization. This mimics as if this wallet
// is opened as an existing wallet instead of a new wallet
storageService.contextCorrelationIdToRecords = {
default: {
records: JSON.parse(aliceW3cCredentialRecordsString),
creationDate: new Date(),
},
}

expect(await updateAssistant.isUpToDate('0.5')).toBe(false)
expect(await updateAssistant.getNeededUpdates('0.5')).toEqual([
{
fromVersion: '0.4',
toVersion: '0.5',
doUpdate: expect.any(Function),
},
])

await updateAssistant.update()

expect(await updateAssistant.isUpToDate()).toBe(true)
expect(await updateAssistant.getNeededUpdates('0.5')).toEqual([])

expect(storageService.contextCorrelationIdToRecords[agent.context.contextCorrelationId].records).toMatchSnapshot()

await agent.shutdown()
await agent.wallet.delete()

uuidSpy.mockReset()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"STORAGE_VERSION_RECORD_ID": {
"value": {
"metadata": {},
"id": "STORAGE_VERSION_RECORD_ID",
"createdAt": "2023-03-18T18:35:02.888Z",
"storageVersion": "0.3.1",
"updatedAt": "2023-03-18T18:35:02.888Z"
},
"id": "STORAGE_VERSION_RECORD_ID",
"type": "StorageVersionRecord",
"tags": {}
},
"da65187b-f461-4f39-8597-b0d95531d40d": {
"value": {
"metadata": {},
"id": "da65187b-f461-4f39-8597-b0d95531d40d",
"createdAt": "2024-02-05T06:44:47.600Z",
"credential": "eyJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtva3JzVm84RGJHRHNuTUFqbm9IaEpvdE1iRFppSGZ2eE00ajY1ZDhwclhVciIsInN1YiI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sImlkIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzM3MzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIl0sImlzc3VlciI6eyJpZCI6ImRpZDprZXk6ejZNa29rcnNWbzhEYkdEc25NQWpub0hoSm90TWJEWmlIZnZ4TTRqNjVkOHByWFVyIn0sImlzc3VhbmNlRGF0ZSI6IjIwMTAtMDEtMDFUMTk6MjM6MjRaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEifX0sImp0aSI6Imh0dHA6Ly9leGFtcGxlLmVkdS9jcmVkZW50aWFscy8zNzMyIiwibmJmIjoxMjYyMzczODA0fQ.suzrfmzM07yiiibK0vOdP9Q0dARA7XVNRUa9DSbH519EWrUDgzsq6SiIG9yyBt39yaqsZc1-8byyuMrPziyWBg",
"updatedAt": "2024-02-05T06:44:47.600Z"
},
"type": "W3cCredentialRecord",
"id": "da65187b-f461-4f39-8597-b0d95531d40d",
"tags": {
"algs": ["EdDSA"],
"contexts": ["https://www.w3.org/2018/credentials/v1"],
"givenId": "http://example.edu/credentials/3732",
"issuerId": "did:key:z6MkokrsVo8DbGDsnMAjnoHhJotMbDZiHfvxM4j65d8prXUr",
"subjectIds": ["did:example:ebfeb1f712ebc6f1c276e12ec21"],
"schemaIds": []
}
},
"0e1f070a-e31f-46cf-88db-25c1621b2f4e": {
"value": {
"metadata": {},
"id": "0e1f070a-e31f-46cf-88db-25c1621b2f4e",
"createdAt": "2024-02-05T06:44:47.543Z",
"credential": {
"@context": ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"],
"type": ["VerifiableCredential", "UniversityDegreeCredential"],
"issuer": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL",
"issuanceDate": "2017-10-22T12:23:48Z",
"credentialSubject": {
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
},
"proof": {
"verificationMethod": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL",
"type": "Ed25519Signature2018",
"created": "2022-04-18T23:13:10Z",
"proofPurpose": "assertionMethod",
"jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..ECQsj_lABelr1jkehSkqaYpc5CBvbSjbi3ZvgiVVKxZFDYfj5xZmeXb_awa4aw_cGEVaoypeN2uCFmeG6WKkBw"
}
},
"updatedAt": "2024-02-05T06:44:47.543Z"
},
"type": "W3cCredentialRecord",
"id": "0e1f070a-e31f-46cf-88db-25c1621b2f4e",
"tags": {
"contexts": ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"],
"expandedTypes": ["https", "https"],
"issuerId": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL",
"proofTypes": ["Ed25519Signature2018"],
"subjectIds": [],
"schemaIds": []
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"STORAGE_VERSION_RECORD_ID": {
"value": {
"metadata": {},
"id": "STORAGE_VERSION_RECORD_ID",
"createdAt": "2023-03-18T18:35:02.888Z",
"storageVersion": "0.4",
"updatedAt": "2023-03-18T18:35:02.888Z"
},
"id": "STORAGE_VERSION_RECORD_ID",
"type": "StorageVersionRecord",
"tags": {}
},
"da65187b-f461-4f39-8597-b0d95531d40d": {
"value": {
"metadata": {},
"id": "da65187b-f461-4f39-8597-b0d95531d40d",
"createdAt": "2024-02-05T06:44:47.600Z",
"credential": "eyJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtva3JzVm84RGJHRHNuTUFqbm9IaEpvdE1iRFppSGZ2eE00ajY1ZDhwclhVciIsInN1YiI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sImlkIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzM3MzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIl0sImlzc3VlciI6eyJpZCI6ImRpZDprZXk6ejZNa29rcnNWbzhEYkdEc25NQWpub0hoSm90TWJEWmlIZnZ4TTRqNjVkOHByWFVyIn0sImlzc3VhbmNlRGF0ZSI6IjIwMTAtMDEtMDFUMTk6MjM6MjRaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEifX0sImp0aSI6Imh0dHA6Ly9leGFtcGxlLmVkdS9jcmVkZW50aWFscy8zNzMyIiwibmJmIjoxMjYyMzczODA0fQ.suzrfmzM07yiiibK0vOdP9Q0dARA7XVNRUa9DSbH519EWrUDgzsq6SiIG9yyBt39yaqsZc1-8byyuMrPziyWBg",
"updatedAt": "2024-02-05T06:44:47.600Z"
},
"type": "W3cCredentialRecord",
"id": "da65187b-f461-4f39-8597-b0d95531d40d",
"tags": {
"algs": ["EdDSA"],
"claimFormat": "jwt_vc",
"contexts": ["https://www.w3.org/2018/credentials/v1"],
"givenId": "http://example.edu/credentials/3732",
"issuerId": "did:key:z6MkokrsVo8DbGDsnMAjnoHhJotMbDZiHfvxM4j65d8prXUr",
"subjectIds": ["did:example:ebfeb1f712ebc6f1c276e12ec21"],
"schemaIds": []
}
},
"0e1f070a-e31f-46cf-88db-25c1621b2f4e": {
"value": {
"metadata": {},
"id": "0e1f070a-e31f-46cf-88db-25c1621b2f4e",
"createdAt": "2024-02-05T06:44:47.543Z",
"credential": {
"@context": ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"],
"type": ["VerifiableCredential", "UniversityDegreeCredential"],
"issuer": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL",
"issuanceDate": "2017-10-22T12:23:48Z",
"credentialSubject": {
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
},
"proof": {
"verificationMethod": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL",
"type": "Ed25519Signature2018",
"created": "2022-04-18T23:13:10Z",
"proofPurpose": "assertionMethod",
"jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..ECQsj_lABelr1jkehSkqaYpc5CBvbSjbi3ZvgiVVKxZFDYfj5xZmeXb_awa4aw_cGEVaoypeN2uCFmeG6WKkBw"
}
},
"updatedAt": "2024-02-05T06:44:47.543Z"
},
"type": "W3cCredentialRecord",
"id": "0e1f070a-e31f-46cf-88db-25c1621b2f4e",
"tags": {
"claimFormat": "ldp_vc",
"contexts": ["https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"],
"expandedTypes": ["https", "https"],
"issuerId": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL",
"proofTypes": ["Ed25519Signature2018"],
"subjectIds": [],
"schemaIds": []
}
}
}
Loading

0 comments on commit bb065c0

Please sign in to comment.