-
Notifications
You must be signed in to change notification settings - Fork 71
Add organization support to test token command for M2M Applications #1475
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
da553b5
57f8c9b
33d9a14
325e0c8
09dc7d2
ac0fcf9
0740e5f
26722d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| package cli | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "testing" | ||
|
|
||
| "github.com/auth0/go-auth0/management" | ||
| "github.com/golang/mock/gomock" | ||
| "github.com/stretchr/testify/assert" | ||
|
|
||
| "github.com/auth0/auth0-cli/internal/auth0" | ||
| "github.com/auth0/auth0-cli/internal/auth0/mock" | ||
| ) | ||
|
|
||
| func TestOrganizationPickerOptionsForGrant(t *testing.T) { | ||
| const audience = "https://cli-demo.us.auth0.com/api/v2/" | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| orgList *management.OrganizationList | ||
| apiError error | ||
| expectedError string | ||
| expectedOpts pickerOptions | ||
| }{ | ||
| { | ||
| name: "api error fetching organizations", | ||
| apiError: errors.New("unexpected error"), | ||
| expectedError: "unexpected error", | ||
| }, | ||
| { | ||
| name: "no organizations exist", | ||
| orgList: &management.OrganizationList{}, | ||
| expectedError: "the client grant for " + audience + " requires an organization, but no organizations exist on this tenant.\n\n" + | ||
| "Create one by running: 'auth0 orgs create'", | ||
| }, | ||
| { | ||
| name: "organizations exist", | ||
| orgList: &management.OrganizationList{ | ||
| Organizations: []*management.Organization{ | ||
| {ID: auth0.String("org_abc123"), Name: auth0.String("My Org")}, | ||
| {ID: auth0.String("org_def456"), Name: auth0.String("Other Org")}, | ||
| }, | ||
| }, | ||
| expectedOpts: pickerOptions{ | ||
| {value: "org_abc123", label: "My Org (org_abc123)"}, | ||
| {value: "org_def456", label: "Other Org (org_def456)"}, | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| for _, test := range tests { | ||
| t.Run(test.name, func(t *testing.T) { | ||
| ctrl := gomock.NewController(t) | ||
| defer ctrl.Finish() | ||
|
|
||
| orgAPI := mock.NewMockOrganizationAPI(ctrl) | ||
| orgAPI.EXPECT(). | ||
| List(gomock.Any(), gomock.Any()). | ||
| Return(test.orgList, test.apiError) | ||
|
|
||
| cli := &cli{ | ||
| api: &auth0.API{Organization: orgAPI}, | ||
| } | ||
|
|
||
| opts, err := cli.organizationPickerOptionsForGrant(audience)(context.Background()) | ||
|
|
||
| if test.expectedError != "" { | ||
| assert.ErrorContains(t, err, test.expectedError) | ||
| } else { | ||
| assert.NoError(t, err) | ||
| assert.Equal(t, test.expectedOpts, opts) | ||
| } | ||
| }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,13 +46,16 @@ func BuildOauthTokenURL(domain string) string { | |
| return u.String() | ||
| } | ||
|
|
||
| func BuildOauthTokenParams(clientID, clientSecret, audience string) url.Values { | ||
| func BuildOauthTokenParams(clientID, clientSecret, audience, organization string) url.Values { | ||
| q := url.Values{ | ||
| "audience": {audience}, | ||
| "client_id": {clientID}, | ||
| "client_secret": {clientSecret}, | ||
| "grant_type": {"client_credentials"}, | ||
| } | ||
| if organization != "" { | ||
| q.Set("organization", organization) | ||
| } | ||
| return q | ||
| } | ||
|
|
||
|
|
@@ -64,13 +67,14 @@ func runClientCredentialsFlow( | |
| client *management.Client, | ||
| audience string, | ||
| tenantDomain string, | ||
| organization string, | ||
| ) (*authutil.TokenResponse, error) { | ||
| if err := checkClientIsAuthorizedForAPI(ctx, cli, client, audience); err != nil { | ||
| if err := checkClientIsAuthorizedForAPI(ctx, cli, client, audience, organization); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| tokenURL := BuildOauthTokenURL(tenantDomain) | ||
| payload := BuildOauthTokenParams(client.GetClientID(), client.GetClientSecret(), audience) | ||
| payload := BuildOauthTokenParams(client.GetClientID(), client.GetClientSecret(), audience, organization) | ||
|
|
||
| var tokenResponse *authutil.TokenResponse | ||
| err := ansi.Spinner("Waiting for token", func() error { | ||
|
|
@@ -92,6 +96,46 @@ func runClientCredentialsFlow( | |
| return tokenResponse, err | ||
| } | ||
|
|
||
| func checkClientIsAuthorizedForAPI(ctx context.Context, cli *cli, client *management.Client, audience, organization string) error { | ||
| var list *management.ClientGrantList | ||
| if err := ansi.Waiting(func() (err error) { | ||
| list, err = cli.api.ClientGrant.List( | ||
| ctx, | ||
| management.Parameter("audience", audience), | ||
| management.Parameter("client_id", client.GetClientID()), | ||
| ) | ||
| return err | ||
| }); err != nil { | ||
| return fmt.Errorf( | ||
| "failed to find client grants for API identifier %q and client ID %q: %w", | ||
| audience, | ||
| client.GetClientID(), | ||
| err, | ||
| ) | ||
| } | ||
|
|
||
| if len(list.ClientGrants) < 1 { | ||
| return fmt.Errorf( | ||
| "the %s application is not authorized to request access tokens for this API %s.\n\n"+ | ||
| "Run: 'auth0 apps open %s' to open the dashboard and authorize the application.", | ||
| ansi.Bold(client.GetName()), | ||
| ansi.Bold(audience), | ||
| client.GetClientID(), | ||
| ) | ||
| } | ||
|
|
||
| grant := list.ClientGrants[0] | ||
| if grant.GetOrganizationUsage() == "require" && organization == "" { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since organization usage is required here, we should first validate whether the tenant has any organizations configured. If none exist, we can return a clear error to the user.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ramya18101 so just to clarify this further: If the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll spend some time working on this addition! Thanks for the feedback
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added this feature in 4dbf75e Here is the example output when the audience requires an organization but none is specified.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for adding this! |
||
| return fmt.Errorf( | ||
| "the client grant for %s requires an organization.\n\n"+ | ||
| "Use the --organization flag to specify one.", | ||
| ansi.Bold(audience), | ||
| ) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // runLoginFlowPreflightChecks checks if we need to make any updates | ||
| // to the client being tested in order to log in successfully. | ||
| // If so, it asks the user to confirm whether to proceed. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.