diff --git a/docs/auth0_apps_create.md b/docs/auth0_apps_create.md index 011038122..a6cb7ed37 100644 --- a/docs/auth0_apps_create.md +++ b/docs/auth0_apps_create.md @@ -31,6 +31,7 @@ auth0 apps create [flags] auth0 apps create -n myapp -d -t [native|spa|regular|m2m|resource_server] -r --json --metadata "foo=bar,bazz=buzz" auth0 apps create --name "My API Client" --type resource_server --resource-server-identifier "https://api.example.com" auth0 apps create --name myapp --type resource_server --allow-any-profile-of-type custom_authentication,on_behalf_of_token_exchange + auth0 apps create --name "My 3P App" --type regular --third-party-security-mode strict --redirection-policy open_redirect_protection ``` @@ -48,9 +49,11 @@ auth0 apps create [flags] --metadata stringToString Arbitrary keys-value pairs (max 255 characters each), that can be assigned to each application. More about application metadata: https://auth0.com/docs/get-started/applications/configure-application-metadata (default []) -n, --name string Name of the application. -o, --origins strings Comma-separated list of URLs allowed to make requests from JavaScript to Auth0 API (typically used with CORS). By default, all your callback URLs will be allowed. This field allows you to enter other origins if necessary. You can also use wildcards at the subdomain level (e.g., https://*.contoso.com). Query strings and hash information are not taken into account when validating these URLs. + -y, --redirection-policy string Controls whether Auth0 redirects users to the application's callback URL on authentication errors or in email verification flows: 'allow_always' or 'open_redirect_protection'. -z, --refresh-token string Refresh Token Config for the application, formatted as JSON. --resource-server-identifier string The identifier of the resource server that this client is associated with. This property can only be sent when app_type=resource_server and cannot be changed once the client is created. -r, --reveal-secrets Display the application secrets ('signing_keys', 'client_secret') as part of the command output. + -s, --third-party-security-mode string Security mode for third-party clients: 'strict' or 'permissive'. -t, --type string Type of application: - native: mobile, desktop, CLI and smart device apps running natively. - spa (single page application): a JavaScript front-end app that uses an API. diff --git a/docs/auth0_apps_update.md b/docs/auth0_apps_update.md index b9b5fdd23..5429c8606 100644 --- a/docs/auth0_apps_update.md +++ b/docs/auth0_apps_update.md @@ -30,6 +30,8 @@ auth0 apps update [flags] auth0 apps update -n myapp -d -t [native|spa|regular|m2m] -r --json --metadata "foo=bar" --metadata "bazz=buzz" auth0 apps update -n myapp -d -t [native|spa|regular|m2m] -r --json --metadata "foo=bar,bazz=buzz" auth0 apps update --allow-any-profile-of-type custom_authentication,on_behalf_of_token_exchange + auth0 apps update --redirection-policy allow_always + auth0 apps update --third-party-security-mode strict --redirection-policy open_redirect_protection ``` @@ -47,8 +49,10 @@ auth0 apps update [flags] --metadata stringToString Arbitrary keys-value pairs (max 255 characters each), that can be assigned to each application. More about application metadata: https://auth0.com/docs/get-started/applications/configure-application-metadata (default []) -n, --name string Name of the application. -o, --origins strings Comma-separated list of URLs allowed to make requests from JavaScript to Auth0 API (typically used with CORS). By default, all your callback URLs will be allowed. This field allows you to enter other origins if necessary. You can also use wildcards at the subdomain level (e.g., https://*.contoso.com). Query strings and hash information are not taken into account when validating these URLs. + -y, --redirection-policy string Controls whether Auth0 redirects users to the application's callback URL on authentication errors or in email verification flows: 'allow_always' or 'open_redirect_protection'. -z, --refresh-token string Refresh Token Config for the application, formatted as JSON. -r, --reveal-secrets Display the application secrets ('signing_keys', 'client_secret') as part of the command output. + -s, --third-party-security-mode string Security mode for third-party clients: 'strict' or 'permissive'. -t, --type string Type of application: - native: mobile, desktop, CLI and smart device apps running natively. - spa (single page application): a JavaScript front-end app that uses an API. diff --git a/internal/cli/apps.go b/internal/cli/apps.go index f7ffc716c..3b7f108dc 100644 --- a/internal/cli/apps.go +++ b/internal/cli/apps.go @@ -176,6 +176,18 @@ var ( ShortForm: "p", Help: "Comma-separated list of enabled token exchange types for this client. Possible values: custom_authentication, on_behalf_of_token_exchange.", } + appThirdPartySecurityMode = Flag{ + Name: "Third Party Security Mode", + LongForm: "third-party-security-mode", + ShortForm: "s", + Help: "Security mode for third-party clients: 'strict' or 'permissive'.", + } + appRedirectionPolicy = Flag{ + Name: "Redirection Policy", + LongForm: "redirection-policy", + ShortForm: "y", + Help: "Controls whether Auth0 redirects users to the application's callback URL on authentication errors or in email verification flows: 'allow_always' or 'open_redirect_protection'.", + } ) func appsCmd(cli *cli) *cobra.Command { @@ -434,6 +446,8 @@ func createAppCmd(cli *cli) *cobra.Command { RefreshToken string ResourceServerIdentifier string AllowAnyProfileOfType []string + ThirdPartySecurityMode string + RedirectionPolicy string } var oidcConformant = true var algorithm = "RS256" @@ -456,7 +470,8 @@ func createAppCmd(cli *cli) *cobra.Command { auth0 apps create -n myapp -d -t [native|spa|regular|m2m|resource_server] -r --json --metadata "foo=bar" --metadata "bazz=buzz" auth0 apps create -n myapp -d -t [native|spa|regular|m2m|resource_server] -r --json --metadata "foo=bar,bazz=buzz" auth0 apps create --name "My API Client" --type resource_server --resource-server-identifier "https://api.example.com" - auth0 apps create --name myapp --type resource_server --allow-any-profile-of-type custom_authentication,on_behalf_of_token_exchange`, + auth0 apps create --name myapp --type resource_server --allow-any-profile-of-type custom_authentication,on_behalf_of_token_exchange + auth0 apps create --name "My 3P App" --type regular --third-party-security-mode strict --redirection-policy open_redirect_protection`, RunE: func(cmd *cobra.Command, args []string) error { if err := appName.Ask(cmd, &inputs.Name, nil); err != nil { return err @@ -474,6 +489,7 @@ func createAppCmd(cli *cli) *cobra.Command { appIsNative := apiTypeFor(inputs.Type) == appTypeNative appIsSPA := apiTypeFor(inputs.Type) == appTypeSPA appIsResourceServer := apiTypeFor(inputs.Type) == appTypeResourceServer + isThirdPartyApp := inputs.ThirdPartySecurityMode != "" || inputs.RedirectionPolicy != "" // Prompt for callback URLs if app is not m2m and not resource_server. if !appIsM2M && !appIsResourceServer { @@ -488,8 +504,8 @@ func createAppCmd(cli *cli) *cobra.Command { } } - // Prompt for logout URLs if app is not m2m and not resource_server. - if !appIsM2M && !appIsResourceServer { + // Prompt for logout URLs if app is not m2m and not resource_server and not a third-party app. + if !appIsM2M && !appIsResourceServer && !isThirdPartyApp { var defaultValue string if !appIsNative { @@ -593,11 +609,21 @@ func createAppCmd(cli *cli) *cobra.Command { a.TokenEndpointAuthMethod = apiAuthMethodFor(inputs.AuthMethod) } + if inputs.ThirdPartySecurityMode != "" { + a.ThirdPartySecurityMode = &inputs.ThirdPartySecurityMode + } + if inputs.RedirectionPolicy != "" { + a.RedirectionPolicy = &inputs.RedirectionPolicy + } + if isThirdPartyApp { + a.IsFirstParty = auth0.Bool(false) + } + // Set grants. - if len(inputs.Grants) == 0 { - a.GrantTypes = apiDefaultGrantsFor(inputs.Type) - } else { + if len(inputs.Grants) > 0 { a.GrantTypes = apiGrantsFor(inputs.Grants) + } else if !isThirdPartyApp { + a.GrantTypes = apiDefaultGrantsFor(inputs.Type) } // Create app. @@ -633,26 +659,30 @@ func createAppCmd(cli *cli) *cobra.Command { appTEAllowAnyProfileOfType.RegisterStringSlice(cmd, &inputs.AllowAnyProfileOfType, nil) revealSecrets.RegisterBool(cmd, &inputs.RevealSecrets, false) refreshToken.RegisterString(cmd, &inputs.RefreshToken, "") + appThirdPartySecurityMode.RegisterString(cmd, &inputs.ThirdPartySecurityMode, "") + appRedirectionPolicy.RegisterString(cmd, &inputs.RedirectionPolicy, "") return cmd } func updateAppCmd(cli *cli) *cobra.Command { var inputs struct { - ID string - Name string - Type string - Description string - Callbacks []string - AllowedOrigins []string - AllowedWebOrigins []string - AllowedLogoutURLs []string - AuthMethod string - Grants []string - RevealSecrets bool - Metadata map[string]string - RefreshToken string - AllowAnyProfileOfType []string + ID string + Name string + Type string + Description string + Callbacks []string + AllowedOrigins []string + AllowedWebOrigins []string + AllowedLogoutURLs []string + AuthMethod string + Grants []string + RevealSecrets bool + Metadata map[string]string + RefreshToken string + AllowAnyProfileOfType []string + ThirdPartySecurityMode string + RedirectionPolicy string } cmd := &cobra.Command{ @@ -673,7 +703,9 @@ func updateAppCmd(cli *cli) *cobra.Command { auth0 apps update -n myapp -d -t [native|spa|regular|m2m] -r --json --metadata "foo=bar" auth0 apps update -n myapp -d -t [native|spa|regular|m2m] -r --json --metadata "foo=bar" --metadata "bazz=buzz" auth0 apps update -n myapp -d -t [native|spa|regular|m2m] -r --json --metadata "foo=bar,bazz=buzz" - auth0 apps update --allow-any-profile-of-type custom_authentication,on_behalf_of_token_exchange`, + auth0 apps update --allow-any-profile-of-type custom_authentication,on_behalf_of_token_exchange + auth0 apps update --redirection-policy allow_always + auth0 apps update --third-party-security-mode strict --redirection-policy open_redirect_protection`, RunE: func(cmd *cobra.Command, args []string) error { var current *management.Client @@ -847,6 +879,14 @@ func updateAppCmd(cli *cli) *cobra.Command { } } + if appThirdPartySecurityMode.IsSet(cmd) { + a.ThirdPartySecurityMode = &inputs.ThirdPartySecurityMode + } + + if appRedirectionPolicy.IsSet(cmd) { + a.RedirectionPolicy = &inputs.RedirectionPolicy + } + if err := ansi.Waiting(func() error { return cli.api.Client.Update(cmd.Context(), inputs.ID, a) }); err != nil { @@ -874,6 +914,8 @@ func updateAppCmd(cli *cli) *cobra.Command { appTEAllowAnyProfileOfType.RegisterStringSliceU(cmd, &inputs.AllowAnyProfileOfType, nil) revealSecrets.RegisterBool(cmd, &inputs.RevealSecrets, false) refreshToken.RegisterString(cmd, &inputs.RefreshToken, "") + appThirdPartySecurityMode.RegisterStringU(cmd, &inputs.ThirdPartySecurityMode, "") + appRedirectionPolicy.RegisterStringU(cmd, &inputs.RedirectionPolicy, "") return cmd } diff --git a/internal/cli/terraform_fetcher.go b/internal/cli/terraform_fetcher.go index d0106905c..d3df097c5 100644 --- a/internal/cli/terraform_fetcher.go +++ b/internal/cli/terraform_fetcher.go @@ -274,8 +274,14 @@ func (f *clientGrantResourceFetcher) FetchData(ctx context.Context) (importDataL } for _, grant := range grants.ClientGrants { + var resourceName string + if grant.GetDefaultFor() != "" { + resourceName = "auth0_client_grant." + sanitizeResourceName("default_for_"+grant.GetDefaultFor()+"_"+grant.GetAudience()) + } else { + resourceName = "auth0_client_grant." + sanitizeResourceName(grant.GetClientID()+"_"+grant.GetAudience()) + } data = append(data, importDataItem{ - ResourceName: "auth0_client_grant." + sanitizeResourceName(grant.GetClientID()+"_"+grant.GetAudience()), + ResourceName: resourceName, ImportID: grant.GetID(), }) } diff --git a/internal/cli/terraform_fetcher_test.go b/internal/cli/terraform_fetcher_test.go index 4cf7c7d72..e7fc03f92 100644 --- a/internal/cli/terraform_fetcher_test.go +++ b/internal/cli/terraform_fetcher_test.go @@ -723,6 +723,67 @@ func TestClientGrantResourceFetcher_FetchData(t *testing.T) { _, err := fetcher.FetchData(context.Background()) assert.EqualError(t, err, "failed to list clients") }) + + t.Run("it handles default_for grants correctly", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + clientGrantAPI := mock.NewMockClientGrantAPI(ctrl) + clientGrantAPI.EXPECT(). + List(gomock.Any(), gomock.Any()). + Return( + &management.ClientGrantList{ + List: management.List{ + Start: 0, + Limit: 3, + Total: 3, + }, + ClientGrants: []*management.ClientGrant{ + { + ID: auth0.String("cgr_1"), + ClientID: auth0.String("client-id-1"), + Audience: auth0.String("https://travel0.com/api"), + }, + { + ID: auth0.String("cgr_2"), + DefaultFor: auth0.String("third_party_clients"), + Audience: auth0.String("https://travel0.com/api"), + }, + { + ID: auth0.String("cgr_3"), + DefaultFor: auth0.String("third_party_clients"), + Audience: auth0.String("https://partner-api.example.com"), + }, + }, + }, + nil, + ) + + fetcher := clientGrantResourceFetcher{ + api: &auth0.API{ + ClientGrant: clientGrantAPI, + }, + } + + expectedData := importDataList{ + { + ResourceName: "auth0_client_grant.client_id_1_https_travel0_com_api", + ImportID: "cgr_1", + }, + { + ResourceName: "auth0_client_grant.default_for_third_party_clients_https_travel0_com_api", + ImportID: "cgr_2", + }, + { + ResourceName: "auth0_client_grant.default_for_third_party_clients_https_partner_api_example_com", + ImportID: "cgr_3", + }, + } + + data, err := fetcher.FetchData(context.Background()) + assert.NoError(t, err) + assert.Equal(t, expectedData, data) + }) } func TestConnectionResourceFetcher_FetchData(t *testing.T) { diff --git a/internal/display/apps.go b/internal/display/apps.go index 853468834..bbcf7c1dd 100644 --- a/internal/display/apps.go +++ b/internal/display/apps.go @@ -40,6 +40,8 @@ type applicationView struct { RefreshToken string ResourceServerIdentifier string AllowAnyProfileOfType []string + ThirdPartySecurityMode string + RedirectionPolicy string revealSecret bool raw interface{} @@ -128,6 +130,14 @@ func (v *applicationView) KeyValues() [][]string { keyValues = append(keyValues, []string{"TOKEN EXCHANGE TYPES", strings.Join(v.AllowAnyProfileOfType, ", ")}) } + if v.ThirdPartySecurityMode != "" { + keyValues = append(keyValues, []string{"THIRD PARTY SECURITY MODE", v.ThirdPartySecurityMode}) + } + + if v.RedirectionPolicy != "" { + keyValues = append(keyValues, []string{"REDIRECTION POLICY", v.RedirectionPolicy}) + } + return keyValues } @@ -213,6 +223,8 @@ func makeApplicationView(client *management.Client, revealSecrets bool) *applica Metadata: mapPointerToArray(client.ClientMetadata), ResourceServerIdentifier: client.GetResourceServerIdentifier(), AllowAnyProfileOfType: client.GetTokenExchange().GetAllowAnyProfileOfType(), + ThirdPartySecurityMode: client.GetThirdPartySecurityMode(), + RedirectionPolicy: client.GetRedirectionPolicy(), raw: client, RefreshToken: string(jsonRefreshToken), } diff --git a/test/integration/apps-test-cases.yaml b/test/integration/apps-test-cases.yaml index cc88d9c0b..c79912521 100644 --- a/test/integration/apps-test-cases.yaml +++ b/test/integration/apps-test-cases.yaml @@ -376,3 +376,13 @@ tests: 050 - given a resource server app, it successfully deletes the app: command: auth0 apps delete $(./test/integration/scripts/get-resource-server-app-id.sh) --force exit-code: 0 + + 051 - it successfully creates a regular app with third-party-security-mode and redirection-policy and outputs in json: + command: auth0 apps create --name integration-test-app-3p-strict --type regular --description 3PApp1 --third-party-security-mode strict --redirection-policy open_redirect_protection --json + exit-code: 0 + stdout: + json: + name: integration-test-app-3p-strict + app_type: regular_web + third_party_security_mode: strict + redirection_policy: open_redirect_protection