diff --git a/sdk/assertion.go b/sdk/assertion.go index 4191c84404..33c96a2b31 100644 --- a/sdk/assertion.go +++ b/sdk/assertion.go @@ -123,6 +123,41 @@ func (a Assertion) GetHash() ([]byte, error) { return ocrypto.SHA256AsHex(transformedJSON), nil } +func (s *Statement) UnmarshalJSON(data []byte) error { + // Define a custom struct for deserialization + type Alias Statement + aux := &struct { + Value json.RawMessage `json:"value,omitempty"` + *Alias + }{ + Alias: (*Alias)(s), + } + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + // Attempt to decode Value as an object + var temp map[string]interface{} + if json.Unmarshal(aux.Value, &temp) == nil { + // Re-encode the object as a string and assign to Value + objAsString, err := json.Marshal(temp) + if err != nil { + return err + } + s.Value = string(objAsString) + } else { + // Assign raw string to Value + var str string + if err := json.Unmarshal(aux.Value, &str); err != nil { + return fmt.Errorf("value is neither a valid JSON object nor a string: %s", string(aux.Value)) + } + s.Value = str + } + + return nil +} + // Statement includes information applying to the scope of the assertion. // It could contain rights, handling instructions, or general metadata. type Statement struct { diff --git a/sdk/assertion_test.go b/sdk/assertion_test.go index f7da55a7cd..9b8ed1722b 100644 --- a/sdk/assertion_test.go +++ b/sdk/assertion_test.go @@ -1,8 +1,10 @@ package sdk import ( + "encoding/json" "testing" + "github.com/gowebpki/jcs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -33,3 +35,150 @@ func TestTDFWithAssertion(t *testing.T) { assert.Equal(t, "4a447a13c5a32730d20bdf7feecb9ffe16649bc731914b574d80035a3927f860", string(hashOfAssertion)) } + +func TestTDFWithAssertionJsonObject(t *testing.T) { + // Define the assertion config with a JSON object in the statement value + value := `{ + "ocl": { + "pol": "2ccf11cb-6c9a-4e49-9746-a7f0a295945d", + "cls": "SECRET", + "catl": [ + { + "type": "P", + "name": "Releasable To", + "vals": ["usa"] + } + ], + "dcr": "2024-12-17T13:00:52Z" + }, + "context": { + "@base": "urn:nato:stanag:5636:A:1:elements:json" + } + }` + assertionConfig := AssertionConfig{ + ID: "ab43266781e64b51a4c52ffc44d6152c", + Type: "handling", + Scope: "payload", + AppliesToState: "", // Use "" or a pointer to a string if necessary + Statement: Statement{ + Format: "json-structured", + Value: value, + }, + } + + // Set up the assertion + assertion := Assertion{ + ID: assertionConfig.ID, + Type: assertionConfig.Type, + Scope: assertionConfig.Scope, + AppliesToState: assertionConfig.AppliesToState, + Statement: assertionConfig.Statement, + } + + var obj map[string]interface{} + err := json.Unmarshal([]byte(assertionConfig.Statement.Value), &obj) + require.NoError(t, err, "Unmarshaling the Value into a map should succeed") + + ocl, ok := obj["ocl"].(map[string]interface{}) + require.True(t, ok, "Parsed Value should contain 'ocl' as an object") + require.Equal(t, "SECRET", ocl["cls"], "'cls' field should match") + require.Equal(t, "2ccf11cb-6c9a-4e49-9746-a7f0a295945d", ocl["pol"], "'pol' field should match") + + context, ok := obj["context"].(map[string]interface{}) + require.True(t, ok, "Parsed Value should contain 'context' as an object") + require.Equal(t, "urn:nato:stanag:5636:A:1:elements:json", context["@base"], "'@base' field should match") + + // Calculate the hash of the assertion + hashOfAssertion, err := assertion.GetHash() + require.NoError(t, err) + + expectedHash := "722dd40a90a0f7ec718fb156207a647e64daa43c0ae1f033033473a172c72aee" + assert.Equal(t, expectedHash, string(hashOfAssertion)) +} + +func TestDeserializingAssertionWithJSONInStatementValue(t *testing.T) { + // the assertion has a JSON object in the statement value + assertionVal := ` { + "id": "bacbe31eab384df39d35a5fbe83778de", + "type": "handling", + "scope": "tdo", + "appliesToState": null, + "statement": { + "format": "json-structured", + "value": { + "ocl": { + "pol": "2ccf11cb-6c9a-4e49-9746-a7f0a295945d", + "cls": "SECRET", + "catl": [ + { + "type": "P", + "name": "Releasable To", + "vals": [ + "usa" + ] + } + ], + "dcr": "2024-12-17T13:00:52Z" + }, + "context": { + "@base": "urn:nato:stanag:5636:A:1:elements:json" + } + } + }, + "binding": { + "method": "jws", + "signature": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJDb25maWRlbnRpYWxpdHlJbmZvcm1hdGlvbiI6InsgXCJvY2xcIjogeyBcInBvbFwiOiBcIjJjY2YxMWNiLTZjOWEtNGU0OS05NzQ2LWE3ZjBhMjk1OTQ1ZFwiLCBcImNsc1wiOiBcIlNFQ1JFVFwiLCBcImNhdGxcIjogWyB7IFwidHlwZVwiOiBcIlBcIiwgXCJuYW1lXCI6IFwiUmVsZWFzYWJsZSBUb1wiLCBcInZhbHNcIjogWyBcInVzYVwiIF0gfSBdLCBcImRjclwiOiBcIjIwMjQtMTItMTdUMTM6MDA6NTJaXCIgfSwgXCJjb250ZXh0XCI6IHsgXCJAYmFzZVwiOiBcInVybjpuYXRvOnN0YW5hZzo1NjM2OkE6MTplbGVtZW50czpqc29uXCIgfSB9In0.LlOzRLKKXMAqXDNsx9Ha5915CGcAkNLuBfI7jJmx6CnfQrLXhlRHWW3_aLv5DPsKQC6vh9gDQBH19o7q7EcukvK4IabA4l0oP8ePgHORaajyj7ONjoeudv_zQ9XN7xU447S3QznzOoasuWAFoN4682Fhf99Kjl6rhDCzmZhTwQw9drP7s41nNA5SwgEhoZj-X9KkNW5GbWjA95eb8uVRRWk8dOnVje6j8mlJuOtKdhMxQ8N5n0vBYYhiss9c4XervBjWAxwAMdbRaQN0iPZtMzIkxKLYxBZDvTnYSAqzpvfGPzkSI-Ze_hUZs2hp-ADNnYUJBf_LzFmKyqHjPSFQ7A" + } + }` + + var assertion Assertion + err := json.Unmarshal([]byte(assertionVal), &assertion) + require.NoError(t, err, "Error deserializing the assertion with a JSON object in the statement value") + + var expectedAssertionValue, _ = jcs.Transform([]byte(`{ + "ocl": { + "pol": "2ccf11cb-6c9a-4e49-9746-a7f0a295945d", + "cls": "SECRET", + "catl": [ + { + "type": "P", + "name": "Releasable To", + "vals": [ + "usa" + ] + } + ], + "dcr": "2024-12-17T13:00:52Z" + }, + "context": { + "@base": "urn:nato:stanag:5636:A:1:elements:json" + } + }`)) + actualAssertionValue, err := jcs.Transform([]byte(assertion.Statement.Value)) + require.NoError(t, err, "Error transforming the assertion statement value") + assert.Equal(t, expectedAssertionValue, actualAssertionValue) +} + +func TestDeserializingAssertionWithStringInStatementValue(t *testing.T) { + // the assertion has a JSON object in the statement value + assertionVal := ` { + "id": "bacbe31eab384df39d35a5fbe83778de", + "type": "handling", + "scope": "tdo", + "appliesToState": null, + "statement": { + "format": "json-structured", + "value": "this is a value" + }, + "binding": { + "method": "jws", + "signature": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJDb25maWRlbnRpYWxpdHlJbmZvcm1hdGlvbiI6InsgXCJvY2xcIjogeyBcInBvbFwiOiBcIjJjY2YxMWNiLTZjOWEtNGU0OS05NzQ2LWE3ZjBhMjk1OTQ1ZFwiLCBcImNsc1wiOiBcIlNFQ1JFVFwiLCBcImNhdGxcIjogWyB7IFwidHlwZVwiOiBcIlBcIiwgXCJuYW1lXCI6IFwiUmVsZWFzYWJsZSBUb1wiLCBcInZhbHNcIjogWyBcInVzYVwiIF0gfSBdLCBcImRjclwiOiBcIjIwMjQtMTItMTdUMTM6MDA6NTJaXCIgfSwgXCJjb250ZXh0XCI6IHsgXCJAYmFzZVwiOiBcInVybjpuYXRvOnN0YW5hZzo1NjM2OkE6MTplbGVtZW50czpqc29uXCIgfSB9In0.LlOzRLKKXMAqXDNsx9Ha5915CGcAkNLuBfI7jJmx6CnfQrLXhlRHWW3_aLv5DPsKQC6vh9gDQBH19o7q7EcukvK4IabA4l0oP8ePgHORaajyj7ONjoeudv_zQ9XN7xU447S3QznzOoasuWAFoN4682Fhf99Kjl6rhDCzmZhTwQw9drP7s41nNA5SwgEhoZj-X9KkNW5GbWjA95eb8uVRRWk8dOnVje6j8mlJuOtKdhMxQ8N5n0vBYYhiss9c4XervBjWAxwAMdbRaQN0iPZtMzIkxKLYxBZDvTnYSAqzpvfGPzkSI-Ze_hUZs2hp-ADNnYUJBf_LzFmKyqHjPSFQ7A" + } + }` + + var assertion Assertion + err := json.Unmarshal([]byte(assertionVal), &assertion) + require.NoError(t, err, "Error deserializing the assertion with a JSON object in the statement value") + + assert.Equal(t, "this is a value", assertion.Statement.Value) +}