Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge d128bb7 into b600e6f
Browse files Browse the repository at this point in the history
acsauk authored Oct 19, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
2 parents b600e6f + d128bb7 commit 372113f
Showing 21 changed files with 631 additions and 310 deletions.
2 changes: 1 addition & 1 deletion .air.toml
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ root = "."
tmp_dir = "tmp"

[build]
exclude_dir = ["cypress", "docs", "tmp", "web/assets", "web/static", "node_modules", "scripts", "terraform"]
exclude_dir = ["cypress", "docs", "tmp", "web/assets", "web/static", "node_modules", "scripts", "terraform", "cmd/event-received", "cmd/enumerator", "cmd/event-mock-notify", "cmd/mock.onelogin", "cmd/mock-os-api"]
cmd = "cd cmd/mlpa && go build -ldflags='-X main.Tag=${TAG}' -gcflags='all=-N -l' -o /tmp/mlpa ."
full_bin = "pkill -9 'dlv|mlpa'; sleep 0.1; dlv exec --accept-multiclient --log --headless --continue --listen :2345 --api-version 2 /tmp/mlpa"
include_ext = ["go", "gohtml", "json"]
36 changes: 24 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
@@ -84,25 +84,37 @@ run-structurizr-export:
scan-lpas: ##@app dumps all entries in the lpas dynamodb table
docker compose -f docker/docker-compose.yml exec localstack awslocal dynamodb scan --table-name lpas

get-lpa: ##@app dumps all entries in the lpas dynamodb table that are related to the LPA id supplied e.g. get-lpa ID=abc-123
get-lpa: ##@app dumps all entries in the lpas dynamodb table that are related to the LPA id supplied e.g. get-lpa id=abc-123
docker compose -f docker/docker-compose.yml exec localstack awslocal dynamodb \
query --table-name lpas --key-condition-expression 'PK = :pk' --expression-attribute-values '{":pk": {"S": "LPA#$(ID)"}}'
query --table-name lpas --key-condition-expression 'PK = :pk' --expression-attribute-values '{":pk": {"S": "LPA#$(id)"}}'

get-evidence: ##@app dumps all fee evidence in the lpas dynamodb table that are related to the LPA id supplied e.g. get-evidence ID=abc-123
get-evidence: ##@app dumps all fee evidence in the lpas dynamodb table that are related to the LPA id supplied e.g. get-evidence id=abc-123
docker compose -f docker/docker-compose.yml exec localstack awslocal dynamodb \
query --table-name lpas --key-condition-expression 'PK = :pk' --expression-attribute-values '{":pk": {"S": "LPA#$(ID)"}}' --projection-expression "EvidenceKeys"
query --table-name lpas --key-condition-expression 'PK = :pk' --expression-attribute-values '{":pk": {"S": "LPA#$(id)"}}' --projection-expression "Evidence"

emit-evidence-received: ##@app emits an evidence-received event with the given UID e.g. emit-evidence-received UID=abc-123
curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"version":"0","id":"63eb7e5f-1f10-4744-bba9-e16d327c3b98","detail-type":"evidence-received","source":"opg.poas.sirius","account":"653761790766","time":"2023-08-30T13:40:30Z","region":"eu-west-1","resources":[],"detail":{"UID":"$(UID)"}}'
emit-evidence-received: ##@app emits an evidence-received event with the given UID e.g. emit-evidence-received uid=abc-123
curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"version":"0","id":"63eb7e5f-1f10-4744-bba9-e16d327c3b98","detail-type":"evidence-received","source":"opg.poas.sirius","account":"653761790766","time":"2023-08-30T13:40:30Z","region":"eu-west-1","resources":[],"detail":{"UID":"$(uid)"}}'

emit-fee-approved: ##@app emits a fee-approved event with the given UID e.g. emit-fee-approved UID=abc-123
curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"version":"0","id":"63eb7e5f-1f10-4744-bba9-e16d327c3b98","detail-type":"fee-approved","source":"opg.poas.sirius","account":"653761790766","time":"2023-08-30T13:40:30Z","region":"eu-west-1","resources":[],"detail":{"UID":"$(UID)"}}'
emit-fee-approved: ##@app emits a fee-approved event with the given UID e.g. emit-fee-approved uid=abc-123
curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"version":"0","id":"63eb7e5f-1f10-4744-bba9-e16d327c3b98","detail-type":"fee-approved","source":"opg.poas.sirius","account":"653761790766","time":"2023-08-30T13:40:30Z","region":"eu-west-1","resources":[],"detail":{"UID":"$(uid)"}}'

emit-fee-denied: ##@app emits a fee-denied event with the given UID e.g. emit-fee-denied UID=abc-123
curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"version":"0","id":"63eb7e5f-1f10-4744-bba9-e16d327c3b98","detail-type":"fee-denied","source":"opg.poas.sirius","account":"653761790766","time":"2023-08-30T13:40:30Z","region":"eu-west-1","resources":[],"detail":{"UID":"$(UID)"}}'
emit-fee-denied: ##@app emits a fee-denied event with the given UID e.g. emit-fee-denied uid=abc-123
curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"version":"0","id":"63eb7e5f-1f10-4744-bba9-e16d327c3b98","detail-type":"fee-denied","source":"opg.poas.sirius","account":"653761790766","time":"2023-08-30T13:40:30Z","region":"eu-west-1","resources":[],"detail":{"UID":"$(uid)"}}'

emit-more-evidence-required: ##@app emits a more-evidence-required event with the given UID e.g. emit-more-evidence-required UID=abc-123
curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"version":"0","id":"63eb7e5f-1f10-4744-bba9-e16d327c3b98","detail-type":"more-evidence-required","source":"opg.poas.sirius","account":"653761790766","time":"2023-08-30T13:40:30Z","region":"eu-west-1","resources":[],"detail":{"UID":"$(UID)"}}'
emit-more-evidence-required: ##@app emits a more-evidence-required event with the given UID e.g. emit-more-evidence-required uid=abc-123
curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"version":"0","id":"63eb7e5f-1f10-4744-bba9-e16d327c3b98","detail-type":"more-evidence-required","source":"opg.poas.sirius","account":"653761790766","time":"2023-08-30T13:40:30Z","region":"eu-west-1","resources":[],"detail":{"UID":"$(uid)"}}'

emit-object-tags-added-with-virus: ##@app emits a Object Tags Added event with the given S3 key e.g. emit-object-tags-added-with-virus key=doc/key. Also ensures a tag with virus-scan-status exists on an existing object set to infected
docker compose -f docker/docker-compose.yml exec localstack awslocal s3api \
put-object-tagging --bucket evidence --key $(key) --tagging '{"TagSet": [{ "Key": "virus-scan-status", "Value": "infected" }]}'

curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"version":"0","id":"63eb7e5f-1f10-4744-bba9-e16d327c3b98","detail-type":"Object Tags Added","source":"aws.s3","account":"653761790766","time":"2023-08-30T13:40:30Z","region":"eu-west-1","resources":[],"detail":{"object":{"key":"$(key)"}}}'

emit-object-tags-added-without-virus: ##@app emits a Object Tags Added event with the given S3 key e.g. emit-object-tags-added-with-virus key=doc/key. Also ensures a tag with virus-scan-status exists on an existing object set to ok
docker compose -f docker/docker-compose.yml exec localstack awslocal s3api \
put-object-tagging --bucket evidence --key $(key) --tagging '{"TagSet": [{ "Key": "virus-scan-status", "Value": "ok" }]}'

curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"version":"0","id":"63eb7e5f-1f10-4744-bba9-e16d327c3b98","detail-type":"Object Tags Added","source":"aws.s3","account":"653761790766","time":"2023-08-30T13:40:30Z","region":"eu-west-1","resources":[],"detail":{"object":{"key":"$(key)"}}}'

logs: ##@app tails logs for all containers running
docker compose -f docker/docker-compose.yml -f docker/docker-compose.dev.yml logs -f
165 changes: 121 additions & 44 deletions cmd/event-received/main.go
Original file line number Diff line number Diff line change
@@ -3,37 +3,59 @@ package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"strings"
"time"

"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/ministryofjustice/opg-modernising-lpa/internal/actor"
"github.com/ministryofjustice/opg-modernising-lpa/internal/app"
"github.com/ministryofjustice/opg-modernising-lpa/internal/dynamo"
"github.com/ministryofjustice/opg-modernising-lpa/internal/localize"
"github.com/ministryofjustice/opg-modernising-lpa/internal/notify"
"github.com/ministryofjustice/opg-modernising-lpa/internal/page"
"github.com/ministryofjustice/opg-modernising-lpa/internal/random"
"github.com/ministryofjustice/opg-modernising-lpa/internal/s3"
"github.com/ministryofjustice/opg-modernising-lpa/internal/secrets"
)

const (
virusFound = "infected"
evidenceReceivedEventName = "evidence-received"
feeApprovedEventName = "fee-approved"
feeDeniedEventName = "fee-denied"
moreEvidenceRequiredEventName = "more-evidence-required"
objectTagsAddedEventName = "Object Tags Added"
)

type uidEvent struct {
UID string `json:"uid"`
}

type objectTagsAddedEvent struct {
Object struct {
Key string `json:"key"`
} `json:"object"`
}

//go:generate mockery --testonly --inpackage --name dynamodbClient --structname mockDynamodbClient
type dynamodbClient interface {
One(ctx context.Context, pk, sk string, v interface{}) error
OneByUID(ctx context.Context, uid string, v interface{}) error
Put(ctx context.Context, v interface{}) error
}

//go:generate mockery --testonly --inpackage --name s3Client --structname mockS3Client
type s3Client interface {
GetObjectTags(ctx context.Context, key string) ([]types.Tag, error)
}

//go:generate mockery --testonly --inpackage --name shareCodeSender --structname mockShareCodeSender
type shareCodeSender interface {
SendCertificateProvider(context.Context, notify.Template, page.AppData, bool, *page.Lpa) error
@@ -45,6 +67,7 @@ func Handler(ctx context.Context, event events.CloudWatchEvent) error {
appPublicURL := os.Getenv("APP_PUBLIC_URL")
awsBaseURL := os.Getenv("AWS_BASE_URL")
notifyBaseURL := os.Getenv("GOVUK_NOTIFY_BASE_URL")
evidenceBucketName := os.Getenv("UPLOADS_S3_BUCKET_NAME")

cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
@@ -61,6 +84,8 @@ func Handler(ctx context.Context, event events.CloudWatchEvent) error {
})
}

s3Client := s3.NewClient(cfg, evidenceBucketName)

dynamoClient, err := dynamo.NewClient(cfg, tableName)
if err != nil {
return fmt.Errorf("failed to create dynamodb client: %w", err)
@@ -87,14 +112,16 @@ func Handler(ctx context.Context, event events.CloudWatchEvent) error {
now := time.Now

switch event.DetailType {
case "evidence-received":
case evidenceReceivedEventName:
return handleEvidenceReceived(ctx, dynamoClient, event)
case "fee-approved":
case feeApprovedEventName:
return handleFeeApproved(ctx, dynamoClient, event, shareCodeSender, appData, now)
case "more-evidence-required":
case moreEvidenceRequiredEventName:
return handleMoreEvidenceRequired(ctx, dynamoClient, event, now)
case "fee-denied":
case feeDeniedEventName:
return handleFeeDenied(ctx, dynamoClient, event, now)
case objectTagsAddedEventName:
return handleObjectTagsAdded(ctx, dynamoClient, event, now, s3Client)
default:
return fmt.Errorf("unknown event received: %s", event.DetailType)
}
@@ -103,20 +130,20 @@ func Handler(ctx context.Context, event events.CloudWatchEvent) error {
func handleEvidenceReceived(ctx context.Context, client dynamodbClient, event events.CloudWatchEvent) error {
var v uidEvent
if err := json.Unmarshal(event.Detail, &v); err != nil {
return fmt.Errorf("failed to unmarshal 'evidence-received' detail: %w", err)
return fmt.Errorf("failed to unmarshal '%s' detail: %w", evidenceReceivedEventName, err)
}

var key dynamo.Key
if err := client.OneByUID(ctx, v.UID, &key); err != nil {
return fmt.Errorf("failed to resolve uid for 'evidence-received': %w", err)
return fmt.Errorf("failed to resolve uid for '%s': %w", evidenceReceivedEventName, err)
}

if key.PK == "" {
return errors.New("PK missing from LPA in response to 'evidence-received'")
return fmt.Errorf("PK missing from LPA in response to '%s'", evidenceReceivedEventName)
}

if err := client.Put(ctx, map[string]string{"PK": key.PK, "SK": "#EVIDENCE_RECEIVED"}); err != nil {
return fmt.Errorf("failed to persist evidence received for 'evidence-received': %w", err)
return fmt.Errorf("failed to persist evidence received for '%s': %w", evidenceReceivedEventName, err)
}

return nil
@@ -125,28 +152,23 @@ func handleEvidenceReceived(ctx context.Context, client dynamodbClient, event ev
func handleFeeApproved(ctx context.Context, dynamoClient dynamodbClient, event events.CloudWatchEvent, shareCodeSender shareCodeSender, appData page.AppData, now func() time.Time) error {
var v uidEvent
if err := json.Unmarshal(event.Detail, &v); err != nil {
return fmt.Errorf("failed to unmarshal 'fee-approved' detail: %w", err)
return fmt.Errorf("failed to unmarshal '%s' detail: %w", feeApprovedEventName, err)
}

var key dynamo.Key
if err := dynamoClient.OneByUID(ctx, v.UID, &key); err != nil {
return fmt.Errorf("failed to resolve uid for 'fee-approved': %w", err)
}

var lpa page.Lpa
if err := dynamoClient.One(ctx, key.PK, key.SK, &lpa); err != nil {
return fmt.Errorf("failed to get LPA for 'fee-approved': %w", err)
lpa, err := getLpaByUID(ctx, dynamoClient, v.UID, feeApprovedEventName)
if err != nil {
return err
}

lpa.Tasks.PayForLpa = actor.PaymentTaskCompleted
lpa.UpdatedAt = now()

if err := dynamoClient.Put(ctx, lpa); err != nil {
return fmt.Errorf("failed to update LPA task status for 'fee-approved': %w", err)
return fmt.Errorf("failed to update LPA task status for '%s': %w", feeApprovedEventName, err)
}

if err := shareCodeSender.SendCertificateProvider(ctx, notify.CertificateProviderInviteEmail, appData, false, &lpa); err != nil {
return fmt.Errorf("failed to send share code to certificate provider for 'fee-approved': %w", err)
return fmt.Errorf("failed to send share code to certificate provider for '%s': %w", feeApprovedEventName, err)
}

return nil
@@ -155,63 +177,118 @@ func handleFeeApproved(ctx context.Context, dynamoClient dynamodbClient, event e
func handleMoreEvidenceRequired(ctx context.Context, client dynamodbClient, event events.CloudWatchEvent, now func() time.Time) error {
var v uidEvent
if err := json.Unmarshal(event.Detail, &v); err != nil {
return fmt.Errorf("failed to unmarshal 'more-evidence-required' detail: %w", err)
return fmt.Errorf("failed to unmarshal '%s' detail: %w", moreEvidenceRequiredEventName, err)
}

var key dynamo.Key
if err := client.OneByUID(ctx, v.UID, &key); err != nil {
return fmt.Errorf("failed to resolve uid for 'more-evidence-required': %w", err)
lpa, err := getLpaByUID(ctx, client, v.UID, moreEvidenceRequiredEventName)
if err != nil {
return err
}

if key.PK == "" {
return errors.New("PK missing from LPA in response to 'more-evidence-required'")
lpa.Tasks.PayForLpa = actor.PaymentTaskMoreEvidenceRequired
lpa.UpdatedAt = now()

if err := client.Put(ctx, lpa); err != nil {
return fmt.Errorf("failed to update LPA task status for '%s': %w", moreEvidenceRequiredEventName, err)
}

var lpa page.Lpa
if err := client.One(ctx, key.PK, key.SK, &lpa); err != nil {
return fmt.Errorf("failed to get LPA for 'more-evidence-required': %w", err)
return nil
}

func handleFeeDenied(ctx context.Context, client dynamodbClient, event events.CloudWatchEvent, now func() time.Time) error {
var v uidEvent
if err := json.Unmarshal(event.Detail, &v); err != nil {
return fmt.Errorf("failed to unmarshal '%s' detail: %w", feeDeniedEventName, err)
}

lpa.Tasks.PayForLpa = actor.PaymentTaskMoreEvidenceRequired
lpa, err := getLpaByUID(ctx, client, v.UID, feeDeniedEventName)
if err != nil {
return err
}

lpa.Tasks.PayForLpa = actor.PaymentTaskDenied
lpa.UpdatedAt = now()

if err := client.Put(ctx, lpa); err != nil {
return fmt.Errorf("failed to update LPA task status for 'more-evidence-required': %w", err)
return fmt.Errorf("failed to update LPA task status for '%s': %w", feeDeniedEventName, err)
}

return nil
}

func handleFeeDenied(ctx context.Context, client dynamodbClient, event events.CloudWatchEvent, now func() time.Time) error {
var v uidEvent
func handleObjectTagsAdded(ctx context.Context, client dynamodbClient, event events.CloudWatchEvent, now func() time.Time, s3Client s3Client) error {
var v objectTagsAddedEvent
if err := json.Unmarshal(event.Detail, &v); err != nil {
return fmt.Errorf("failed to unmarshal 'fee-denied' detail: %w", err)
return fmt.Errorf("failed to unmarshal '%s' detail: %w", objectTagsAddedEventName, err)
}

var key dynamo.Key
if err := client.OneByUID(ctx, v.UID, &key); err != nil {
return fmt.Errorf("failed to resolve uid for 'fee-denied': %w", err)
objectKey := v.Object.Key

tags, err := s3Client.GetObjectTags(ctx, objectKey)
if err != nil {
return fmt.Errorf("failed to get tags for object in '%s': %w", objectTagsAddedEventName, err)
}

if key.PK == "" {
return errors.New("PK missing from LPA in response to 'fee-denied'")
hasScannedTag := false
var tagIndex int

for i, tag := range tags {
if *tag.Key == "virus-scan-status" {
hasScannedTag = true
tagIndex = i
}
}

var lpa page.Lpa
if err := client.One(ctx, key.PK, key.SK, &lpa); err != nil {
return fmt.Errorf("failed to get LPA for 'fee-denied': %w", err)
if !hasScannedTag {
return nil
}

uid := strings.Split(objectKey, "/")

lpa, err := getLpaByUID(ctx, client, uid[0], objectTagsAddedEventName)
if err != nil {
return err
}

document := lpa.Evidence.GetByDocumentKey(objectKey)
if document.Key == "" {
return fmt.Errorf("LPA did not contain a document with key %s for '%s'", objectKey, objectTagsAddedEventName)
}

document.Scanned = now()
document.VirusDetected = *tags[tagIndex].Value == virusFound

if ok := lpa.Evidence.Update(document); !ok {
return fmt.Errorf("failed to update evidence on LPA for '%s'", objectTagsAddedEventName)
}

lpa.Tasks.PayForLpa = actor.PaymentTaskDenied
lpa.UpdatedAt = now()

if err := client.Put(ctx, lpa); err != nil {
return fmt.Errorf("failed to update LPA task status for 'fee-denied': %w", err)
return fmt.Errorf("failed to update LPA for '%s': %w", objectTagsAddedEventName, err)
}

return nil
}

func getLpaByUID(ctx context.Context, client dynamodbClient, uid, eventName string) (page.Lpa, error) {
var key dynamo.Key
if err := client.OneByUID(ctx, uid, &key); err != nil {
return page.Lpa{}, fmt.Errorf("failed to resolve uid for '%s': %w", eventName, err)
}

if key.PK == "" {
return page.Lpa{}, fmt.Errorf("PK missing from LPA in response to '%s'", eventName)
}

var lpa page.Lpa
if err := client.One(ctx, key.PK, key.SK, &lpa); err != nil {
return page.Lpa{}, fmt.Errorf("failed to get LPA for '%s': %w", eventName, err)
}

return lpa, nil
}

func main() {
lambda.Start(Handler)
}
Loading

0 comments on commit 372113f

Please sign in to comment.