diff --git a/.changeset/popular-rings-count.md b/.changeset/popular-rings-count.md new file mode 100644 index 00000000..04427100 --- /dev/null +++ b/.changeset/popular-rings-count.md @@ -0,0 +1,5 @@ +--- +'@tufjs/models': patch +--- + +Raise error when metadata version is missing diff --git a/packages/models/src/__tests__/base.test.ts b/packages/models/src/__tests__/base.test.ts index 488d6100..199858d4 100644 --- a/packages/models/src/__tests__/base.test.ts +++ b/packages/models/src/__tests__/base.test.ts @@ -12,7 +12,7 @@ describe('Signed', () => { describe('constructor', () => { describe('when called with no arguments', () => { it('constructs an object', () => { - const subject = new DummySigned({}); + const subject = new DummySigned({} as SignedOptions); expect(subject).toBeTruthy(); }); }); @@ -20,7 +20,7 @@ describe('Signed', () => { describe('when spec version is too short', () => { it('constructs an object', () => { expect(() => { - new DummySigned({ specVersion: '1' }); + new DummySigned({ specVersion: '1' } as SignedOptions); }).toThrow(ValueError); }); }); @@ -28,7 +28,7 @@ describe('Signed', () => { describe('when spec version is too long', () => { it('constructs an object', () => { expect(() => { - new DummySigned({ specVersion: '1.0.0.0' }); + new DummySigned({ specVersion: '1.0.0.0' } as SignedOptions); }).toThrow(ValueError); }); }); @@ -36,7 +36,7 @@ describe('Signed', () => { describe('when spec version includes non number', () => { it('constructs an object', () => { expect(() => { - new DummySigned({ specVersion: '1.b.c' }); + new DummySigned({ specVersion: '1.b.c' } as SignedOptions); }).toThrow(ValueError); }); }); @@ -44,7 +44,7 @@ describe('Signed', () => { describe('when spec version is unsupported', () => { it('constructs an object', () => { expect(() => { - new DummySigned({ specVersion: '2.0.0' }); + new DummySigned({ specVersion: '2.0.0' } as SignedOptions); }).toThrow(ValueError); }); }); @@ -53,7 +53,7 @@ describe('Signed', () => { describe('isExpired', () => { const subject = new DummySigned({ expires: '2021-12-18T13:28:12.99008-06:00', - }); + } as SignedOptions); describe('when reference time is not provided', () => { it('returns true', () => { @@ -106,7 +106,9 @@ describe('Signed', () => { }); describe('isExpired', () => { - const subject = new DummySigned({ expires: '1970-01-01T00:00:01.000Z' }); + const subject = new DummySigned({ + expires: '1970-01-01T00:00:01.000Z', + } as SignedOptions); describe('when reference time is not provided', () => { it('returns true', () => { @@ -143,6 +145,17 @@ describe('Signed', () => { }); }); + describe('when the version is not included', () => { + it('throws an error', () => { + expect(() => { + DummySigned.commonFieldsFromJSON({ + ...json, + version: undefined, + } as any); /* eslint-disable-line @typescript-eslint/no-explicit-any */ + }).toThrow(ValueError); + }); + }); + describe('when there is a type error with spec_version', () => { it('throws an error', () => { expect(() => { @@ -151,6 +164,17 @@ describe('Signed', () => { }); }); + describe('when the spec_version is not included', () => { + it('throws an error', () => { + expect(() => { + DummySigned.commonFieldsFromJSON({ + ...json, + spec_version: undefined, + } as any); /* eslint-disable-line @typescript-eslint/no-explicit-any */ + }).toThrow(ValueError); + }); + }); + describe('when there is a type error with expires', () => { it('throws an error', () => { expect(() => { @@ -159,6 +183,17 @@ describe('Signed', () => { }); }); + describe('when the expires is not included', () => { + it('throws an error', () => { + expect(() => { + DummySigned.commonFieldsFromJSON({ + ...json, + expires: undefined, + } as any); /* eslint-disable-line @typescript-eslint/no-explicit-any */ + }).toThrow(ValueError); + }); + }); + describe('when the JSON is valid', () => { it('throws an error', () => { const opts = DummySigned.commonFieldsFromJSON(json); diff --git a/packages/models/src/__tests__/key.test.ts b/packages/models/src/__tests__/key.test.ts index 1f64c6b7..9e9547a6 100644 --- a/packages/models/src/__tests__/key.test.ts +++ b/packages/models/src/__tests__/key.test.ts @@ -1,4 +1,4 @@ -import { Signed } from '../base'; +import { Signed, SignedOptions } from '../base'; import { UnsignedMetadataError } from '../error'; import { Key } from '../key'; import { Signature } from '../signature'; @@ -42,7 +42,7 @@ describe('Key', () => { } } const metadata = { - signed: new DummySigned({}), + signed: new DummySigned({} as SignedOptions), signatures: { [keyID]: signature }, }; @@ -63,7 +63,7 @@ describe('Key', () => { describe('when no signature does NOT match', () => { const badMetadata = { - signed: new DummySigned({}), + signed: new DummySigned({} as SignedOptions), signatures: { [keyID]: new Signature({ keyID: keyID, sig: 'bad' }) }, }; diff --git a/packages/models/src/__tests__/metadata.test.ts b/packages/models/src/__tests__/metadata.test.ts index 3d1a51c8..40abbd0c 100644 --- a/packages/models/src/__tests__/metadata.test.ts +++ b/packages/models/src/__tests__/metadata.test.ts @@ -12,7 +12,10 @@ describe('Metadata', () => { sig: 'BEEF', }); - const subject = new Metadata(new Root({}), { [sig1.keyID]: sig1 }); + const subject = new Metadata( + new Root({ version: 1, specVersion: '1.0.0', expires: '' }), + { [sig1.keyID]: sig1 } + ); describe('when appending a signature', () => { const append = true; diff --git a/packages/models/src/__tests__/root.test.ts b/packages/models/src/__tests__/root.test.ts index 9a78f918..541fd6b2 100644 --- a/packages/models/src/__tests__/root.test.ts +++ b/packages/models/src/__tests__/root.test.ts @@ -94,7 +94,7 @@ describe('Root', () => { scheme: 'ed25519', }); - const root = new Root({}); + const root = new Root({ version: 1, specVersion: '1.0.0', expires: '' }); describe('when called with a valid role', () => { it('adds the key', () => { diff --git a/packages/models/src/__tests__/targets.test.ts b/packages/models/src/__tests__/targets.test.ts index 33923551..09b94dfb 100644 --- a/packages/models/src/__tests__/targets.test.ts +++ b/packages/models/src/__tests__/targets.test.ts @@ -64,7 +64,11 @@ describe('Targets', () => { hashes: { sha256: 'abc' }, }); - const targets = new Targets({}); + const targets = new Targets({ + version: 1, + specVersion: '1.0.0', + expires: '', + }); it('adds a target', () => { targets.addTarget(targetFile); diff --git a/packages/models/src/base.ts b/packages/models/src/base.ts index 65a8c98e..76c14161 100644 --- a/packages/models/src/base.ts +++ b/packages/models/src/base.ts @@ -11,9 +11,9 @@ export interface Signable { } export interface SignedOptions { - version?: number; - specVersion?: string; - expires?: string; + version: number; + specVersion: string; + expires: string; unrecognizedFields?: Record; } @@ -60,8 +60,8 @@ export abstract class Signed { throw new ValueError('Unsupported specVersion'); } - this.expires = options.expires || new Date().toISOString(); - this.version = options.version || 1; + this.expires = options.expires; + this.version = options.version; this.unrecognizedFields = options.unrecognizedFields || {}; } @@ -88,15 +88,21 @@ export abstract class Signed { public static commonFieldsFromJSON(data: JSONObject): SignedOptions { const { spec_version, expires, version, ...rest } = data; - if (guard.isDefined(spec_version) && !(typeof spec_version === 'string')) { + if (!guard.isDefined(spec_version)) { + throw new ValueError('spec_version is not defined'); + } else if (typeof spec_version !== 'string') { throw new TypeError('spec_version must be a string'); } - if (guard.isDefined(expires) && !(typeof expires === 'string')) { + if (!guard.isDefined(expires)) { + throw new ValueError('expires is not defined'); + } else if (!(typeof expires === 'string')) { throw new TypeError('expires must be a string'); } - if (guard.isDefined(version) && !(typeof version === 'number')) { + if (!guard.isDefined(version)) { + throw new ValueError('version is not defined'); + } else if (!(typeof version === 'number')) { throw new TypeError('version must be a number'); } diff --git a/packages/repo-mock/src/metadata.ts b/packages/repo-mock/src/metadata.ts index 17fc148f..7b0a569d 100644 --- a/packages/repo-mock/src/metadata.ts +++ b/packages/repo-mock/src/metadata.ts @@ -16,6 +16,8 @@ export function createTargetsMeta( ): Metadata { const targets = new Metadata( new Targets({ + version: 1, + specVersion: '1.0.0', expires: getExpires(), }) ); @@ -33,6 +35,8 @@ export function createSnapshotMeta( ): Metadata { const snapshot = new Metadata( new Snapshot({ + version: 1, + specVersion: '1.0.0', expires: getExpires(), meta: { 'targets.json': toMetaFile(targets) }, }) @@ -48,6 +52,8 @@ export function createTimestampMeta( ): Metadata { const timestamp = new Metadata( new Timestamp({ + version: 1, + specVersion: '1.0.0', expires: getExpires(), snapshotMeta: toMetaFile(snapshot), }) @@ -61,6 +67,8 @@ export function createTimestampMeta( export function createRootMeta(keyPair: KeyPair): Metadata { const root = new Metadata( new Root({ + version: 1, + specVersion: '1.0.0', expires: getExpires(), consistentSnapshot: false, })