Skip to content

Commit

Permalink
Merge 3b5f1f3 into a400968
Browse files Browse the repository at this point in the history
  • Loading branch information
hawx authored Dec 12, 2023
2 parents a400968 + 3b5f1f3 commit f1ed4f7
Show file tree
Hide file tree
Showing 29 changed files with 1,112 additions and 406 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
- name: Publish pacts
run: |
pact-broker publish ./internal/uid/pacts/modernising-lpa-data-lpa-uid.json \
pact-broker publish ./pacts \
--consumer-app-version ${{ inputs.commit_sha }} \
--branch ${{ inputs.branch }} \
--tag ${{ inputs.tag }} \
Expand Down
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ multi-reporter-config.json
.idea/*
/app/tmp/*
/app/.air.toml
/internal/uid/log/*
/internal/uid/logs/*
/internal/uid/pacts/*
/logs/*
/pacts/*
/coverage/*
3 changes: 2 additions & 1 deletion cmd/event-received/cloud_watch_event_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/ministryofjustice/opg-modernising-lpa/internal/app"
"github.com/ministryofjustice/opg-modernising-lpa/internal/dynamo"
"github.com/ministryofjustice/opg-modernising-lpa/internal/event"
"github.com/ministryofjustice/opg-modernising-lpa/internal/lambda"
"github.com/ministryofjustice/opg-modernising-lpa/internal/localize"
"github.com/ministryofjustice/opg-modernising-lpa/internal/notify"
"github.com/ministryofjustice/opg-modernising-lpa/internal/page"
Expand All @@ -36,7 +37,7 @@ func (h *cloudWatchEventHandler) Handle(ctx context.Context, event events.CloudW
switch event.DetailType {
case "uid-requested":
uidStore := app.NewUidStore(h.dynamoClient, h.now)
uidClient := uid.New(h.uidBaseURL, &http.Client{Timeout: 10 * time.Second}, h.cfg, v4.NewSigner(), time.Now)
uidClient := uid.New(h.uidBaseURL, lambda.New(h.cfg, v4.NewSigner(), &http.Client{Timeout: 10 * time.Second}, time.Now))

return handleUidRequested(ctx, uidStore, uidClient, event)

Expand Down
11 changes: 9 additions & 2 deletions cmd/mlpa/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ import (
"github.com/ministryofjustice/opg-modernising-lpa/internal/app"
"github.com/ministryofjustice/opg-modernising-lpa/internal/dynamo"
"github.com/ministryofjustice/opg-modernising-lpa/internal/event"
"github.com/ministryofjustice/opg-modernising-lpa/internal/lambda"
"github.com/ministryofjustice/opg-modernising-lpa/internal/localize"
"github.com/ministryofjustice/opg-modernising-lpa/internal/lpastore"
"github.com/ministryofjustice/opg-modernising-lpa/internal/notify"
"github.com/ministryofjustice/opg-modernising-lpa/internal/onelogin"
"github.com/ministryofjustice/opg-modernising-lpa/internal/page"
Expand Down Expand Up @@ -71,6 +73,7 @@ func main() {
ApplicationID: env.Get("AWS_RUM_APPLICATION_ID", ""),
}
uidBaseURL = env.Get("UID_BASE_URL", "http://mock-uid:8080")
lpaStoreBaseURL = env.Get("LPA_STORE_BASE_URL", "http://mock-lpa-store:8080")
metadataURL = env.Get("ECS_CONTAINER_METADATA_URI_V4", "")
oneloginURL = env.Get("ONELOGIN_URL", "https://home.integration.account.gov.uk")
evidenceBucketName = env.Get("UPLOADS_S3_BUCKET_NAME", "evidence")
Expand Down Expand Up @@ -204,10 +207,12 @@ func main() {
logger.Fatal(err)
}

uidClient := uid.New(uidBaseURL, httpClient, cfg, v4.NewSigner(), time.Now)

evidenceS3Client := s3.NewClient(cfg, evidenceBucketName)

lambdaClient := lambda.New(cfg, v4.NewSigner(), httpClient, time.Now)
uidClient := uid.New(uidBaseURL, lambdaClient)
lpaStoreClient := lpastore.New(lpaStoreBaseURL, lambdaClient)

mux := http.NewServeMux()
mux.HandleFunc(page.Paths.HealthCheck.Service.String(), func(w http.ResponseWriter, r *http.Request) {})
mux.Handle(page.Paths.HealthCheck.Dependency.String(), page.DependencyHealthCheck(logger, map[string]page.HealthChecker{
Expand Down Expand Up @@ -239,6 +244,7 @@ func main() {
oneloginURL,
evidenceS3Client,
eventClient,
lpaStoreClient,
)))
mux.Handle("/", app.App(
logger,
Expand All @@ -258,6 +264,7 @@ func main() {
oneloginURL,
evidenceS3Client,
eventClient,
lpaStoreClient,
))

var handler http.Handler = mux
Expand Down
9 changes: 9 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ services:
depends_on:
- event-logger
- localstack
- mock-lpa-store
- mock-onelogin
- mock-pay
- mock-os-api
Expand Down Expand Up @@ -57,6 +58,14 @@ services:
- DATA_DIR=/tmp/localstack/data
- DEBUG=1

mock-lpa-store:
image: outofcoffee/imposter:latest
volumes:
- ./mock-lpa-store/mock-lpa-store-config.yaml:/opt/imposter/config/mock-lpa-store-config.yaml
container_name: mock-lpa-store
ports:
- "8081:8080"

mock-onelogin:
build:
context: ..
Expand Down
5 changes: 5 additions & 0 deletions docker/mock-lpa-store/mock-lpa-store-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
plugin: openapi
specFile: https://github.com/ministryofjustice/opg-data-lpa-store/raw/main/docs/openapi/openapi.yaml

validation:
request: true
3 changes: 3 additions & 0 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/ministryofjustice/opg-modernising-lpa/internal/dynamo"
"github.com/ministryofjustice/opg-modernising-lpa/internal/event"
"github.com/ministryofjustice/opg-modernising-lpa/internal/localize"
"github.com/ministryofjustice/opg-modernising-lpa/internal/lpastore"
"github.com/ministryofjustice/opg-modernising-lpa/internal/notify"
"github.com/ministryofjustice/opg-modernising-lpa/internal/onelogin"
"github.com/ministryofjustice/opg-modernising-lpa/internal/page"
Expand Down Expand Up @@ -85,6 +86,7 @@ func App(
oneloginURL string,
s3Client S3Client,
eventClient *event.Client,
lpaStoreClient *lpastore.Client,
) http.Handler {
documentStore := NewDocumentStore(lpaDynamoClient, s3Client, eventClient, random.UuidString, time.Now)

Expand Down Expand Up @@ -188,6 +190,7 @@ func App(
evidenceReceivedStore,
documentStore,
eventClient,
lpaStoreClient,
)

return withAppData(page.ValidateCsrf(rootMux, sessionStore, random.String, errorHandler), localizer, lang, rumConfig, staticHash, oneloginURL)
Expand Down
2 changes: 1 addition & 1 deletion internal/app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
)

func TestApp(t *testing.T) {
app := App(&logging.Logger{}, &localize.Localizer{}, localize.En, template.Templates{}, nil, nil, "http://public.url", &pay.Client{}, &notify.Client{}, &place.Client{}, page.RumConfig{}, "?%3fNEI0t9MN", page.Paths, &onelogin.Client{}, "http://onelogin.url", nil, nil)
app := App(&logging.Logger{}, &localize.Localizer{}, localize.En, template.Templates{}, nil, nil, "http://public.url", &pay.Client{}, &notify.Client{}, &place.Client{}, page.RumConfig{}, "?%3fNEI0t9MN", page.Paths, &onelogin.Client{}, "http://onelogin.url", nil, nil, nil)

assert.Implements(t, (*http.Handler)(nil), app)
}
Expand Down
72 changes: 72 additions & 0 deletions internal/lambda/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package lambda

import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
)

const apiGatewayServiceName = "execute-api"

//go:generate mockery --testonly --inpackage --name Signer --structname mockSigner
type Signer interface {
SignHTTP(context.Context, aws.Credentials, *http.Request, string, string, string, time.Time, ...func(options *v4.SignerOptions)) error
}

//go:generate mockery --testonly --inpackage --name Doer --structname mockDoer
type Doer interface {
Do(*http.Request) (*http.Response, error)
}

// A Client makes HTTP requests to AWS Lambda functions
type Client struct {
cfg aws.Config
signer Signer
doer Doer
now func() time.Time
}

// New creates a Client for calling AWS Lambda functions
func New(cfg aws.Config, signer Signer, doer Doer, now func() time.Time) *Client {
return &Client{
cfg: cfg,
signer: signer,
doer: doer,
now: now,
}
}

// Do executes the HTTP request
func (c *Client) Do(req *http.Request) (*http.Response, error) {
hash := sha256.New()

if req.Body != nil {
var reqBody bytes.Buffer

if _, err := io.Copy(hash, io.TeeReader(req.Body, &reqBody)); err != nil {
return nil, err
}

req.Body = io.NopCloser(&reqBody)
}

encodedBody := hex.EncodeToString(hash.Sum(nil))

credentials, err := c.cfg.Credentials.Retrieve(req.Context())
if err != nil {
return nil, err
}

if err := c.signer.SignHTTP(req.Context(), credentials, req, encodedBody, apiGatewayServiceName, c.cfg.Region, c.now().UTC()); err != nil {
return nil, err
}

return c.doer.Do(req)
}
167 changes: 167 additions & 0 deletions internal/lambda/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package lambda

import (
"context"
"errors"
"io"
"net/http"
"strings"
"testing"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

var (
expectedError = errors.New("an error")
credentials = aws.Credentials{AccessKeyID: "access-key-id", SecretAccessKey: "secret-access-key"}
region = "eu-west-1"
testNow = time.Date(2000, 1, 2, 0, 0, 0, 0, time.UTC)
testNowFn = func() time.Time { return testNow }
)

type mockCredentialsProvider struct {
willFail bool
}

func (m *mockCredentialsProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {
if m.willFail {
return aws.Credentials{}, errors.New("an error")
}

return credentials, nil
}

func (m *mockCredentialsProvider) IsExpired() bool {
return false
}

func createTestConfig(willFailRetrieveCreds bool) aws.Config {
return aws.Config{
Region: region,
Credentials: &mockCredentialsProvider{
willFail: willFailRetrieveCreds,
},
}
}

func TestNew(t *testing.T) {
signer := v4.NewSigner()
cfg := createTestConfig(false)

client := New(cfg, signer, http.DefaultClient, testNowFn)

assert.Equal(t, cfg, client.cfg)
assert.Equal(t, http.DefaultClient, client.doer)
assert.Equal(t, signer, client.signer)
assert.Equal(t, testNow, client.now())
}

func TestClientDo(t *testing.T) {
testCases := map[string]struct {
body io.Reader
encodedBody string
signedHeaders string
signature string
}{
"empty body": {
body: nil,
encodedBody: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
signedHeaders: "a-header;host;x-amz-date",
signature: "99f815531e473759852fb13154796d31f4cfaccc3036f91193df440adeba0588",
},
"with body": {
body: strings.NewReader(`{"some": "body data"}`),
encodedBody: "50c0065bb0d1e4f12d2505e2dab0219250b8202a395e6b2ab3b62e12fe4b8100",
signedHeaders: "a-header;content-length;host;x-amz-date",
signature: "c9f9b78004a45e947d0fd7ea4eba56a86d3234bed2a7b69240231a1beb8150e9",
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
now := func() time.Time { return testNow }

req, _ := http.NewRequest(http.MethodPost, "/an-url", tc.body)
req.Header.Set("a-header", "with-a-value")

expectedResponse := &http.Response{StatusCode: http.StatusTeapot}

signer := newMockSigner(t)
signer.
On("SignHTTP", req.Context(), credentials, req, tc.encodedBody, "execute-api", region, testNow).
Return(nil)

doer := newMockDoer(t)
doer.
On("Do", req).
Return(expectedResponse, expectedError)

client := New(createTestConfig(false), signer, doer, now)

resp, err := client.Do(req)
assert.Equal(t, expectedError, err)
assert.Equal(t, expectedResponse, resp)
})
}
}

type errReader struct{}

func (errReader) Read(p []byte) (int, error) {
return 1, expectedError
}

func TestClientDoWhenReadAllError(t *testing.T) {
req, _ := http.NewRequest(http.MethodPost, "/an-url", errReader{})
req.Header.Set("Content-Length", "100")

now := func() time.Time { return time.Now() }
client := &Client{
doer: http.DefaultClient,
cfg: createTestConfig(false),
signer: v4.NewSigner(),
now: now,
}

_, err := client.Do(req)
assert.Equal(t, expectedError, err)
}

func TestClientDoWhenRetrieveCredentialsError(t *testing.T) {
req, _ := http.NewRequest(http.MethodPost, "/an-url", nil)
req.Header.Set("a-header", "with-a-value")

client := &Client{
doer: http.DefaultClient,
cfg: createTestConfig(true),
signer: nil,
now: testNowFn,
}

_, err := client.Do(req)
assert.Equal(t, expectedError, err)
}

func TestClientDoWhenSignerError(t *testing.T) {
req, _ := http.NewRequest(http.MethodPost, "/an-url", nil)
req.Header.Set("a-header", "with-a-value")

v4Signer := newMockSigner(t)
v4Signer.
On("SignHTTP", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(expectedError)

client := &Client{
doer: http.DefaultClient,
cfg: createTestConfig(false),
signer: v4Signer,
now: testNowFn,
}

_, err := client.Do(req)
assert.Equal(t, expectedError, err)
}
Loading

0 comments on commit f1ed4f7

Please sign in to comment.