From 9e98fdc350c3a19cd89bcad09e975b3ae84c213d Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Fri, 17 Sep 2021 00:27:39 +0100 Subject: [PATCH] azuread_application Allow URNs to be specified for web redirect URIs (#577) Resolves: #575 --- docs/resources/application.md | 6 +-- .../applications/application_resource.go | 14 +++--- .../applications/application_resource_test.go | 1 + .../migrations/application_resource.go | 10 ++-- .../invitations/invitation_resource.go | 2 +- .../service_principal_resource.go | 2 +- internal/validate/uri.go | 48 ++++++++++--------- internal/validate/uri_test.go | 6 +-- 8 files changed, 46 insertions(+), 43 deletions(-) diff --git a/docs/resources/application.md b/docs/resources/application.md index ca47d7192..633199040 100644 --- a/docs/resources/application.md +++ b/docs/resources/application.md @@ -254,7 +254,7 @@ The following arguments are supported: `public_client` block supports the following: -* `redirect_uris` - (Optional) A set of URLs where user tokens are sent for sign-in, or the redirect URIs where OAuth 2.0 authorization codes and access tokens are sent. +* `redirect_uris` - (Optional) A set of URLs where user tokens are sent for sign-in, or the redirect URIs where OAuth 2.0 authorization codes and access tokens are sent. Must be a valid `https` or `ms-appx-web` URL. --- @@ -276,7 +276,7 @@ The following arguments are supported: `single_page_application` block supports the following: -* `redirect_uris` - (Optional) A set of URLs where user tokens are sent for sign-in, or the redirect URIs where OAuth 2.0 authorization codes and access tokens are sent. +* `redirect_uris` - (Optional) A set of URLs where user tokens are sent for sign-in, or the redirect URIs where OAuth 2.0 authorization codes and access tokens are sent. Must be a valid `https` URL. --- @@ -285,7 +285,7 @@ The following arguments are supported: * `homepage_url` - (Optional) Home page or landing page of the application. * `implicit_grant` - (Optional) An `implicit_grant` block as documented above. * `logout_url` - (Optional) The URL that will be used by Microsoft's authorization service to sign out a user using front-channel, back-channel or SAML logout protocols. -* `redirect_uris` - (Optional) A set of URLs where user tokens are sent for sign-in, or the redirect URIs where OAuth 2.0 authorization codes and access tokens are sent. +* `redirect_uris` - (Optional) A set of URLs where user tokens are sent for sign-in, or the redirect URIs where OAuth 2.0 authorization codes and access tokens are sent. Must be a valid `http` URL or a URN. --- diff --git a/internal/services/applications/application_resource.go b/internal/services/applications/application_resource.go index 2bab45c98..dc3609a1a 100644 --- a/internal/services/applications/application_resource.go +++ b/internal/services/applications/application_resource.go @@ -291,7 +291,7 @@ func applicationResource() *schema.Resource { Optional: true, Elem: &schema.Schema{ Type: schema.TypeString, - ValidateDiagFunc: validate.IsAppURI, + ValidateDiagFunc: validate.IsAppUri, }, }, @@ -370,7 +370,7 @@ func applicationResource() *schema.Resource { MaxItems: 256, Elem: &schema.Schema{ Type: schema.TypeString, - ValidateDiagFunc: validate.IsRedirectURI, + ValidateDiagFunc: validate.IsRedirectUriFunc(false), }, }, }, @@ -447,7 +447,7 @@ func applicationResource() *schema.Resource { MaxItems: 256, Elem: &schema.Schema{ Type: schema.TypeString, - ValidateDiagFunc: validate.IsRedirectURI, + ValidateDiagFunc: validate.IsRedirectUriFunc(false), }, }, }, @@ -486,14 +486,14 @@ func applicationResource() *schema.Resource { Description: "Home page or landing page of the application", Type: schema.TypeString, Optional: true, - ValidateDiagFunc: validate.IsHTTPOrHTTPSURL, + ValidateDiagFunc: validate.IsHttpOrHttpsUrl, }, "logout_url": { Description: "The URL that will be used by Microsoft's authorization service to sign out a user using front-channel, back-channel or SAML logout protocols", Type: schema.TypeString, Optional: true, - ValidateDiagFunc: validate.IsLogoutURL, + ValidateDiagFunc: validate.IsLogoutUrl, }, "redirect_uris": { @@ -503,7 +503,7 @@ func applicationResource() *schema.Resource { MaxItems: 256, Elem: &schema.Schema{ Type: schema.TypeString, - ValidateDiagFunc: validate.IsRedirectURI, + ValidateDiagFunc: validate.IsRedirectUriFunc(true), }, }, @@ -664,7 +664,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)(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/services/applications/application_resource_test.go b/internal/services/applications/application_resource_test.go index 4cc85a508..0635786e2 100644 --- a/internal/services/applications/application_resource_test.go +++ b/internal/services/applications/application_resource_test.go @@ -674,6 +674,7 @@ resource "azuread_application" "test" { redirect_uris = [ "https://app.hashitown-%[1]d.com/", "https://classic.hashitown-%[1]d.com/", + "urn:ietf:wg:oauth:2.0:oob", ] implicit_grant { diff --git a/internal/services/applications/migrations/application_resource.go b/internal/services/applications/migrations/application_resource.go index 13552e725..ed5257baf 100644 --- a/internal/services/applications/migrations/application_resource.go +++ b/internal/services/applications/migrations/application_resource.go @@ -194,7 +194,7 @@ func ResourceApplicationInstanceResourceV0() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, - ValidateDiagFunc: validate.IsHTTPOrHTTPSURL, + ValidateDiagFunc: validate.IsHttpOrHttpsUrl, ConflictsWith: []string{"web.0.homepage_url"}, Deprecated: "[NOTE] This attribute will be replaced by a new attribute `homepage_url` in the `web` block in version 2.0 of the AzureAD provider", }, @@ -205,14 +205,14 @@ func ResourceApplicationInstanceResourceV0() *schema.Resource { Computed: true, Elem: &schema.Schema{ Type: schema.TypeString, - ValidateDiagFunc: validate.IsAppURI, + ValidateDiagFunc: validate.IsAppUri, }, }, "logout_url": { Type: schema.TypeString, Optional: true, - ValidateDiagFunc: validate.IsHTTPOrHTTPSURL, + ValidateDiagFunc: validate.IsHttpOrHttpsUrl, Computed: true, ConflictsWith: []string{"web.0.logout_url"}, Deprecated: "[NOTE] This attribute will be moved into the `web` block in version 2.0 of the AzureAD provider", @@ -488,14 +488,14 @@ func ResourceApplicationInstanceResourceV0() *schema.Resource { Type: schema.TypeString, Optional: true, ConflictsWith: []string{"homepage"}, - ValidateDiagFunc: validate.IsHTTPOrHTTPSURL, + ValidateDiagFunc: validate.IsHttpOrHttpsUrl, }, "logout_url": { Type: schema.TypeString, Optional: true, ConflictsWith: []string{"logout_url"}, - ValidateDiagFunc: validate.IsHTTPOrHTTPSURL, + ValidateDiagFunc: validate.IsHttpOrHttpsUrl, }, "redirect_uris": { diff --git a/internal/services/invitations/invitation_resource.go b/internal/services/invitations/invitation_resource.go index 1e4a0184e..1c7f906ec 100644 --- a/internal/services/invitations/invitation_resource.go +++ b/internal/services/invitations/invitation_resource.go @@ -40,7 +40,7 @@ func invitationResource() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateDiagFunc: validate.IsHTTPOrHTTPSURL, + ValidateDiagFunc: validate.IsHttpOrHttpsUrl, }, "user_email_address": { diff --git a/internal/services/serviceprincipals/service_principal_resource.go b/internal/services/serviceprincipals/service_principal_resource.go index 0d76b3ade..d5bf9eb33 100644 --- a/internal/services/serviceprincipals/service_principal_resource.go +++ b/internal/services/serviceprincipals/service_principal_resource.go @@ -125,7 +125,7 @@ func servicePrincipalResource() *schema.Resource { Description: "The URL where the service provider redirects the user to Azure AD to authenticate. Azure AD uses the URL to launch the application from Microsoft 365 or the Azure AD My Apps. When blank, Azure AD performs IdP-initiated sign-on for applications configured with SAML-based single sign-on", Type: schema.TypeString, Optional: true, - ValidateDiagFunc: validate.IsHTTPOrHTTPSURL, + ValidateDiagFunc: validate.IsHttpOrHttpsUrl, }, "notes": { diff --git a/internal/validate/uri.go b/internal/validate/uri.go index 132387057..912141e3f 100644 --- a/internal/validate/uri.go +++ b/internal/validate/uri.go @@ -10,20 +10,20 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func IsHTTPSURL(i interface{}, path cty.Path) diag.Diagnostics { - return IsURIFunc([]string{"https"}, false, false)(i, path) +func IsAppUri(i interface{}, path cty.Path) diag.Diagnostics { + return IsUriFunc([]string{"http", "https", "api", "ms-appx"}, true, false)(i, path) } -func IsHTTPOrHTTPSURL(i interface{}, path cty.Path) diag.Diagnostics { - return IsURIFunc([]string{"http", "https"}, false, false)(i, path) +func IsHttpOrHttpsUrl(i interface{}, path cty.Path) diag.Diagnostics { + return IsUriFunc([]string{"http", "https"}, false, false)(i, path) } -func IsAppURI(i interface{}, path cty.Path) diag.Diagnostics { - return IsURIFunc([]string{"http", "https", "api", "ms-appx"}, true, false)(i, path) +func IsHttpsUrl(i interface{}, path cty.Path) diag.Diagnostics { + return IsUriFunc([]string{"https"}, false, false)(i, path) } -func IsLogoutURL(i interface{}, path cty.Path) (ret diag.Diagnostics) { - ret = IsURIFunc([]string{"http", "https"}, false, false)(i, path) +func IsLogoutUrl(i interface{}, path cty.Path) (ret diag.Diagnostics) { + ret = IsUriFunc([]string{"http", "https"}, false, false)(i, path) if len(ret) > 0 { return } @@ -39,24 +39,26 @@ func IsLogoutURL(i interface{}, path cty.Path) (ret diag.Diagnostics) { return } -func IsRedirectURI(i interface{}, path cty.Path) (ret diag.Diagnostics) { - ret = IsURIFunc([]string{"http", "https", "ms-appx-web"}, false, true)(i, path) - if len(ret) > 0 { - return - } +func IsRedirectUriFunc(urnAllowed bool) schema.SchemaValidateDiagFunc { + return func(i interface{}, path cty.Path) (ret diag.Diagnostics) { + ret = IsUriFunc([]string{"http", "https", "ms-appx-web"}, urnAllowed, true)(i, path) + if len(ret) > 0 { + return + } - if len(i.(string)) > 256 { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: "URI must be 256 characters or less", - AttributePath: path, - }) - } + if len(i.(string)) > 256 { + ret = append(ret, diag.Diagnostic{ + Severity: diag.Error, + Summary: "URI must be 256 characters or less", + AttributePath: path, + }) + } - return + return + } } -func IsURIFunc(validURLSchemes []string, URNAllowed bool, forceTrailingSlash bool) schema.SchemaValidateDiagFunc { +func IsUriFunc(validURLSchemes []string, urnAllowed bool, forceTrailingSlash bool) schema.SchemaValidateDiagFunc { return func(i interface{}, path cty.Path) (ret diag.Diagnostics) { v, ok := i.(string) if !ok { @@ -77,7 +79,7 @@ func IsURIFunc(validURLSchemes []string, URNAllowed bool, forceTrailingSlash boo return } - if URNAllowed { + if urnAllowed { parts := strings.Split(v, ":") if len(parts) >= 3 && parts[0] == "urn" { return diff --git a/internal/validate/uri_test.go b/internal/validate/uri_test.go index 7bba23e53..0ae2645d2 100644 --- a/internal/validate/uri_test.go +++ b/internal/validate/uri_test.go @@ -39,7 +39,7 @@ func TestIsHTTPSURL(t *testing.T) { for _, tc := range cases { t.Run(tc.Url, func(t *testing.T) { - diags := IsHTTPSURL(tc.Url, cty.Path{}) + diags := IsHttpsUrl(tc.Url, cty.Path{}) if len(diags) != tc.Errors { t.Fatalf("Expected URLIsHTTPS to have %d not %d errors for %q", tc.Errors, len(diags), tc.Url) @@ -81,7 +81,7 @@ func TestIsHTTPOrHTTPSURL(t *testing.T) { for _, tc := range cases { t.Run(tc.Url, func(t *testing.T) { - diags := IsHTTPOrHTTPSURL(tc.Url, cty.Path{}) + diags := IsHttpOrHttpsUrl(tc.Url, cty.Path{}) if len(diags) != tc.Errors { t.Fatalf("Expected URLIsHTTPOrHTTPS to have %d not %d errors for %q", tc.Errors, len(diags), tc.Url) @@ -139,7 +139,7 @@ func TestIsAppURI(t *testing.T) { for _, tc := range cases { t.Run(tc.Url, func(t *testing.T) { - diags := IsAppURI(tc.Url, cty.Path{}) + diags := IsAppUri(tc.Url, cty.Path{}) if len(diags) != tc.Errors { t.Fatalf("Expected URLIsAppURI to have %d not %d errors for %q", tc.Errors, len(diags), tc.Url)