Skip to content

Commit

Permalink
MLPAB-2318: Support ATTORNEY_OPT_OUT (#241)
Browse files Browse the repository at this point in the history
  • Loading branch information
acsauk authored Aug 12, 2024
1 parent aa19c96 commit daa60de
Show file tree
Hide file tree
Showing 14 changed files with 515 additions and 7 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ repos:
rev: v8.18.4
hooks:
- id: gitleaks
args: [ "--baseline-path", "./gitleaks-report.json" ]
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ test-api:
cat ./docs/example-lpa.json | ./api-test/tester -expectedStatus=201 REQUEST PUT $(URL)/lpas/$(LPA_UID) "`xargs -0`"
cat ./docs/certificate-provider-opt-out.json | ./api-test/tester -expectedStatus=201 REQUEST POST $(URL)/lpas/$(LPA_UID)/updates "`xargs -0`"

# attorney opt out
$(eval LPA_UID := "$(shell ./api-test/tester UID)")
cat ./docs/example-lpa.json | ./api-test/tester -expectedStatus=201 REQUEST PUT $(URL)/lpas/$(LPA_UID) "`xargs -0`"
cat ./docs/attorney-opt-out.json | ./api-test/tester -expectedStatus=201 -authorUID=9ac5cb7c-fc75-40c7-8e53-059f36dbbe3d REQUEST POST $(URL)/lpas/$(LPA_UID)/updates "`xargs -0`"

# donor withdraws lpa
$(eval LPA_UID := "$(shell ./api-test/tester UID)")
cat ./docs/example-lpa.json | ./api-test/tester -expectedStatus=201 REQUEST PUT $(URL)/lpas/$(LPA_UID) "`xargs -0`"
Expand Down
9 changes: 5 additions & 4 deletions api-test/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
func main() {
ctx := context.Background()
expectedStatusCode := flag.Int("expectedStatus", 200, "Expected response status code")
authorUID := flag.String("authorUID", "34", "Set the UID of the author in the header")
writeBody := flag.Bool("write", false, "Write the response body to STDOUT")
flag.Parse()
args := flag.Args()
Expand All @@ -41,7 +42,7 @@ func main() {
fmt.Print("M-" + strings.ToUpper(uuid.NewString()[9:23]))
os.Exit(0)
case "JWT":
fmt.Print(makeJwt([]byte(jwtSecret)))
fmt.Print(makeJwt([]byte(jwtSecret), authorUID))
os.Exit(0)
case "REQUEST":
// continue
Expand All @@ -68,7 +69,7 @@ func main() {
}

if jwtSecret != "" {
tokenString := makeJwt([]byte(jwtSecret))
tokenString := makeJwt([]byte(jwtSecret), authorUID)

req.Header.Add("X-Jwt-Authorization", fmt.Sprintf("Bearer %s", tokenString))
}
Expand Down Expand Up @@ -127,12 +128,12 @@ func main() {
}
}

func makeJwt(secretKey []byte) string {
func makeJwt(secretKey []byte, uid *string) string {
claims := jwt.MapClaims{
"exp": time.Now().Add(time.Hour * 24).Unix(),
"iat": time.Now().Add(time.Hour * -24).Unix(),
"iss": "opg.poas.sirius",
"sub": "urn:opg:sirius:users:34",
"sub": "urn:opg:poas:sirius:users:" + *uid,
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
Expand Down
4 changes: 4 additions & 0 deletions docs/attorney-opt-out.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "ATTORNEY_OPT_OUT",
"changes": []
}
1 change: 1 addition & 0 deletions docs/openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ components:
- DONOR_CONFIRM_IDENTITY
- CERTIFICATE_PROVIDER_CONFIRM_IDENTITY
- DONOR_WITHDRAW_LPA
- ATTORNEY_OPT_OUT
changes:
type: array
items:
Expand Down
22 changes: 22 additions & 0 deletions gitleaks-report.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[
{
"Description": "Detected a Generic API Key, potentially exposing access to various services and sensitive operations.",
"StartLine": 71,
"EndLine": 71,
"StartColumn": 77,
"EndColumn": 123,
"Match": "authorUID=9ac5cb7c-fc75-40c7-8e53-059f36dbbe3d ",
"Secret": "9ac5cb7c-fc75-40c7-8e53-059f36dbbe3d",
"File": "Makefile",
"SymlinkFile": "",
"Commit": "4155b1eec33bdadc524a0582b4b858599f624466",
"Entropy": 3.7289722,
"Author": "Alex Saunders",
"Email": "[email protected]",
"Date": "2024-08-08T15:50:22Z",
"Message": "add api test for attorney opt out",
"Tags": [],
"RuleID": "generic-api-key",
"Fingerprint": "4155b1eec33bdadc524a0582b4b858599f624466:Makefile:generic-api-key:71"
}
]
39 changes: 39 additions & 0 deletions internal/shared/lpa.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package shared

import (
"slices"
"time"
)

Expand All @@ -26,6 +27,44 @@ type LpaInit struct {
CertificateProviderNotRelatedConfirmedAt *time.Time `json:"certificateProviderNotRelatedConfirmedAt,omitempty"`
}

func (l *Lpa) GetAttorney(uid string) (Attorney, bool) {
idx := slices.IndexFunc(l.Attorneys, func(a Attorney) bool { return a.UID == uid })
if idx == -1 {
return Attorney{}, false
}

return l.Attorneys[idx], true
}

func (l *Lpa) PutAttorney(attorney Attorney) {
idx := slices.IndexFunc(l.Attorneys, func(a Attorney) bool { return a.UID == attorney.UID })
if idx == -1 {
l.Attorneys = append(l.Attorneys, attorney)
} else {
l.Attorneys[idx] = attorney
}
}

func (l *Lpa) ActiveAttorneys() (attorneys []Attorney) {
for _, a := range l.Attorneys {
if a.Status == AttorneyStatusActive {
attorneys = append(attorneys, a)
}
}

return attorneys
}

func (l *Lpa) ActiveTrustCorporations() (trustCorporations []TrustCorporation) {
for _, tc := range l.TrustCorporations {
if tc.Status == AttorneyStatusActive {
trustCorporations = append(trustCorporations, tc)
}
}

return trustCorporations
}

type Lpa struct {
LpaInit
Uid string `json:"uid"`
Expand Down
111 changes: 111 additions & 0 deletions internal/shared/lpa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,114 @@ func TestLpaInitMarshalJSON(t *testing.T) {
data, _ := json.Marshal(LpaInit{})
assert.JSONEq(t, expected, string(data))
}

func TestAttorneysGet(t *testing.T) {
testCases := map[string]struct {
attorneys []Attorney
expectedAttorney Attorney
uid string
expectedFound bool
}{
"found": {
attorneys: []Attorney{
{Person: Person{UID: "abc", FirstNames: "a"}},
{Person: Person{UID: "xyz", FirstNames: "b"}},
},
expectedAttorney: Attorney{Person: Person{UID: "xyz", FirstNames: "b"}},
uid: "xyz",
expectedFound: true,
},
"not found": {
attorneys: []Attorney{
{Person: Person{UID: "abc", FirstNames: "a"}},
{Person: Person{UID: "xyz", FirstNames: "b"}},
},
expectedAttorney: Attorney{},
uid: "not-a-match",
expectedFound: false,
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
lpa := &Lpa{LpaInit: LpaInit{Attorneys: tc.attorneys}}
a, found := lpa.GetAttorney(tc.uid)

assert.Equal(t, tc.expectedFound, found)
assert.Equal(t, tc.expectedAttorney, a)
})
}
}

func TestAttorneysPut(t *testing.T) {
testCases := map[string]struct {
attorneys []Attorney
expectedAttorneys []Attorney
updatedAttorney Attorney
}{
"does not exist": {
attorneys: []Attorney{
{Person: Person{UID: "abc", FirstNames: "a"}},
},
expectedAttorneys: []Attorney{
{Person: Person{UID: "abc", FirstNames: "a"}},
{Person: Person{UID: "xyz", FirstNames: "b"}},
},
updatedAttorney: Attorney{Person: Person{UID: "xyz", FirstNames: "b"}},
},
"exists": {
attorneys: []Attorney{
{Person: Person{UID: "abc", FirstNames: "a"}},
{Person: Person{UID: "xyz", FirstNames: "b"}},
},
expectedAttorneys: []Attorney{
{Person: Person{UID: "abc", FirstNames: "a"}},
{Person: Person{UID: "xyz", FirstNames: "z"}},
},
updatedAttorney: Attorney{Person: Person{UID: "xyz", FirstNames: "z"}},
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
lpa := &Lpa{LpaInit: LpaInit{Attorneys: tc.attorneys}}
lpa.PutAttorney(tc.updatedAttorney)

assert.Equal(t, tc.expectedAttorneys, lpa.Attorneys)
})
}
}

func TestActiveAttorneys(t *testing.T) {
lpa := &Lpa{LpaInit: LpaInit{
Attorneys: []Attorney{
{Person: Person{FirstNames: "a"}},
{Person: Person{FirstNames: "b"}, Status: AttorneyStatusActive},
{Person: Person{FirstNames: "c"}, Status: AttorneyStatusReplacement},
{Person: Person{FirstNames: "d"}, Status: AttorneyStatusRemoved},
{Person: Person{FirstNames: "e"}, Status: AttorneyStatusActive},
},
}}

assert.Equal(t, []Attorney{
{Person: Person{FirstNames: "b"}, Status: AttorneyStatusActive},
{Person: Person{FirstNames: "e"}, Status: AttorneyStatusActive},
}, lpa.ActiveAttorneys())
}

func TestActiveTrustCorporations(t *testing.T) {
lpa := &Lpa{LpaInit: LpaInit{
TrustCorporations: []TrustCorporation{
{Name: "a"},
{Name: "b", Status: AttorneyStatusActive},
{Name: "c", Status: AttorneyStatusReplacement},
{Name: "d", Status: AttorneyStatusRemoved},
{Name: "e", Status: AttorneyStatusActive},
},
}}

assert.Equal(t, []TrustCorporation{
{Name: "b", Status: AttorneyStatusActive},
{Name: "e", Status: AttorneyStatusActive},
}, lpa.ActiveTrustCorporations())
}
27 changes: 25 additions & 2 deletions internal/shared/update.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,41 @@
package shared

import "encoding/json"
import (
"encoding/json"
"strings"
)

type Change struct {
Key string `json:"key"`
Old json.RawMessage `json:"old"`
New json.RawMessage `json:"new"`
}

type URN string

func (u URN) Details() AuthorDetails {
parts := strings.Split(string(u), ":")

if len(parts) != 6 || parts[3] == "" || parts[5] == "" {
return AuthorDetails{}
}

return AuthorDetails{
UID: parts[5],
Service: parts[3],
}
}

type Update struct {
Id string `json:"id"` // UUID for the update
Uid string `json:"uid"` // UID of the changed LPA
Applied string `json:"applied"` // RFC3339 datetime
Author string `json:"author"`
Author URN `json:"author"`
Type string `json:"type"`
Changes []Change `json:"changes"`
}

type AuthorDetails struct {
UID string
Service string
}
48 changes: 48 additions & 0 deletions internal/shared/update_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package shared

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestURNDetails(t *testing.T) {
testcases := map[URN]struct {
UID string
Service string
}{
"urn:opg:poas:makeregister:users:123": {
UID: "123",
Service: "makeregister",
},
"urn:opg:poas:sirius:users:456": {
UID: "456",
Service: "sirius",
},
}

for urn, tc := range testcases {
t.Run(string(urn), func(t *testing.T) {
details := urn.Details()

assert.Equal(t, tc.UID, details.UID)
assert.Equal(t, tc.Service, details.Service)
})
}

}

func TestURNDetailsWhenURNInvalidFormat(t *testing.T) {
testcases := []URN{
"urn:opg:poas:makeregister:users:",
"urn-opg-poas-makeregister-users-123",
}

for _, urn := range testcases {
t.Run(string(urn), func(t *testing.T) {
details := urn.Details()

assert.Equal(t, AuthorDetails{}, details)
})
}
}
Loading

0 comments on commit daa60de

Please sign in to comment.