Skip to content
Open
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
12 changes: 11 additions & 1 deletion mmv1/third_party/terraform/fwvalidators/framework_validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,18 @@ func (v jwtValidator) ValidateString(ctx context.Context, request validator.Stri
return
}

// Check that each part is base64 encoded
// Check that each part is non-empty and base64url encoded. An empty
// segment decodes without error, so a token like ".." would otherwise
// be accepted as a valid JWT.
for i, part := range parts {
if part == "" {
response.Diagnostics.AddAttributeError(
request.Path,
"Invalid JWT Format",
fmt.Sprintf("Part %d of JWT must not be empty (expected header.payload.signature)", i+1),
)
continue
}
if _, err := base64.RawURLEncoding.DecodeString(part); err != nil {
response.Diagnostics.AddAttributeError(
request.Path,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package fwvalidators_test

import (
"context"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -161,6 +162,99 @@ func TestServiceAccountEmailValidator(t *testing.T) {
}
}

func TestJWTValidator(t *testing.T) {
t.Parallel()

type testCase struct {
value types.String
expectError bool
errorContains string
}

tests := map[string]testCase{
"valid jwt": {
value: types.StringValue("eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjMifQ.c2ln"),
expectError: false,
},
"empty string": {
value: types.StringValue(""),
expectError: true,
errorContains: "JWT token must not be empty",
},
"wrong number of segments": {
value: types.StringValue("header.payload"),
expectError: true,
errorContains: "JWT token must have 3 parts separated by dots (header.payload.signature)",
},
"all segments empty": {
value: types.StringValue(".."),
expectError: true,
errorContains: "Part 1 of JWT must not be empty (expected header.payload.signature)",
},
"empty payload segment": {
value: types.StringValue("ab..cd"),
expectError: true,
errorContains: "Part 2 of JWT must not be empty (expected header.payload.signature)",
},
"empty signature segment": {
value: types.StringValue("eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjMifQ."),
expectError: true,
errorContains: "Part 3 of JWT must not be empty (expected header.payload.signature)",
},
"invalid base64 segment": {
value: types.StringValue("not base64.eyJzdWIiOiIxMjMifQ.c2ln"),
expectError: true,
errorContains: "Part 1 of JWT is not valid base64",
},
"null value": {
value: types.StringNull(),
expectError: false,
},
"unknown value": {
value: types.StringUnknown(),
expectError: false,
},
}

for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {
t.Parallel()

request := validator.StringRequest{
Path: path.Root("test"),
PathExpression: path.MatchRoot("test"),
ConfigValue: test.value,
}
response := validator.StringResponse{}
v := fwvalidators.JWTValidator()

v.ValidateString(context.Background(), request, &response)

if test.expectError && !response.Diagnostics.HasError() {
t.Errorf("expected error, got none for value: %q", test.value.ValueString())
}

if !test.expectError && response.Diagnostics.HasError() {
t.Errorf("got unexpected error for value: %q: %s", test.value.ValueString(), response.Diagnostics.Errors())
}

if test.errorContains != "" {
foundError := false
for _, err := range response.Diagnostics.Errors() {
if strings.Contains(err.Detail(), test.errorContains) {
foundError = true
break
}
}
if !foundError {
t.Errorf("expected error containing %q, got %v", test.errorContains, response.Diagnostics.Errors())
}
}
})
}
}

func TestBoundedDuration(t *testing.T) {
t.Parallel()

Expand Down
14 changes: 14 additions & 0 deletions mmv1/third_party/terraform/provider/provider_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,20 @@ func TestProvider_ValidateJWT(t *testing.T) {
errors.New("\"\" is not a valid JWT format"),
},
},
"a JWT with an empty segment is rejected": {
ConfigValue: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..c2ln",
ExpectedErrors: []error{
errors.New("part 2 of JWT must not be empty (expected header.payload.signature)"),
},
},
"a JWT with all empty segments is rejected": {
ConfigValue: "..",
ExpectedErrors: []error{
errors.New("part 1 of JWT must not be empty (expected header.payload.signature)"),
errors.New("part 2 of JWT must not be empty (expected header.payload.signature)"),
errors.New("part 3 of JWT must not be empty (expected header.payload.signature)"),
},
},
"unconfigured value is not valid": {
ValueNotProvided: true,
ExpectedErrors: []error{
Expand Down
8 changes: 7 additions & 1 deletion mmv1/third_party/terraform/provider/provider_validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,14 @@ func ValidateJWT(v interface{}, k string) (warnings []string, errors []error) {
return
}

// Check that each part is base64 encoded
// Check that each part is non-empty and base64url encoded. An empty
// segment decodes without error, so a token like ".." would otherwise
// be accepted as a valid JWT.
for i, part := range parts {
if part == "" {
errors = append(errors, fmt.Errorf("part %d of JWT must not be empty (expected header.payload.signature)", i+1))
continue
}
if _, err := base64.RawURLEncoding.DecodeString(part); err != nil {
errors = append(errors, fmt.Errorf("part %d of JWT is not valid base64: %v", i+1, err))
}
Expand Down
Loading