diff --git a/internal/services/applications/application_resource.go b/internal/services/applications/application_resource.go index c124d3a30..38e3c81f4 100644 --- a/internal/services/applications/application_resource.go +++ b/internal/services/applications/application_resource.go @@ -712,7 +712,7 @@ func applicationResourceCustomizeDiff(ctx context.Context, diff *schema.Resource } // urn scheme not supported with personal account sign-ins for _, v := range identifierUris { - if diags := validate.IsUriFunc([]string{"http", "https", "api", "ms-appx"}, false, false)(v, cty.Path{}); diags.HasError() { + if diags := validate.IsUriFunc([]string{"http", "https", "api", "ms-appx"}, false, false, false)(v, cty.Path{}); diags.HasError() { return fmt.Errorf("`identifier_uris` is invalid. The URN scheme is not supported when `sign_in_audience` is %q or %q", msgraph.SignInAudienceAzureADandPersonalMicrosoftAccount, msgraph.SignInAudiencePersonalMicrosoftAccount) } diff --git a/internal/validate/uri.go b/internal/validate/uri.go index a9a0cae33..a2b75f59f 100644 --- a/internal/validate/uri.go +++ b/internal/validate/uri.go @@ -11,19 +11,19 @@ import ( ) func IsAppUri(i interface{}, path cty.Path) diag.Diagnostics { - return IsUriFunc([]string{"http", "https", "api", "ms-appx"}, true, false)(i, path) + return IsUriFunc([]string{"http", "https", "api", "ms-appx"}, true, false, false)(i, path) } func IsHttpOrHttpsUrl(i interface{}, path cty.Path) diag.Diagnostics { - return IsUriFunc([]string{"http", "https"}, false, false)(i, path) + return IsUriFunc([]string{"http", "https"}, false, true, false)(i, path) } func IsHttpsUrl(i interface{}, path cty.Path) diag.Diagnostics { - return IsUriFunc([]string{"https"}, false, false)(i, path) + return IsUriFunc([]string{"https"}, false, true, false)(i, path) } func IsLogoutUrl(i interface{}, path cty.Path) (ret diag.Diagnostics) { - ret = IsUriFunc([]string{"http", "https"}, false, false)(i, path) + ret = IsUriFunc([]string{"http", "https"}, false, true, false)(i, path) if len(ret) > 0 { return } @@ -47,7 +47,7 @@ func IsRedirectUriFunc(urnAllowed bool, publicClient bool) schema.SchemaValidate allowedSchemes = []string{"http", "https", "ms-appx-web"} } - ret = IsUriFunc(allowedSchemes, urnAllowed, true)(i, path) + ret = IsUriFunc(allowedSchemes, urnAllowed, true, true)(i, path) if len(ret) > 0 { return } @@ -64,7 +64,7 @@ func IsRedirectUriFunc(urnAllowed bool, publicClient bool) schema.SchemaValidate } } -func IsUriFunc(validURLSchemes []string, urnAllowed bool, forceTrailingSlash bool) schema.SchemaValidateDiagFunc { +func IsUriFunc(validURLSchemes []string, urnAllowed bool, allowTrailingSlash bool, forceTrailingSlash bool) schema.SchemaValidateDiagFunc { return func(i interface{}, path cty.Path) (ret diag.Diagnostics) { v, ok := i.(string) if !ok { @@ -103,6 +103,15 @@ func IsUriFunc(validURLSchemes []string, urnAllowed bool, forceTrailingSlash boo return } + if !allowTrailingSlash && u.Path == "/" { + ret = append(ret, diag.Diagnostic{ + Severity: diag.Error, + Summary: "URI must not have a trailing slash when there is no path segment", + AttributePath: path, + }) + return + } + if u.Host == "" { ret = append(ret, diag.Diagnostic{ Severity: diag.Error, diff --git a/internal/validate/uri_test.go b/internal/validate/uri_test.go index 0ae2645d2..33362b2f5 100644 --- a/internal/validate/uri_test.go +++ b/internal/validate/uri_test.go @@ -147,3 +147,61 @@ func TestIsAppURI(t *testing.T) { }) } } + +func TestIsUriFunc(t *testing.T) { + cases := []struct { + TestName string + Url string + UrnAllowed bool + AllowTrailingSlash bool + ForceTrailingSlash bool + Errors int + Schemes []string + }{ + { + TestName: "no path with trailing slash not allowed should error", + Url: "http://www.example.com/", + UrnAllowed: true, + AllowTrailingSlash: false, + ForceTrailingSlash: false, + Errors: 1, + Schemes: []string{"http"}, + }, + { + TestName: "no path with no trailing slash valid", + Url: "http://www.example.com", + UrnAllowed: true, + AllowTrailingSlash: false, + ForceTrailingSlash: false, + Errors: 0, + Schemes: []string{"http"}, + }, + { + TestName: "path with no trailing slash is valid", + Url: "http://www.example.com/path", + UrnAllowed: true, + AllowTrailingSlash: false, + ForceTrailingSlash: false, + Errors: 0, + Schemes: []string{"http"}, + }, + { + TestName: "uri empty should not be valid", + Url: "", + UrnAllowed: true, + AllowTrailingSlash: false, + ForceTrailingSlash: false, + Errors: 1, + Schemes: []string{"http"}, + }, + } + + for _, tc := range cases { + t.Run(tc.TestName, func(t *testing.T) { + diags := IsUriFunc(tc.Schemes, tc.UrnAllowed, tc.AllowTrailingSlash, tc.ForceTrailingSlash) + if len(diags(tc.Url, cty.Path{})) != tc.Errors { + t.Fatalf("Expected IsUriFunc to have %d errors for %v", tc.Errors, tc.Url) + } + }) + } +}