Skip to content
This repository has been archived by the owner on Jul 4, 2024. It is now read-only.

Commit

Permalink
Url template validation (#1889)
Browse files Browse the repository at this point in the history
* validation for webhookInput.urlTemplate.method

* add teset for invalid webhookInput.urlTemplate.method

* add applicationInput validation for applicationTemplate create and update

* add test for applicationInput validation

* PR number

* make imports

* address comments

* fix test name

Co-authored-by: Desislava Asenova <9128192+desislavaa@users.noreply.github.com>
  • Loading branch information
StanislavStefanov and desislavaa authored Jun 17, 2021
1 parent 4eba220 commit f2fe2f4
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 13 deletions.
2 changes: 1 addition & 1 deletion chart/compass/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ global:
version: "PR-1884"
director:
dir:
version: "PR-1902"
version: "PR-1889"
gateway:
dir:
version: "PR-1884"
Expand Down
83 changes: 78 additions & 5 deletions components/director/internal/domain/apptemplate/fixtures_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ const (
)

var (
testDescription = "Lorem ipsum"
testProviderName = "provider-display-name"
testURL = "http://valid.url"
testError = errors.New("test error")
testTableColumns = []string{"id", "name", "description", "application_input", "placeholders", "access_level"}
testDescription = "Lorem ipsum"
testDescriptionWithPlaceholder = "Lorem ipsum {{test}}"
testProviderName = "provider-display-name"
testURL = "http://valid.url"
testError = errors.New("test error")
testTableColumns = []string{"id", "name", "description", "application_input", "placeholders", "access_level"}
)

func fixModelApplicationTemplate(id, name string, webhooks []*model.Webhook) *model.ApplicationTemplate {
Expand Down Expand Up @@ -138,6 +139,42 @@ func fixGQLAppTemplateInput(name string) *graphql.ApplicationTemplateInput {
}
}

func fixGQLAppTemplateInputWithPlaceholder(name string) *graphql.ApplicationTemplateInput {
desc := testDescriptionWithPlaceholder

return &graphql.ApplicationTemplateInput{
Name: name,
Description: &desc,
ApplicationInput: &graphql.ApplicationRegisterInput{
Name: "foo",
Description: &desc,
},
Placeholders: fixGQLPlaceholderDefinitionInput(),
AccessLevel: graphql.ApplicationTemplateAccessLevelGlobal,
}
}

func fixGQLAppTemplateInputInvalidAppInputUrlTemplateMethod(name string) *graphql.ApplicationTemplateInput {
desc := testDescriptionWithPlaceholder

return &graphql.ApplicationTemplateInput{
Name: name,
Description: &desc,
ApplicationInput: &graphql.ApplicationRegisterInput{
Name: "foo",
Description: &desc,
Webhooks: []*graphql.WebhookInput{
{
Type: "ASYNC",
URLTemplate: str.Ptr(`{"path": "https://target.url", "method":"invalid method"}`),
},
},
},
Placeholders: fixGQLPlaceholderDefinitionInput(),
AccessLevel: graphql.ApplicationTemplateAccessLevelGlobal,
}
}

func fixGQLAppTemplateUpdateInput(name string) *graphql.ApplicationTemplateUpdateInput {
desc := testDescription

Expand All @@ -153,6 +190,42 @@ func fixGQLAppTemplateUpdateInput(name string) *graphql.ApplicationTemplateUpdat
}
}

func fixGQLAppTemplateUpdateInputWithPlaceholder(name string) *graphql.ApplicationTemplateUpdateInput {
desc := testDescriptionWithPlaceholder

return &graphql.ApplicationTemplateUpdateInput{
Name: name,
Description: &desc,
ApplicationInput: &graphql.ApplicationRegisterInput{
Name: "foo",
Description: &desc,
},
Placeholders: fixGQLPlaceholderDefinitionInput(),
AccessLevel: graphql.ApplicationTemplateAccessLevelGlobal,
}
}

func fixGQLAppTemplateUpdateInputInvalidAppInput(name string) *graphql.ApplicationTemplateUpdateInput {
desc := testDescriptionWithPlaceholder

return &graphql.ApplicationTemplateUpdateInput{
Name: name,
Description: &desc,
ApplicationInput: &graphql.ApplicationRegisterInput{
Name: "foo",
Description: &desc,
Webhooks: []*graphql.WebhookInput{
{
Type: "ASYNC",
URLTemplate: str.Ptr(`{"path": "https://target.url", "method":"invalid method"}`),
},
},
},
Placeholders: fixGQLPlaceholderDefinitionInput(),
AccessLevel: graphql.ApplicationTemplateAccessLevelGlobal,
}
}

func fixEntityApplicationTemplate(t *testing.T, id, name string) *apptemplate.Entity {
marshalledAppInput := `{"Name":"foo","ProviderName":"compass","Description":"Lorem ipsum","Labels":{"test":["val","val2"]},"HealthCheckURL":"https://foo.bar","Webhooks":[{"Type":"","URL":"webhook1.foo.bar","Auth":null},{"Type":"","URL":"webhook2.foo.bar","Auth":null}],"IntegrationSystemID":"iiiiiiiii-iiii-iiii-iiii-iiiiiiiiiiii"}`

Expand Down
8 changes: 8 additions & 0 deletions components/director/internal/domain/apptemplate/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ func (r *Resolver) CreateApplicationTemplate(ctx context.Context, in graphql.App

ctx = persistence.SaveToContext(ctx, tx)

if err := in.Validate(); err != nil {
return nil, err
}

convertedIn, err := r.appTemplateConverter.InputFromGraphQL(in)
if err != nil {
return nil, err
Expand Down Expand Up @@ -271,6 +275,10 @@ func (r *Resolver) UpdateApplicationTemplate(ctx context.Context, id string, in

ctx = persistence.SaveToContext(ctx, tx)

if err := in.Validate(); err != nil {
return nil, err
}

convertedIn, err := r.appTemplateConverter.UpdateInputFromGraphQL(in)
if err != nil {
return nil, err
Expand Down
34 changes: 32 additions & 2 deletions components/director/internal/domain/apptemplate/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ func TestResolver_CreateApplicationTemplate(t *testing.T) {
modelAppTemplate := fixModelApplicationTemplate(testID, testName, fixModelApplicationWebhooks(testWebhookID, testID))
modelAppTemplateInput := fixModelAppTemplateInput(testName, appInputJSONString)
gqlAppTemplate := fixGQLAppTemplate(testID, testName, fixGQLApplicationTemplateWebhooks(testWebhookID, testID))
gqlAppTemplateInput := fixGQLAppTemplateInput(testName)
gqlAppTemplateInput := fixGQLAppTemplateInputWithPlaceholder(testName)

testCases := []struct {
Name string
Expand Down Expand Up @@ -671,6 +671,21 @@ func TestResolver_CreateApplicationTemplate(t *testing.T) {
appTemplateConv.AssertExpectations(t)
})
}
t.Run("Returns error when application template inputs url template has invalid method", func(t *testing.T) {
gqlAppTemplateInputInvalid := fixGQLAppTemplateInputInvalidAppInputUrlTemplateMethod(testName)
expectedError := errors.New("failed to parse webhook url template")
_, transact := txGen.ThatSucceeds()

resolver := apptemplate.NewResolver(transact, nil, nil, nil, nil, nil, nil)

// WHEN
_, err := resolver.CreateApplicationTemplate(ctx, *gqlAppTemplateInputInvalid)

// THEN
require.Error(t, err)
assert.Contains(t, err.Error(), expectedError.Error())
})

}

func TestResolver_RegisterApplicationFromTemplate(t *testing.T) {
Expand Down Expand Up @@ -1042,7 +1057,7 @@ func TestResolver_UpdateApplicationTemplate(t *testing.T) {
modelAppTemplate := fixModelApplicationTemplate(testID, testName, fixModelApplicationTemplateWebhooks(testWebhookID, testID))
modelAppTemplateInput := fixModelAppTemplateUpdateInput(testName, appInputJSONString)
gqlAppTemplate := fixGQLAppTemplate(testID, testName, fixGQLApplicationTemplateWebhooks(testWebhookID, testID))
gqlAppTemplateUpdateInput := fixGQLAppTemplateUpdateInput(testName)
gqlAppTemplateUpdateInput := fixGQLAppTemplateUpdateInputWithPlaceholder(testName)

testCases := []struct {
Name string
Expand Down Expand Up @@ -1234,6 +1249,21 @@ func TestResolver_UpdateApplicationTemplate(t *testing.T) {
appTemplateConv.AssertExpectations(t)
})
}

t.Run("Returns error when application template inputs url template has invalid method", func(t *testing.T) {
gqlAppTemplateUpdateInputInvalid := fixGQLAppTemplateUpdateInputInvalidAppInput(testName)
expectedError := errors.New("failed to parse webhook url template")
_, transact := txGen.ThatSucceeds()

resolver := apptemplate.NewResolver(transact, nil, nil, nil, nil, nil, nil)

// WHEN
_, err := resolver.UpdateApplicationTemplate(ctx, testID, *gqlAppTemplateUpdateInputInvalid)

// THEN
require.Error(t, err)
assert.Contains(t, err.Error(), expectedError.Error())
})
}

func TestResolver_DeleteApplicationTemplate(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions components/director/pkg/graphql/app_template_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
func (i ApplicationTemplateInput) Validate() error {
return validation.Errors{
"Rule.ValidPlaceholders": validPlaceholders(i.Placeholders, i.ApplicationInput),
"appInput": validation.Validate(i.ApplicationInput),
"name": validation.Validate(i.Name, validation.Required, is.PrintableASCII, validation.Length(1, 100)),
"description": validation.Validate(i.Description, validation.RuneLength(0, descriptionStringLengthLimit)),
"placeholders": validation.Validate(i.Placeholders, validation.Each(validation.Required)),
Expand All @@ -26,6 +27,7 @@ func (i ApplicationTemplateInput) Validate() error {
func (i ApplicationTemplateUpdateInput) Validate() error {
return validation.Errors{
"Rule.ValidPlaceholders": validPlaceholders(i.Placeholders, i.ApplicationInput),
"appInput": validation.Validate(i.ApplicationInput),
"name": validation.Validate(i.Name, validation.Required, is.PrintableASCII, validation.Length(1, 100)),
"description": validation.Validate(i.Description, validation.RuneLength(0, descriptionStringLengthLimit)),
"placeholders": validation.Validate(i.Placeholders, validation.Each(validation.Required)),
Expand Down
71 changes: 66 additions & 5 deletions components/director/pkg/graphql/app_template_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,10 @@ func TestApplicationTemplateInput_Validate_AccessLevel(t *testing.T) {

func TestApplicationTemplateInput_Validate_Webhooks(t *testing.T) {
webhookInput := fixValidWebhookInput(inputvalidationtest.ValidURL)
invalidWebhookInput := fixValidWebhookInput(inputvalidationtest.ValidURL)
invalidWebhookInput.URL = nil

webhookInputWithInvalidOutputTemplate := fixValidWebhookInput(inputvalidationtest.ValidURL)
webhookInputWithInvalidOutputTemplate.OutputTemplate = stringPtr(`{ "gone_status_code": 404, "success_status_code": 200}`)
webhookInputwithInvalidURL := fixValidWebhookInput(inputvalidationtest.ValidURL)
webhookInputwithInvalidURL.URL = nil
testCases := []struct {
Name string
Value []*graphql.WebhookInput
Expand All @@ -288,8 +289,13 @@ func TestApplicationTemplateInput_Validate_Webhooks(t *testing.T) {
Valid: true,
},
{
Name: "Invalid - some of the webhooks are in invalid state",
Value: []*graphql.WebhookInput{&invalidWebhookInput},
Name: "Invalid - some of the webhooks are in invalid state - invalid output template",
Value: []*graphql.WebhookInput{&webhookInputWithInvalidOutputTemplate},
Valid: false,
},
{
Name: "Invalid - some of the webhooks are in invalid state - invalid URL",
Value: []*graphql.WebhookInput{&webhookInputwithInvalidURL},
Valid: false,
},
}
Expand Down Expand Up @@ -561,6 +567,61 @@ func TestApplicationTemplateUpdateInput_Validate_AccessLevel(t *testing.T) {
}
}

func TestApplicationTemplateUpdateInput_Validate_Webhooks(t *testing.T) {
webhookInput := fixValidWebhookInput(inputvalidationtest.ValidURL)
webhookInputWithInvalidOutputTemplate := fixValidWebhookInput(inputvalidationtest.ValidURL)
webhookInputWithInvalidOutputTemplate.OutputTemplate = stringPtr(`{ "gone_status_code": 404, "success_status_code": 200}`)
webhookInputWithInvalidURL := fixValidWebhookInput(inputvalidationtest.ValidURL)
webhookInputWithInvalidURL.URL = nil
testCases := []struct {
Name string
Value []*graphql.WebhookInput
Valid bool
}{
{
Name: "Valid",
Value: []*graphql.WebhookInput{&webhookInput},
Valid: true,
},
{
Name: "Valid - Empty",
Value: []*graphql.WebhookInput{},
Valid: true,
},
{
Name: "Valid - nil",
Value: nil,
Valid: true,
},
{
Name: "Invalid - some of the webhooks are in invalid state - invalid output template",
Value: []*graphql.WebhookInput{&webhookInputWithInvalidOutputTemplate},
Valid: false,
},
{
Name: "Invalid - some of the webhooks are in invalid state - invalid URL",
Value: []*graphql.WebhookInput{&webhookInputWithInvalidURL},
Valid: false,
},
}

for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
//GIVEN
sut := fixValidApplicationTemplateUpdateInput()
sut.ApplicationInput.Webhooks = testCase.Value
//WHEN
err := sut.Validate()
//THEN
if testCase.Valid {
require.NoError(t, err)
} else {
require.Error(t, err)
}
})
}
}

// PlaceholderDefinitionInput

func TestPlaceholderDefinitionInput_Validate_Name(t *testing.T) {
Expand Down
8 changes: 8 additions & 0 deletions components/director/pkg/graphql/webhook_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,14 @@ func TestWebhookInput_Validate_URLTemplate(t *testing.T) {
Name: "MissingMethod",
Value: stringPtr(`{
"path": "https://my-int-system/api/v1/{{.Application.ID}}/pairing"
}`),
ExpectedValid: false,
},
{
Name: "MethodNotAllowed",
Value: stringPtr(`{
"method": "HEAD",
"path": "https://my-int-system/api/v1/{{.Application.ID}}/pairing"
}`),
ExpectedValid: false,
},
Expand Down
16 changes: 16 additions & 0 deletions components/director/pkg/webhook/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package webhook
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"text/template"
Expand All @@ -28,6 +29,8 @@ import (
"github.com/pkg/errors"
)

var allowedMethods = []string{"GET", "POST", "PUT", "DELETE"}

type Mode string

// Resource is used to identify entities which can be part of a webhook's request data
Expand Down Expand Up @@ -76,6 +79,10 @@ func (u *URL) Validate() error {
return errors.New("missing URL Template method field")
}

if !isAllowedHTTPMethod(*u.Method) {
return errors.New(fmt.Sprint("http method not allowed, allowed methods: ", allowedMethods))
}

if u.Path == nil {
return errors.New("missing URL Template path field")
}
Expand Down Expand Up @@ -178,3 +185,12 @@ func parseTemplate(tmpl *string, data interface{}, dest interface{}) error {

return nil
}

func isAllowedHTTPMethod(method string) bool {
for _, m := range allowedMethods {
if m == method {
return true
}
}
return false
}

0 comments on commit f2fe2f4

Please sign in to comment.