diff --git a/auth/contract/contract_test.go b/auth/contract/contract_test.go index 3280ccbc72..a4009aeac5 100644 --- a/auth/contract/contract_test.go +++ b/auth/contract/contract_test.go @@ -25,6 +25,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/goodsign/monday" "github.com/stretchr/testify/assert" ) @@ -214,6 +216,19 @@ func TestContract_Verify(t *testing.T) { } func TestParseContractString(t *testing.T) { + t.Run("ok", func(t *testing.T) { + // note that the contract text contains an ampersand (&) to test if it is properly url encoded + rawText := "NL:BehandelaarLogin:v1 Ondergetekende geeft toestemming aan Demo EHR om namens Toon & Zoonen en ondergetekende het Nuts netwerk te bevragen. Deze toestemming is geldig van dinsdag, 1 oktober 2019 13:30:42 tot dinsdag, 1 oktober 2019 14:30:42." + signedContract, err := ParseContractString(rawText, StandardContractTemplates) + + require.NoError(t, err) + require.NotNil(t, signedContract) + assert.Equal(t, "Toon & Zoonen", signedContract.Params["legal_entity"]) + assert.Equal(t, "dinsdag, 1 oktober 2019 13:30:42", signedContract.Params["valid_from"]) + assert.Equal(t, "dinsdag, 1 oktober 2019 14:30:42", signedContract.Params["valid_to"]) + assert.Equal(t, "Demo EHR", signedContract.Params["acting_party"]) + }) + t.Run("Missing legalEntity returns error", func(t *testing.T) { rawText := "NL:BehandelaarLogin:v1 Ondergetekende geeft toestemming aan Demo EHR om namens en ondergetekende het Nuts netwerk te bevragen. Deze toestemming is geldig van dinsdag, 1 oktober 2019 13:30:42 tot dinsdag, 1 oktober 2019 14:30:42." signedContract, err := ParseContractString(rawText, StandardContractTemplates) diff --git a/auth/contract/store.go b/auth/contract/store.go index da85c4b9ac..dc7be36a31 100644 --- a/auth/contract/store.go +++ b/auth/contract/store.go @@ -34,7 +34,7 @@ var StandardContractTemplates = TemplateStore{ Language: "NL", Locale: "nl_NL", SignerAttributes: []string{".nuts.agb.agbcode"}, - Template: `NL:BehandelaarLogin:v1 Ondergetekende geeft toestemming aan {{` + ActingPartyAttr + `}} om namens {{` + LegalEntityAttr + `}} en ondergetekende het Nuts netwerk te bevragen. Deze toestemming is geldig van {{` + ValidFromAttr + `}} tot {{` + ValidToAttr + `}}.`, + Template: `NL:BehandelaarLogin:v1 Ondergetekende geeft toestemming aan {{{` + ActingPartyAttr + `}}} om namens {{{` + LegalEntityAttr + `}}} en ondergetekende het Nuts netwerk te bevragen. Deze toestemming is geldig van {{` + ValidFromAttr + `}} tot {{` + ValidToAttr + `}}.`, TemplateAttributes: []string{ActingPartyAttr, LegalEntityAttr, ValidFromAttr, ValidToAttr}, Regexp: `NL:BehandelaarLogin:v1 Ondergetekende geeft toestemming aan (.+) om namens (.+) en ondergetekende het Nuts netwerk te bevragen. Deze toestemming is geldig van (.+) tot (.+).`, }, @@ -44,7 +44,7 @@ var StandardContractTemplates = TemplateStore{ Language: "NL", Locale: "nl_NL", SignerAttributes: StandardSignerAttributes, - Template: `NL:BehandelaarLogin:v3 Hierbij verklaar ik te handelen in naam van {{` + LegalEntityAttr + `}} te {{` + LegalEntityCityAttr + `}}. Deze verklaring is geldig van {{` + ValidFromAttr + `}} tot {{` + ValidToAttr + `}}.`, + Template: `NL:BehandelaarLogin:v3 Hierbij verklaar ik te handelen in naam van {{{` + LegalEntityAttr + `}}} te {{{` + LegalEntityCityAttr + `}}}. Deze verklaring is geldig van {{` + ValidFromAttr + `}} tot {{` + ValidToAttr + `}}.`, TemplateAttributes: []string{LegalEntityAttr, LegalEntityCityAttr, ValidFromAttr, ValidToAttr}, Regexp: `NL:BehandelaarLogin:v3 Hierbij verklaar ik te handelen in naam van (.+) te (.+). Deze verklaring is geldig van (.+) tot (.+).`, }, @@ -56,7 +56,7 @@ var StandardContractTemplates = TemplateStore{ Language: "EN", Locale: "en_US", SignerAttributes: StandardSignerAttributes, - Template: `EN:PractitionerLogin:v3 I hereby declare to act on behalf of {{` + LegalEntityAttr + `}} located in {{` + LegalEntityCityAttr + `}}. This declaration is valid from {{` + ValidFromAttr + `}} until {{` + ValidToAttr + `}}.`, + Template: `EN:PractitionerLogin:v3 I hereby declare to act on behalf of {{{` + LegalEntityAttr + `}}} located in {{{` + LegalEntityCityAttr + `}}}. This declaration is valid from {{` + ValidFromAttr + `}} until {{` + ValidToAttr + `}}.`, TemplateAttributes: []string{LegalEntityAttr, LegalEntityCityAttr, ValidFromAttr, ValidToAttr}, Regexp: `EN:PractitionerLogin:v3 I hereby declare to act on behalf of (.+) located in (.+). This declaration is valid from (.+) until (.+).`, }, diff --git a/auth/contract/template_test.go b/auth/contract/template_test.go index 169b27558d..17c01ea40a 100644 --- a/auth/contract/template_test.go +++ b/auth/contract/template_test.go @@ -21,28 +21,88 @@ package contract import ( "fmt" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "testing" "time" "github.com/goodsign/monday" ) +func TestContract_StandardTemplates(t *testing.T) { + t.Run("v1", func(t *testing.T) { + attrs := map[string]string{ + LegalEntityAttr: "Zorg & Zo", + ActingPartyAttr: "Alpha & Beta", + } + t.Run("NL", func(t *testing.T) { + tpl := StandardContractTemplates.Get("BehandelaarLogin", "NL", "v1") + require.NotNil(t, tpl) + + actual, err := tpl.Render(attrs, time.Date(2020, 1, 1, 1, 1, 1, 0, time.UTC), time.Hour) + + require.NoError(t, err) + assert.Equal(t, "NL:BehandelaarLogin:v1 Ondergetekende geeft toestemming aan Alpha & Beta om namens Zorg & Zo en ondergetekende het Nuts netwerk te bevragen. Deze toestemming is geldig van woensdag, 1 januari 2020 02:01:01 tot woensdag, 1 januari 2020 03:01:01.", actual.RawContractText) + }) + }) + t.Run("v3", func(t *testing.T) { + attrs := map[string]string{ + LegalEntityAttr: "Zorg & Zo", + LegalEntityCityAttr: "A & B", + } + t.Run("NL", func(t *testing.T) { + tpl := StandardContractTemplates.Get("BehandelaarLogin", "NL", "v3") + require.NotNil(t, tpl) + + actual, err := tpl.Render(attrs, time.Date(2020, 1, 1, 1, 1, 1, 0, time.UTC), time.Hour) + + require.NoError(t, err) + assert.Equal(t, "NL:BehandelaarLogin:v3 Hierbij verklaar ik te handelen in naam van Zorg & Zo te A & B. Deze verklaring is geldig van woensdag, 1 januari 2020 02:01:01 tot woensdag, 1 januari 2020 03:01:01.", actual.RawContractText) + }) + t.Run("EN", func(t *testing.T) { + tpl := StandardContractTemplates.Get("PractitionerLogin", "EN", "v3") + require.NotNil(t, tpl) + + actual, err := tpl.Render(attrs, time.Date(2020, 1, 1, 1, 1, 1, 0, time.UTC), time.Hour) + + require.NoError(t, err) + assert.Equal(t, "EN:PractitionerLogin:v3 I hereby declare to act on behalf of Zorg & Zo located in A & B. This declaration is valid from Wednesday, 1 January 2020 02:01:01 until Wednesday, 1 January 2020 03:01:01.", actual.RawContractText) + }) + }) +} + func TestContract_RenderTemplate(t *testing.T) { - template := &Template{Type: "Simple", Template: "ga je akkoord met {{wat}} van {{valid_from}} tot {{valid_to}}?", Locale: "nl_NL"} - now := time.Now() - result, err := template.Render(map[string]string{"wat": "alles"}, now, 60*time.Minute) - if !assert.NoError(t, err) { - return - } - amsterdamLocation, _ := time.LoadLocation(AmsterdamTimeZone) - - from := monday.Format(now.In(amsterdamLocation).Add(0), timeLayout, monday.LocaleNlNL) - to := monday.Format(now.In(amsterdamLocation).Add(60*time.Minute), timeLayout, monday.LocaleNlNL) - - expected := fmt.Sprintf("ga je akkoord met alles van %s tot %s?", from, to) - if result.RawContractText != expected { - t.Errorf("Error while rendering the Template: got '%v', expected '%v'", result, expected) - } + t.Run("ok", func(t *testing.T) { + template := &Template{Type: "Simple", Template: "ga je akkoord met {{wat}} van {{valid_from}} tot {{valid_to}}?", Locale: "nl_NL"} + now := time.Now() + result, err := template.Render(map[string]string{"wat": "alles"}, now, 60*time.Minute) + if !assert.NoError(t, err) { + return + } + amsterdamLocation, _ := time.LoadLocation(AmsterdamTimeZone) + + from := monday.Format(now.In(amsterdamLocation).Add(0), timeLayout, monday.LocaleNlNL) + to := monday.Format(now.In(amsterdamLocation).Add(60*time.Minute), timeLayout, monday.LocaleNlNL) + + expected := fmt.Sprintf("ga je akkoord met alles van %s tot %s?", from, to) + if result.RawContractText != expected { + t.Errorf("Error while rendering the Template: got '%v', expected '%v'", result, expected) + } + }) + t.Run("with ampersand (triple escape)", func(t *testing.T) { + template := &Template{Type: "Simple", Template: "ampersand & in text and message {{{wat}}} van {{valid_from}} tot {{valid_to}}?", Locale: "nl_NL"} + now := time.Now() + result, err := template.Render(map[string]string{"wat": "&"}, now, 60*time.Minute) + if !assert.NoError(t, err) { + return + } + amsterdamLocation, _ := time.LoadLocation(AmsterdamTimeZone) + + from := monday.Format(now.In(amsterdamLocation).Add(0), timeLayout, monday.LocaleNlNL) + to := monday.Format(now.In(amsterdamLocation).Add(60*time.Minute), timeLayout, monday.LocaleNlNL) + + expected := fmt.Sprintf("ampersand & in text and message & van %s tot %s?", from, to) + assert.Equal(t, expected, result.RawContractText) + }) } func TestParseTime(t *testing.T) { diff --git a/docs/pages/release_notes.rst b/docs/pages/release_notes.rst index 76bd59b6ac..bac3e785d8 100644 --- a/docs/pages/release_notes.rst +++ b/docs/pages/release_notes.rst @@ -5,6 +5,19 @@ Release notes What has been changed, and how to update between versions. +*********************** +Coconut update (v5.0.10) +*********************** + +Release date: 2023-03-01 + +This patch release fixes the following: + +- Drawing up an IRMA contract with an ampersand in the organization name causes the ampersand to be URL encoded, + causing validation of the signed contract to fail. + +**Full Changelog**: https://github.com/nuts-foundation/nuts-node/compare/v5.0.9...v5.0.10 + *********************** Coconut update (v5.0.9) ***********************