Skip to content

Commit 079ed0d

Browse files
authored
Support third party app security params (#1522)
* feat(apps): add support for third-party security mode and redirection policy - Introduced new flags for third-party applications: - `third-party-security-mode` to specify 'strict' or 'permissive'. - `redirection-policy` to control Auth0's behavior on authentication errors. - Updated `createAppCmd` and `updateAppCmd` functions to handle new inputs. - Enhanced `applicationView` to display third-party security mode and redirection policy. - Ensured backward compatibility by integrating new features without affecting existing functionality. * feat(cli): enhance client grant resource fetching with default_for support - Updated the FetchData method in clientGrantResourceFetcher to handle grants marked as default_for, allowing for more accurate resource naming. - Introduced a new test case to validate the handling of default_for grants in TestClientGrantResourceFetcher_FetchData, ensuring expected behavior when fetching client grants with different audiences. * feat(docs): update application creation and update examples with new flags - Added examples for `auth0 apps create` and `auth0 apps update` commands to demonstrate the usage of `--third-party-security-mode` and `--redirection-policy` flags. - Updated documentation to reflect the new security features for third-party applications, enhancing clarity for users. * test(apps): add test case for creating regular app with third-party security mode - Added a new test case to validate the creation of a regular app with third-party security mode set to strict and redirection policy set to open_redirect_protection. - The test ensures that the app is created successfully and outputs the expected JSON response. * chore(terraform): lint fix * feat(apps): add is-first-party flag for application creation and update - Introduced a new flag `--is-first-party` to differentiate between first-party and third-party applications. - Updated the command examples in `auth0_apps_create.md` and `auth0_apps_update.md` to reflect the new flag usage. - Modified the `createAppCmd` and `updateAppCmd` functions in `apps.go` to handle the new flag. - Enhanced the `applicationView` struct in `display/apps.go` to include the `IsFirstParty` field for display purposes. * chore(terraform_generate): add comment to client grant resource naming for default_for grants * test(apps): add tests for third-party app creation and management - Added integration tests for creating, showing, updating, and deleting a third-party app. - Created a new script `get-3p-app-id.sh` to handle the app ID retrieval for testing. - Updated existing test cases to reflect the new structure and ensure proper validation of third-party app properties. * chore(apps): lint fix * docs(apps): require --is-first-party=false for redirection policy and security mode - Updated documentation for `auth0 apps create` and `auth0 apps update` commands to specify that the `--is-first-party=false` flag is required when using the `--redirection-policy` and `--third-party-security-mode` options. - Enhanced user clarity on the usage of these flags to prevent misconfiguration. * fix(cli): update resource naming for client grants - Refactored the logic for generating resource names in the FetchData method of clientGrantResourceFetcher. - The resource name now uses the client ID or default_for value, ensuring consistency in naming. - Updated test cases to reflect the new naming convention for third-party client grants.
1 parent 0445ac1 commit 079ed0d

8 files changed

Lines changed: 210 additions & 22 deletions

File tree

docs/auth0_apps_create.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ auth0 apps create [flags]
3131
auth0 apps create -n myapp -d <description> -t [native|spa|regular|m2m|resource_server] -r --json --metadata "foo=bar,bazz=buzz"
3232
auth0 apps create --name "My API Client" --type resource_server --resource-server-identifier "https://api.example.com"
3333
auth0 apps create --name myapp --type resource_server --allow-any-profile-of-type custom_authentication,on_behalf_of_token_exchange
34+
auth0 apps create --name "My 3P App" --type regular --is-first-party=false --third-party-security-mode strict --redirection-policy open_redirect_protection
3435
```
3536

3637

@@ -42,15 +43,18 @@ auth0 apps create [flags]
4243
-c, --callbacks strings After the user authenticates we will only call back to any of these URLs. You can specify multiple valid URLs by comma-separating them (typically to handle different environments like QA or testing). Make sure to specify the protocol (https://) otherwise the callback may fail in some cases. With the exception of custom URI schemes for native apps, all callbacks should use protocol https://.
4344
-d, --description string Description of the application. Max character count is 140.
4445
-g, --grants strings List of grant types supported for this application. Can include code, implicit, refresh-token, credentials, password, password-realm, mfa-oob, mfa-otp, mfa-recovery-code, and device-code.
46+
-f, --is-first-party Whether the application is a first-party client (true) or third-party client (false). (default true)
4547
--json Output in json format.
4648
--json-compact Output in compact json format.
4749
-l, --logout-urls strings Comma-separated list of URLs that are valid to redirect to after logout from Auth0. Wildcards are allowed for subdomains.
4850
--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 [])
4951
-n, --name string Name of the application.
5052
-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.
53+
-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'. Require --is-first-party=false
5154
-z, --refresh-token string Refresh Token Config for the application, formatted as JSON.
5255
--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.
5356
-r, --reveal-secrets Display the application secrets ('signing_keys', 'client_secret') as part of the command output.
57+
-s, --third-party-security-mode string Security mode for third-party clients: 'strict' or 'permissive'. Require --is-first-party=false
5458
-t, --type string Type of application:
5559
- native: mobile, desktop, CLI and smart device apps running natively.
5660
- spa (single page application): a JavaScript front-end app that uses an API.

docs/auth0_apps_update.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ auth0 apps update [flags]
3030
auth0 apps update <app-id> -n myapp -d <description> -t [native|spa|regular|m2m] -r --json --metadata "foo=bar" --metadata "bazz=buzz"
3131
auth0 apps update <app-id> -n myapp -d <description> -t [native|spa|regular|m2m] -r --json --metadata "foo=bar,bazz=buzz"
3232
auth0 apps update <app-id> --allow-any-profile-of-type custom_authentication,on_behalf_of_token_exchange
33+
auth0 apps update <app-id> --redirection-policy allow_always
3334
```
3435

3536

@@ -41,14 +42,17 @@ auth0 apps update [flags]
4142
-c, --callbacks strings After the user authenticates we will only call back to any of these URLs. You can specify multiple valid URLs by comma-separating them (typically to handle different environments like QA or testing). Make sure to specify the protocol (https://) otherwise the callback may fail in some cases. With the exception of custom URI schemes for native apps, all callbacks should use protocol https://.
4243
-d, --description string Description of the application. Max character count is 140.
4344
-g, --grants strings List of grant types supported for this application. Can include code, implicit, refresh-token, credentials, password, password-realm, mfa-oob, mfa-otp, mfa-recovery-code, and device-code.
45+
-f, --is-first-party Whether the application is a first-party client (true) or third-party client (false). (default true)
4446
--json Output in json format.
4547
--json-compact Output in compact json format.
4648
-l, --logout-urls strings Comma-separated list of URLs that are valid to redirect to after logout from Auth0. Wildcards are allowed for subdomains.
4749
--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 [])
4850
-n, --name string Name of the application.
4951
-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.
52+
-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'. Require --is-first-party=false
5053
-z, --refresh-token string Refresh Token Config for the application, formatted as JSON.
5154
-r, --reveal-secrets Display the application secrets ('signing_keys', 'client_secret') as part of the command output.
55+
-s, --third-party-security-mode string Security mode for third-party clients: 'strict' or 'permissive'. Require --is-first-party=false
5256
-t, --type string Type of application:
5357
- native: mobile, desktop, CLI and smart device apps running natively.
5458
- spa (single page application): a JavaScript front-end app that uses an API.

internal/cli/apps.go

Lines changed: 79 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,24 @@ var (
176176
ShortForm: "p",
177177
Help: "Comma-separated list of enabled token exchange types for this client. Possible values: custom_authentication, on_behalf_of_token_exchange.",
178178
}
179+
appIsFirstParty = Flag{
180+
Name: "Is First Party",
181+
LongForm: "is-first-party",
182+
ShortForm: "f",
183+
Help: "Whether the application is a first-party client (true) or third-party client (false).",
184+
}
185+
appThirdPartySecurityMode = Flag{
186+
Name: "Third Party Security Mode",
187+
LongForm: "third-party-security-mode",
188+
ShortForm: "s",
189+
Help: "Security mode for third-party clients: 'strict' or 'permissive'. Require --is-first-party=false",
190+
}
191+
appRedirectionPolicy = Flag{
192+
Name: "Redirection Policy",
193+
LongForm: "redirection-policy",
194+
ShortForm: "y",
195+
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'. Require --is-first-party=false",
196+
}
179197
)
180198

181199
func appsCmd(cli *cli) *cobra.Command {
@@ -434,6 +452,9 @@ func createAppCmd(cli *cli) *cobra.Command {
434452
RefreshToken string
435453
ResourceServerIdentifier string
436454
AllowAnyProfileOfType []string
455+
IsFirstParty bool
456+
ThirdPartySecurityMode string
457+
RedirectionPolicy string
437458
}
438459
var oidcConformant = true
439460
var algorithm = "RS256"
@@ -456,7 +477,8 @@ func createAppCmd(cli *cli) *cobra.Command {
456477
auth0 apps create -n myapp -d <description> -t [native|spa|regular|m2m|resource_server] -r --json --metadata "foo=bar" --metadata "bazz=buzz"
457478
auth0 apps create -n myapp -d <description> -t [native|spa|regular|m2m|resource_server] -r --json --metadata "foo=bar,bazz=buzz"
458479
auth0 apps create --name "My API Client" --type resource_server --resource-server-identifier "https://api.example.com"
459-
auth0 apps create --name myapp --type resource_server --allow-any-profile-of-type custom_authentication,on_behalf_of_token_exchange`,
480+
auth0 apps create --name myapp --type resource_server --allow-any-profile-of-type custom_authentication,on_behalf_of_token_exchange
481+
auth0 apps create --name "My 3P App" --type regular --is-first-party=false --third-party-security-mode strict --redirection-policy open_redirect_protection`,
460482
RunE: func(cmd *cobra.Command, args []string) error {
461483
if err := appName.Ask(cmd, &inputs.Name, nil); err != nil {
462484
return err
@@ -475,6 +497,9 @@ func createAppCmd(cli *cli) *cobra.Command {
475497
appIsSPA := apiTypeFor(inputs.Type) == appTypeSPA
476498
appIsResourceServer := apiTypeFor(inputs.Type) == appTypeResourceServer
477499

500+
// IsFirstParty flag is explisitly set to false.
501+
isThirdPartyApp := appIsFirstParty.IsSet(cmd) && !inputs.IsFirstParty
502+
478503
// Prompt for callback URLs if app is not m2m and not resource_server.
479504
if !appIsM2M && !appIsResourceServer {
480505
var defaultValue string
@@ -488,8 +513,8 @@ func createAppCmd(cli *cli) *cobra.Command {
488513
}
489514
}
490515

491-
// Prompt for logout URLs if app is not m2m and not resource_server.
492-
if !appIsM2M && !appIsResourceServer {
516+
// Prompt for logout URLs if app is not m2m and not resource_server and not a third-party app.
517+
if !appIsM2M && !appIsResourceServer && !isThirdPartyApp {
493518
var defaultValue string
494519

495520
if !appIsNative {
@@ -593,11 +618,22 @@ func createAppCmd(cli *cli) *cobra.Command {
593618
a.TokenEndpointAuthMethod = apiAuthMethodFor(inputs.AuthMethod)
594619
}
595620

621+
// Set first and third party fields.
622+
if appIsFirstParty.IsSet(cmd) {
623+
a.IsFirstParty = auth0.Bool(inputs.IsFirstParty)
624+
}
625+
if inputs.ThirdPartySecurityMode != "" {
626+
a.ThirdPartySecurityMode = &inputs.ThirdPartySecurityMode
627+
}
628+
if inputs.RedirectionPolicy != "" {
629+
a.RedirectionPolicy = &inputs.RedirectionPolicy
630+
}
631+
596632
// Set grants.
597-
if len(inputs.Grants) == 0 {
598-
a.GrantTypes = apiDefaultGrantsFor(inputs.Type)
599-
} else {
633+
if len(inputs.Grants) > 0 {
600634
a.GrantTypes = apiGrantsFor(inputs.Grants)
635+
} else if !isThirdPartyApp {
636+
a.GrantTypes = apiDefaultGrantsFor(inputs.Type)
601637
}
602638

603639
// Create app.
@@ -633,26 +669,32 @@ func createAppCmd(cli *cli) *cobra.Command {
633669
appTEAllowAnyProfileOfType.RegisterStringSlice(cmd, &inputs.AllowAnyProfileOfType, nil)
634670
revealSecrets.RegisterBool(cmd, &inputs.RevealSecrets, false)
635671
refreshToken.RegisterString(cmd, &inputs.RefreshToken, "")
672+
appIsFirstParty.RegisterBool(cmd, &inputs.IsFirstParty, true)
673+
appThirdPartySecurityMode.RegisterString(cmd, &inputs.ThirdPartySecurityMode, "")
674+
appRedirectionPolicy.RegisterString(cmd, &inputs.RedirectionPolicy, "")
636675

637676
return cmd
638677
}
639678

640679
func updateAppCmd(cli *cli) *cobra.Command {
641680
var inputs struct {
642-
ID string
643-
Name string
644-
Type string
645-
Description string
646-
Callbacks []string
647-
AllowedOrigins []string
648-
AllowedWebOrigins []string
649-
AllowedLogoutURLs []string
650-
AuthMethod string
651-
Grants []string
652-
RevealSecrets bool
653-
Metadata map[string]string
654-
RefreshToken string
655-
AllowAnyProfileOfType []string
681+
ID string
682+
Name string
683+
Type string
684+
Description string
685+
Callbacks []string
686+
AllowedOrigins []string
687+
AllowedWebOrigins []string
688+
AllowedLogoutURLs []string
689+
AuthMethod string
690+
Grants []string
691+
RevealSecrets bool
692+
Metadata map[string]string
693+
RefreshToken string
694+
AllowAnyProfileOfType []string
695+
IsFirstParty bool
696+
ThirdPartySecurityMode string
697+
RedirectionPolicy string
656698
}
657699

658700
cmd := &cobra.Command{
@@ -673,7 +715,8 @@ func updateAppCmd(cli *cli) *cobra.Command {
673715
auth0 apps update <app-id> -n myapp -d <description> -t [native|spa|regular|m2m] -r --json --metadata "foo=bar"
674716
auth0 apps update <app-id> -n myapp -d <description> -t [native|spa|regular|m2m] -r --json --metadata "foo=bar" --metadata "bazz=buzz"
675717
auth0 apps update <app-id> -n myapp -d <description> -t [native|spa|regular|m2m] -r --json --metadata "foo=bar,bazz=buzz"
676-
auth0 apps update <app-id> --allow-any-profile-of-type custom_authentication,on_behalf_of_token_exchange`,
718+
auth0 apps update <app-id> --allow-any-profile-of-type custom_authentication,on_behalf_of_token_exchange
719+
auth0 apps update <app-id> --redirection-policy allow_always`,
677720
RunE: func(cmd *cobra.Command, args []string) error {
678721
var current *management.Client
679722

@@ -847,6 +890,18 @@ func updateAppCmd(cli *cli) *cobra.Command {
847890
}
848891
}
849892

893+
if appIsFirstParty.IsSet(cmd) {
894+
a.IsFirstParty = auth0.Bool(inputs.IsFirstParty)
895+
}
896+
897+
if appThirdPartySecurityMode.IsSet(cmd) {
898+
a.ThirdPartySecurityMode = &inputs.ThirdPartySecurityMode
899+
}
900+
901+
if appRedirectionPolicy.IsSet(cmd) {
902+
a.RedirectionPolicy = &inputs.RedirectionPolicy
903+
}
904+
850905
if err := ansi.Waiting(func() error {
851906
return cli.api.Client.Update(cmd.Context(), inputs.ID, a)
852907
}); err != nil {
@@ -874,6 +929,9 @@ func updateAppCmd(cli *cli) *cobra.Command {
874929
appTEAllowAnyProfileOfType.RegisterStringSliceU(cmd, &inputs.AllowAnyProfileOfType, nil)
875930
revealSecrets.RegisterBool(cmd, &inputs.RevealSecrets, false)
876931
refreshToken.RegisterString(cmd, &inputs.RefreshToken, "")
932+
appIsFirstParty.RegisterBoolU(cmd, &inputs.IsFirstParty, true)
933+
appThirdPartySecurityMode.RegisterStringU(cmd, &inputs.ThirdPartySecurityMode, "")
934+
appRedirectionPolicy.RegisterStringU(cmd, &inputs.RedirectionPolicy, "")
877935

878936
return cmd
879937
}

internal/cli/terraform_fetcher.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,12 @@ func (f *clientGrantResourceFetcher) FetchData(ctx context.Context) (importDataL
274274
}
275275

276276
for _, grant := range grants.ClientGrants {
277+
identifier := grant.GetClientID()
278+
if identifier == "" {
279+
identifier = grant.GetDefaultFor()
280+
}
277281
data = append(data, importDataItem{
278-
ResourceName: "auth0_client_grant." + sanitizeResourceName(grant.GetClientID()+"_"+grant.GetAudience()),
282+
ResourceName: "auth0_client_grant." + sanitizeResourceName(identifier+"_"+grant.GetAudience()),
279283
ImportID: grant.GetID(),
280284
})
281285
}

internal/cli/terraform_fetcher_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,67 @@ func TestClientGrantResourceFetcher_FetchData(t *testing.T) {
723723
_, err := fetcher.FetchData(context.Background())
724724
assert.EqualError(t, err, "failed to list clients")
725725
})
726+
727+
t.Run("it handles default_for grants correctly", func(t *testing.T) {
728+
ctrl := gomock.NewController(t)
729+
defer ctrl.Finish()
730+
731+
clientGrantAPI := mock.NewMockClientGrantAPI(ctrl)
732+
clientGrantAPI.EXPECT().
733+
List(gomock.Any(), gomock.Any()).
734+
Return(
735+
&management.ClientGrantList{
736+
List: management.List{
737+
Start: 0,
738+
Limit: 3,
739+
Total: 3,
740+
},
741+
ClientGrants: []*management.ClientGrant{
742+
{
743+
ID: auth0.String("cgr_1"),
744+
ClientID: auth0.String("client-id-1"),
745+
Audience: auth0.String("https://travel0.com/api"),
746+
},
747+
{
748+
ID: auth0.String("cgr_2"),
749+
DefaultFor: auth0.String("third_party_clients"),
750+
Audience: auth0.String("https://travel0.com/api"),
751+
},
752+
{
753+
ID: auth0.String("cgr_3"),
754+
DefaultFor: auth0.String("third_party_clients"),
755+
Audience: auth0.String("https://partner-api.example.com"),
756+
},
757+
},
758+
},
759+
nil,
760+
)
761+
762+
fetcher := clientGrantResourceFetcher{
763+
api: &auth0.API{
764+
ClientGrant: clientGrantAPI,
765+
},
766+
}
767+
768+
expectedData := importDataList{
769+
{
770+
ResourceName: "auth0_client_grant.client_id_1_https_travel0_com_api",
771+
ImportID: "cgr_1",
772+
},
773+
{
774+
ResourceName: "auth0_client_grant.third_party_clients_https_travel0_com_api",
775+
ImportID: "cgr_2",
776+
},
777+
{
778+
ResourceName: "auth0_client_grant.third_party_clients_https_partner_api_example_com",
779+
ImportID: "cgr_3",
780+
},
781+
}
782+
783+
data, err := fetcher.FetchData(context.Background())
784+
assert.NoError(t, err)
785+
assert.Equal(t, expectedData, data)
786+
})
726787
}
727788

728789
func TestConnectionResourceFetcher_FetchData(t *testing.T) {

internal/display/apps.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ type applicationView struct {
4040
RefreshToken string
4141
ResourceServerIdentifier string
4242
AllowAnyProfileOfType []string
43+
IsFirstParty *bool
44+
ThirdPartySecurityMode string
45+
RedirectionPolicy string
4346
revealSecret bool
4447

4548
raw interface{}
@@ -128,6 +131,18 @@ func (v *applicationView) KeyValues() [][]string {
128131
keyValues = append(keyValues, []string{"TOKEN EXCHANGE TYPES", strings.Join(v.AllowAnyProfileOfType, ", ")})
129132
}
130133

134+
if v.IsFirstParty != nil {
135+
keyValues = append(keyValues, []string{"IS FIRST PARTY", boolean(*v.IsFirstParty)})
136+
}
137+
138+
if v.ThirdPartySecurityMode != "" {
139+
keyValues = append(keyValues, []string{"THIRD PARTY SECURITY MODE", v.ThirdPartySecurityMode})
140+
}
141+
142+
if v.RedirectionPolicy != "" {
143+
keyValues = append(keyValues, []string{"REDIRECTION POLICY", v.RedirectionPolicy})
144+
}
145+
131146
return keyValues
132147
}
133148

@@ -213,6 +228,9 @@ func makeApplicationView(client *management.Client, revealSecrets bool) *applica
213228
Metadata: mapPointerToArray(client.ClientMetadata),
214229
ResourceServerIdentifier: client.GetResourceServerIdentifier(),
215230
AllowAnyProfileOfType: client.GetTokenExchange().GetAllowAnyProfileOfType(),
231+
IsFirstParty: client.IsFirstParty,
232+
ThirdPartySecurityMode: client.GetThirdPartySecurityMode(),
233+
RedirectionPolicy: client.GetRedirectionPolicy(),
216234
raw: client,
217235
RefreshToken: string(jsonRefreshToken),
218236
}

test/integration/apps-test-cases.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,3 +376,29 @@ tests:
376376
050 - given a resource server app, it successfully deletes the app:
377377
command: auth0 apps delete $(./test/integration/scripts/get-resource-server-app-id.sh) --force
378378
exit-code: 0
379+
380+
051 - it successfully creates a third-party app with strict security mode:
381+
command: ./test/integration/scripts/get-3p-app-id.sh
382+
exit-code: 0
383+
384+
052 - it successfully shows a third-party app with security mode and redirection policy in json:
385+
command: auth0 apps show $(./test/integration/scripts/get-3p-app-id.sh) --json
386+
exit-code: 0
387+
stdout:
388+
json:
389+
name: integration-test-app-3p-strict
390+
app_type: regular_web
391+
is_first_party: "false"
392+
third_party_security_mode: strict
393+
redirection_policy: open_redirect_protection
394+
395+
053 - it successfully updates the redirection-policy of a third-party app and outputs in json:
396+
command: auth0 apps update $(./test/integration/scripts/get-3p-app-id.sh) --redirection-policy allow_always --json
397+
exit-code: 0
398+
stdout:
399+
json:
400+
redirection_policy: allow_always
401+
402+
054 - given a third-party app, it successfully deletes the app:
403+
command: auth0 apps delete $(./test/integration/scripts/get-3p-app-id.sh) --force
404+
exit-code: 0

0 commit comments

Comments
 (0)