From d0013cc2c265feece3446458f7536e193aaa6898 Mon Sep 17 00:00:00 2001 From: Henry Jewell Date: Wed, 10 Jul 2024 18:05:19 +0100 Subject: [PATCH] implement validators for key data types re AB#9604 --- logverification/validation.go | 62 +++++++ logverification/validation_test.go | 250 +++++++++++++++++++++++++++++ 2 files changed, 312 insertions(+) create mode 100644 logverification/validation.go create mode 100644 logverification/validation_test.go diff --git a/logverification/validation.go b/logverification/validation.go new file mode 100644 index 0000000..0276d20 --- /dev/null +++ b/logverification/validation.go @@ -0,0 +1,62 @@ +package logverification + +import "errors" + +// Note: We need this logic to detect incomplete JSON unmarshalled into these types. This should +// eventually be replaced by JSON Schema validation. We believe its a problem to solve for the +// entire go codebase through generation. We already describe the structs with json annotations and +// typing information. We don't want to half-cook that solution, as JSON type bugs have bitten us +// before. + +var ( + ErrNonEmptyEventIDRequired = errors.New("event identity field is required and must be non-empty") + ErrNonEmptyTenantIDRequired = errors.New("tenant identity field is required and must be non-empty") + ErrCommitEntryRequired = errors.New("merkle log commit field is required") + ErrIdTimestampRequired = errors.New("idtimestamp field is required and must be non-empty") +) + +// Validate performs basic validation on the VerifiableEvent, ensuring that critical fields +// are present. +func (e *VerifiableEvent) Validate() error { + if e.EventID == "" { + return ErrNonEmptyEventIDRequired + } + + if e.TenantID == "" { + return ErrNonEmptyTenantIDRequired + } + + if e.MerkleLog == nil || e.MerkleLog.Commit == nil { + return ErrCommitEntryRequired + } + + if e.MerkleLog.Commit.Idtimestamp == "" { + return ErrIdTimestampRequired + } + + return nil +} + +// Validate performs basic validation on the DecodedEvent, ensuring that critical fields +// are present for verification purposes. +func (e *DecodedEvent) Validate() error { + if e.V3Event.Identity == "" { + return ErrNonEmptyEventIDRequired + } + + if e.V3Event.TenantIdentity == "" { + return ErrNonEmptyTenantIDRequired + } + + if e.MerkleLog == nil || e.MerkleLog.Commit == nil { + return ErrCommitEntryRequired + } + + if e.MerkleLog.Commit.Idtimestamp == "" { + return ErrIdTimestampRequired + } + + // TODO: Validate other necessary V3 fields. + + return nil +} diff --git a/logverification/validation_test.go b/logverification/validation_test.go new file mode 100644 index 0000000..c524577 --- /dev/null +++ b/logverification/validation_test.go @@ -0,0 +1,250 @@ +package logverification + +import ( + "testing" + + "github.com/datatrails/go-datatrails-common-api-gen/assets/v2/assets" + "github.com/datatrails/go-datatrails-simplehash/simplehash" + "github.com/stretchr/testify/assert" +) + +func TestVerifiableEvent_Validate(t *testing.T) { + type fields struct { + EventID string + TenantID string + LeafHash []byte + MerkleLog *assets.MerkleLogEntry + } + + tests := []struct { + name string + fields fields + expectedErr error + }{ + { + name: "valid input returns no error", + fields: fields{ + EventID: "event/7189fa3d-9af1-40b1-975c-70f792142a82", + TenantID: "tenant/7189fa3d-9af1-40b1-975c-70f792142a82", + LeafHash: []byte("2091f2349925f93546c54d4140cdca5ab59213ef82daf665d81637260d022069"), + MerkleLog: &assets.MerkleLogEntry{ + Commit: &assets.MerkleLogCommit{ + Index: uint64(0), + Idtimestamp: "018fa97ef269039b00", + }, + }, + }, + expectedErr: nil, + }, + { + name: "missing event identity returns specific error", + fields: fields{ + EventID: "", + TenantID: "tenant/7189fa3d-9af1-40b1-975c-70f792142a82", + LeafHash: []byte("2091f2349925f93546c54d4140cdca5ab59213ef82daf665d81637260d022069"), + MerkleLog: &assets.MerkleLogEntry{ + Commit: &assets.MerkleLogCommit{ + Index: uint64(0), + Idtimestamp: "018fa97ef269039b00", + }, + }, + }, + expectedErr: ErrNonEmptyEventIDRequired, + }, + { + name: "missing tenant identity returns specific error", + fields: fields{ + EventID: "event/7189fa3d-9af1-40b1-975c-70f792142a82", + TenantID: "", + LeafHash: []byte("2091f2349925f93546c54d4140cdca5ab59213ef82daf665d81637260d022069"), + MerkleLog: &assets.MerkleLogEntry{ + Commit: &assets.MerkleLogCommit{ + Index: uint64(0), + Idtimestamp: "018fa97ef269039b00", + }, + }, + }, + expectedErr: ErrNonEmptyTenantIDRequired, + }, + { + name: "missing commit entry returns specific error", + fields: fields{ + EventID: "event/7189fa3d-9af1-40b1-975c-70f792142a82", + TenantID: "tenant/7189fa3d-9af1-40b1-975c-70f792142a82", + LeafHash: []byte("2091f2349925f93546c54d4140cdca5ab59213ef82daf665d81637260d022069"), + MerkleLog: &assets.MerkleLogEntry{}, + }, + expectedErr: ErrCommitEntryRequired, + }, + { + name: "missing idtimestamp returns specific error", + fields: fields{ + EventID: "event/7189fa3d-9af1-40b1-975c-70f792142a82", + TenantID: "tenant/7189fa3d-9af1-40b1-975c-70f792142a82", + LeafHash: []byte("2091f2349925f93546c54d4140cdca5ab59213ef82daf665d81637260d022069"), + MerkleLog: &assets.MerkleLogEntry{ + Commit: &assets.MerkleLogCommit{ + Index: uint64(0), + Idtimestamp: "", + }, + }, + }, + expectedErr: ErrIdTimestampRequired, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := &VerifiableEvent{ + EventID: tt.fields.EventID, + TenantID: tt.fields.TenantID, + LeafHash: tt.fields.LeafHash, + MerkleLog: tt.fields.MerkleLog, + } + + err := e.Validate() + assert.ErrorIs(t, err, tt.expectedErr) + }) + } +} + +func TestDecodedEvent_Validate(t *testing.T) { + testPrincipal := map[string]any{ + "issuer": "https://.soak.stage.datatrails.ai/appidpv1", + "subject": "e96dfa33-b645-4b83-a041-e87ac426c089", + "display_name": "test", + "email": "test@test.datatrails.ai", + } + + type fields struct { + V3Event simplehash.V3Event + MerkleLog *assets.MerkleLogEntry + } + + tests := []struct { + name string + fields fields + expectedErr error + }{ + { + name: "valid input returns no error", + fields: fields{ + V3Event: simplehash.V3Event{ + Identity: "event/7189fa3d-9af1-40b1-975c-70f792142a82", + TenantIdentity: "tenant/7189fa3d-9af1-40b1-975c-70f792142a82", + Operation: "NewAsset", + Behaviour: "AssetCreator", + TimestampDeclared: "2024-03-14T23:24:50Z", + TimestampAccepted: "2024-03-14T23:24:50Z", + TimestampCommitted: "2024-03-22T11:13:55.557Z", + PrincipalDeclared: testPrincipal, + PrincipalAccepted: testPrincipal, + }, + MerkleLog: &assets.MerkleLogEntry{ + Commit: &assets.MerkleLogCommit{ + Index: uint64(0), + Idtimestamp: "018fa97ef269039b00", + }, + }, + }, + expectedErr: nil, + }, + { + name: "missing event identity returns specific error", + fields: fields{ + V3Event: simplehash.V3Event{ + Identity: "", + TenantIdentity: "tenant/7189fa3d-9af1-40b1-975c-70f792142a82", + Operation: "NewAsset", + Behaviour: "AssetCreator", + TimestampDeclared: "2024-03-14T23:24:50Z", + TimestampAccepted: "2024-03-14T23:24:50Z", + TimestampCommitted: "2024-03-22T11:13:55.557Z", + PrincipalDeclared: testPrincipal, + PrincipalAccepted: testPrincipal, + }, + MerkleLog: &assets.MerkleLogEntry{ + Commit: &assets.MerkleLogCommit{ + Index: uint64(0), + Idtimestamp: "018fa97ef269039b00", + }, + }, + }, + expectedErr: ErrNonEmptyEventIDRequired, + }, + { + name: "missing tenant identity returns specific error", + fields: fields{ + V3Event: simplehash.V3Event{ + Identity: "event/7189fa3d-9af1-40b1-975c-70f792142a82", + TenantIdentity: "", + Operation: "NewAsset", + Behaviour: "AssetCreator", + TimestampDeclared: "2024-03-14T23:24:50Z", + TimestampAccepted: "2024-03-14T23:24:50Z", + TimestampCommitted: "2024-03-22T11:13:55.557Z", + PrincipalDeclared: testPrincipal, + PrincipalAccepted: testPrincipal, + }, + MerkleLog: &assets.MerkleLogEntry{ + Commit: &assets.MerkleLogCommit{ + Index: uint64(0), + Idtimestamp: "018fa97ef269039b00", + }, + }, + }, + expectedErr: ErrNonEmptyTenantIDRequired, + }, + { + name: "missing commit entry returns specific error", + fields: fields{ + V3Event: simplehash.V3Event{ + Identity: "event/7189fa3d-9af1-40b1-975c-70f792142a82", + TenantIdentity: "tenant/7189fa3d-9af1-40b1-975c-70f792142a82", + Operation: "NewAsset", + Behaviour: "AssetCreator", + TimestampDeclared: "2024-03-14T23:24:50Z", + TimestampAccepted: "2024-03-14T23:24:50Z", + TimestampCommitted: "2024-03-22T11:13:55.557Z", + PrincipalDeclared: testPrincipal, + PrincipalAccepted: testPrincipal, + }, + MerkleLog: &assets.MerkleLogEntry{}, + }, + expectedErr: ErrCommitEntryRequired, + }, + { + name: "missing idtimestamp returns specific error", + fields: fields{ + V3Event: simplehash.V3Event{ + Identity: "event/7189fa3d-9af1-40b1-975c-70f792142a82", + TenantIdentity: "tenant/7189fa3d-9af1-40b1-975c-70f792142a82", + Operation: "NewAsset", + Behaviour: "AssetCreator", + TimestampDeclared: "2024-03-14T23:24:50Z", + TimestampAccepted: "2024-03-14T23:24:50Z", + TimestampCommitted: "2024-03-22T11:13:55.557Z", + PrincipalDeclared: testPrincipal, + PrincipalAccepted: testPrincipal, + }, + MerkleLog: &assets.MerkleLogEntry{ + Commit: &assets.MerkleLogCommit{ + Index: uint64(0), + Idtimestamp: "", + }, + }, + }, + expectedErr: ErrIdTimestampRequired, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := &DecodedEvent{ + V3Event: tt.fields.V3Event, + MerkleLog: tt.fields.MerkleLog, + } + + err := e.Validate() + assert.ErrorIs(t, err, tt.expectedErr) + }) + } +}