From 9acc4c84278f8d05e1b6a1b64dde353527891970 Mon Sep 17 00:00:00 2001 From: Benjamin Lim <36101433+benjaminlim00@users.noreply.github.com> Date: Wed, 29 Jan 2020 17:05:26 +0800 Subject: [PATCH] feat: added unwrap option to batch command (#13) --- src/__tests__/batchIssue.e2e.test.ts | 76 ++++++++++++++++--- .../wrapped-open-attestation-document.json | 35 +++++++++ src/batchIssue.ts | 24 ++++-- src/index.ts | 10 ++- 4 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 src/__tests__/fixture/wrapped-open-attestation-document.json diff --git a/src/__tests__/batchIssue.e2e.test.ts b/src/__tests__/batchIssue.e2e.test.ts index 83ab3c44..82df0ec2 100644 --- a/src/__tests__/batchIssue.e2e.test.ts +++ b/src/__tests__/batchIssue.e2e.test.ts @@ -8,6 +8,7 @@ const inputDirectoryName = `${fixtureFolderName}/_tmp_in`; const outputDirectoryName = `${fixtureFolderName}/_tmp_out`; const validFileName = `${fixtureFolderName}/valid-open-attestation-document.json`; const invalidFileName = `${fixtureFolderName}/invalid-open-attestation-document.json`; +const wrappedFileName = `${fixtureFolderName}/wrapped-open-attestation-document.json`; const inputDirectory = path.resolve(__dirname, inputDirectoryName); const outputDirectory = path.resolve(__dirname, outputDirectoryName); @@ -29,7 +30,10 @@ describe("batchIssue", () => { path.resolve(__dirname, validFileName), path.resolve(__dirname, `${inputDirectoryName}/valid-open-attestation-document.json`) ); - const merkleRoot = await batchIssue(inputDirectory, outputDirectory, { version: "open-attestation/3.0" }); + const merkleRoot = await batchIssue(inputDirectory, outputDirectory, { + version: "open-attestation/3.0", + unwrap: false + }); const file = JSON.parse( fs.readFileSync(`${outputDirectory}/valid-open-attestation-document.json`, { encoding: "utf8" }) @@ -51,7 +55,10 @@ describe("batchIssue", () => { path.resolve(__dirname, validFileName), path.resolve(__dirname, `${inputDirectoryName}/valid-open-attestation-document-3.json`) ); - const merkleRoot = await batchIssue(inputDirectory, outputDirectory, { version: "open-attestation/3.0" }); + const merkleRoot = await batchIssue(inputDirectory, outputDirectory, { + version: "open-attestation/3.0", + unwrap: false + }); const file1 = JSON.parse( fs.readFileSync(`${outputDirectory}/valid-open-attestation-document-1.json`, { encoding: "utf8" }) ); @@ -78,7 +85,9 @@ describe("batchIssue", () => { path.resolve(__dirname, `${inputDirectoryName}/invalid-open-attestation-document.json`) ); - await expect(batchIssue(inputDirectory, outputDirectory, { version: "open-attestation/3.0" })).rejects.toThrow( + await expect( + batchIssue(inputDirectory, outputDirectory, { version: "open-attestation/3.0", unwrap: false }) + ).rejects.toThrow( expect.objectContaining({ message: expect.stringContaining( "src/__tests__/fixture/_tmp_in/invalid-open-attestation-document.json is not valid against open-attestation schema" @@ -101,7 +110,9 @@ describe("batchIssue", () => { path.resolve(__dirname, `${inputDirectoryName}/invalid-open-attestation-document-3.json`) ); - await expect(batchIssue(inputDirectory, outputDirectory, { version: "open-attestation/3.0" })).rejects.toThrow( + await expect( + batchIssue(inputDirectory, outputDirectory, { version: "open-attestation/3.0", unwrap: false }) + ).rejects.toThrow( expect.objectContaining({ message: expect.stringContaining( "src/__tests__/fixture/_tmp_in/invalid-open-attestation-document-1.json is not valid against open-attestation schema" @@ -110,16 +121,57 @@ describe("batchIssue", () => { ); expect(fs.readdirSync(outputDirectory)).toHaveLength(0); }); + it("should not issue document when given wrapped document without --unwrap", async () => { + fs.copyFileSync( + path.resolve(__dirname, wrappedFileName), + path.resolve(__dirname, `${inputDirectoryName}/wrapped-open-attestation-document.json`) + ); + + await expect( + batchIssue(inputDirectory, outputDirectory, { + version: "open-attestation/3.0", + unwrap: false + }) + ).rejects.toThrow( + expect.objectContaining({ + message: expect.stringContaining( + "src/__tests__/fixture/_tmp_in/wrapped-open-attestation-document.json is not valid against open-attestation schema" + ) + }) + ); + expect(fs.readdirSync(outputDirectory)).toHaveLength(0); + }); + + it("should issue document when the given document is wrapped and --unwrap is specified", async () => { + fs.copyFileSync( + path.resolve(__dirname, wrappedFileName), + path.resolve(__dirname, `${inputDirectoryName}/wrapped-open-attestation-document.json`) + ); + + const merkleRoot = await batchIssue(inputDirectory, outputDirectory, { + version: "open-attestation/3.0", + unwrap: true + }); + + const file = JSON.parse( + fs.readFileSync(`${outputDirectory}/wrapped-open-attestation-document.json`, { encoding: "utf8" }) + ); + + expect(merkleRoot).toHaveLength(64); + expect(merkleRoot).toStrictEqual(file.signature.merkleRoot); + expect(merkleRoot).toStrictEqual(file.signature.targetHash); + }); }); describe("with schema", () => { - it("should issue documents when folder contain one valid open attestation that is also valid against the local schema provided", async () => { + it("should not issue document when the given document is wrapped and --unwrap is not specified", async () => { fs.copyFileSync( path.resolve(__dirname, `${fixtureFolderName}/valid-custom-schema-document.json`), path.resolve(__dirname, `${inputDirectoryName}/valid-custom-schema-document.json`) ); const merkleRoot = await batchIssue(inputDirectory, outputDirectory, { schemaPath: path.resolve(__dirname, fixtureFolderName, "schema.json"), - version: "open-attestation/3.0" + version: "open-attestation/3.0", + unwrap: false }); const file = JSON.parse( @@ -137,7 +189,8 @@ describe("batchIssue", () => { await expect( batchIssue(inputDirectory, outputDirectory, { schemaPath: path.resolve(__dirname, fixtureFolderName, "schema.json"), - version: "open-attestation/3.0" + version: "open-attestation/3.0", + unwrap: false }) ).rejects.toThrow( expect.objectContaining({ @@ -156,7 +209,8 @@ describe("batchIssue", () => { const merkleRoot = await batchIssue(inputDirectory, outputDirectory, { schemaPath: "https://gist.githubusercontent.com/Nebulis/dd8198ab76443489e14121dad225d351/raw/693b50a1694942fb3cc6a8dcf5187cc7c75adb58/schema.json", - version: "open-attestation/3.0" + version: "open-attestation/3.0", + unwrap: false }); const file = JSON.parse( @@ -175,7 +229,8 @@ describe("batchIssue", () => { batchIssue(inputDirectory, outputDirectory, { schemaPath: "https://gist.githubusercontent.com/Nebulis/dd8198ab76443489e14121dad225d351/raw/693b50a1694942fb3cc6a8dcf5187cc7c75adb58/schema.json", - version: "open-attestation/3.0" + version: "open-attestation/3.0", + unwrap: false }) ).rejects.toThrow( expect.objectContaining({ @@ -190,7 +245,8 @@ describe("batchIssue", () => { await expect( batchIssue(inputDirectory, outputDirectory, { schemaPath: path.resolve(__dirname, fixtureFolderName, "invalid-schema.json"), - version: "open-attestation/3.0" + version: "open-attestation/3.0", + unwrap: false }) ).rejects.toThrow("Invalid schema, you must provide an $id property to your schema"); expect(fs.readdirSync(outputDirectory)).toHaveLength(0); diff --git a/src/__tests__/fixture/wrapped-open-attestation-document.json b/src/__tests__/fixture/wrapped-open-attestation-document.json new file mode 100644 index 00000000..c8fae980 --- /dev/null +++ b/src/__tests__/fixture/wrapped-open-attestation-document.json @@ -0,0 +1,35 @@ +{ + "version": "open-attestation/3.0", + "data": { + "reference": "bb580cf5-87f2-42c8-b0cf-e9d445db336d:string:ABCXXXXX00", + "name": "84f3857b-40f7-41e7-8f71-cb8008fab163:string:Certificate of whatever", + "template": { + "name": "25d8bf44-0470-41cf-8ada-39d72c7364df:string:CUSTOM_TEMPLATE", + "type": "b82ae6ef-1f01-4714-b653-72debeea1839:string:EMBEDDED_RENDERER", + "url": "bfea6de8-db78-46f7-884b-ebbd1339e550:string:http://localhost:3000/rederer" + }, + "validFrom": "b99a84c7-3871-4d33-996a-ff03a221371b:string:2018-08-30T00:00:00+08:00", + "proof": { + "type": "710ee6cf-5cea-41b9-a71b-106ad73460a9:string:OpenAttestationSignature2018", + "method": "07d987ad-97b8-4f9e-b06e-a3691a31ae03:string:TOKEN_REGISTRY", + "value": "509cc6a9-3c7d-4214-be79-d2b5fe4a5731:string:0xb53499ee758352fAdDfCed863d9ac35C809E2F20" + }, + "issuer": { + "id": "84cf6e77-ed24-416d-bf1a-bd2a88058211:string:https://example.com", + "name": "90ded788-2f16-4b11-bfd4-21c58445730e:string:Issuer name", + "identityProof": { + "type": "283b3e84-b0e5-4dcb-a925-0036fa1f78ea:string:DNS-TXT", + "location": "75a891d6-9ba9-4d5e-9877-efe966dcd901:string:some.io" + } + } + }, + "privacy": { + "obfuscatedData": [] + }, + "signature": { + "type": "SHA3MerkleProof", + "targetHash": "25d71e606ca2b56c1a5345101264626c9c7b89f124f21182b27e46ec208690ae", + "proof": [], + "merkleRoot": "25d71e606ca2b56c1a5345101264626c9c7b89f124f21182b27e46ec208690ae" + } +} \ No newline at end of file diff --git a/src/batchIssue.ts b/src/batchIssue.ts index 5c5be13a..d645aa29 100644 --- a/src/batchIssue.ts +++ b/src/batchIssue.ts @@ -1,7 +1,7 @@ import { documentsInDirectory, readCert, writeCertToDisk } from "./diskUtils"; import { dirSync } from "tmp"; import mkdirp from "mkdirp"; -import { isSchemaValidationError, wrapDocument, utils } from "@govtechsg/open-attestation"; +import { isSchemaValidationError, wrapDocument, utils, getData } from "@govtechsg/open-attestation"; import path from "path"; import fetch from "node-fetch"; import Ajv from "ajv"; @@ -21,6 +21,7 @@ export const digestDocument = async ( undigestedCertDir: string, digestedCertDir: string, version: "open-attestation/2.0" | "open-attestation/3.0", + unwrap: boolean, schema?: Schema ): Promise => { const hashArray: Buffer[] = []; @@ -29,9 +30,16 @@ export const digestDocument = async ( if (schema) { compile = new Ajv().compile(schema); } + certFileNames.forEach(file => { - // Read individual document - const document = readCert(undigestedCertDir, file); + let document; + if (unwrap) { + document = getData(readCert(undigestedCertDir, file)); + } else { + // Read individual document + document = readCert(undigestedCertDir, file); + } + // Digest individual document if (compile) { const valid = compile(document); @@ -157,7 +165,7 @@ const loadSchema = (schemaPath?: string): Promise => { export const batchIssue = async ( inputDir: string, outputDir: string, - options: { schemaPath?: string; version: "open-attestation/2.0" | "open-attestation/3.0" } + options: { schemaPath?: string; version: "open-attestation/2.0" | "open-attestation/3.0"; unwrap: boolean } ): Promise => { // Create output dir mkdirp.sync(outputDir); @@ -169,7 +177,13 @@ export const batchIssue = async ( // Phase 1: For each document, read content, digest and write to file const schema = await loadSchema(options.schemaPath); - const individualDocumentHashes = await digestDocument(inputDir, intermediateDir, options.version, schema); + const individualDocumentHashes = await digestDocument( + inputDir, + intermediateDir, + options.version, + options.unwrap, + schema + ); if (!individualDocumentHashes || individualDocumentHashes.length === 0) throw new Error(`No documents found in ${inputDir}`); diff --git a/src/index.ts b/src/index.ts index e816baa3..678a00ad 100755 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ interface BatchCommand { batchedDir: string; schema: any; openAttestationV3: boolean; + unwrap: boolean; } const isBatchCommand = (args: any): args is BatchCommand => { @@ -75,13 +76,17 @@ const parseArguments = (argv: string[]) => alias: "oav3", conflicts: "open-attestation-v2" }) + .option("unwrap", { + alias: "u", + description: "Use if raw directory contains wrapped files" + }) ) .parse(argv); const batch = async ( raw: string, batched: string, - options: { schemaPath?: string; version: "open-attestation/2.0" | "open-attestation/3.0" } + options: { schemaPath?: string; version: "open-attestation/2.0" | "open-attestation/3.0"; unwrap: boolean } ): Promise => { mkdirp.sync(batched); return batchIssue(raw, batched, options).then(merkleRoot => { @@ -115,7 +120,8 @@ const main = async (argv: string[]): Promise => { if (isBatchCommand(args)) { return batch(args.rawDir, args.batchedDir, { schemaPath: args.schema, - version: args.openAttestationV3 ? "open-attestation/3.0" : "open-attestation/2.0" + version: args.openAttestationV3 ? "open-attestation/3.0" : "open-attestation/2.0", + unwrap: args.unwrap }); } else if (isFilterCommand(args)) { return obfuscate(args.source, args.destination, args.fields);