Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MLPAB-2318: Support ATTORNEY_OPT_OUT #241

Merged
merged 11 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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