diff --git a/app/app_client.go b/app/app_client.go index f7b1d98dc58..5bbb845278b 100644 --- a/app/app_client.go +++ b/app/app_client.go @@ -857,6 +857,17 @@ func (c *AppClient) OrganizationGetLogo(ctx context.Context, orgID string) (stri return resp.Url, nil } +// ListOAuthApps gets the client's list of OAuth applications. +func (c *AppClient) ListOAuthApps(ctx context.Context, orgID string) ([]string, error) { + resp, err := c.client.ListOAuthApps(ctx, &pb.ListOAuthAppsRequest{ + OrgId: orgID, + }) + if err != nil { + return nil, err + } + return resp.ClientIds, nil +} + // CreateLocation creates a location with the given name under the given organization. func (c *AppClient) CreateLocation(ctx context.Context, orgID, name string, opts *CreateLocationOptions) (*Location, error) { var parentID *string diff --git a/app/app_client_test.go b/app/app_client_test.go index 33c44c9a93b..6a484bf2643 100644 --- a/app/app_client_test.go +++ b/app/app_client_test.go @@ -740,6 +740,21 @@ func TestAppClient(t *testing.T) { test.That(t, resp, test.ShouldEqual, "https://logo.com") }) + t.Run("ListOAuthApps", func(t *testing.T) { + grpcClient.ListOAuthAppsFunc = func( + ctx context.Context, in *pb.ListOAuthAppsRequest, opts ...grpc.CallOption, + ) (*pb.ListOAuthAppsResponse, error) { + test.That(t, in.OrgId, test.ShouldEqual, organizationID) + return &pb.ListOAuthAppsResponse{ + ClientIds: []string{"clientId"}, + }, nil + } + + resp, err := client.ListOAuthApps(context.Background(), organizationID) + test.That(t, err, test.ShouldBeNil) + test.That(t, resp, test.ShouldResemble, []string{"clientId"}) + }) + t.Run("GetSupportEmail", func(t *testing.T) { grpcClient.OrganizationGetSupportEmailFunc = func( ctx context.Context, in *pb.OrganizationGetSupportEmailRequest, opts ...grpc.CallOption, diff --git a/cli/app.go b/cli/app.go index e36ca9314c3..2fc86a3cb46 100644 --- a/cli/app.go +++ b/cli/app.go @@ -482,6 +482,19 @@ var app = &cli.App{ Before: createCommandWithT[deleteOAuthAppArgs](DeleteOAuthAppConfirmation), Action: createCommandWithT[deleteOAuthAppArgs](DeleteOAuthAppAction), }, + { + Name: "list", + Usage: "list oauth applications for an organization", + UsageText: createUsageText("delete", []string{generalFlagOrgID, authApplicationFlagClientID}, true), + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: generalFlagOrgID, + Required: true, + Usage: "the org to get applications for", + }, + }, + Action: createCommandWithT[listOAuthAppsArgs](ListOAuthAppsAction), + }, }, }, }, diff --git a/cli/client.go b/cli/client.go index 4d58dfbeae4..81d5bf1009b 100644 --- a/cli/client.go +++ b/cli/client.go @@ -464,6 +464,49 @@ func (c *viamClient) organizationsLogoGetAction(cCtx *cli.Context, orgID string) return nil } +type listOAuthAppsArgs struct { + OrgID string +} + +// ListOAuthAppsAction corresponds to `organizations auth-service oauth-app list`. +func ListOAuthAppsAction(cCtx *cli.Context, args listOAuthAppsArgs) error { + c, err := newViamClient(cCtx) + if err != nil { + return err + } + + orgID := args.OrgID + if orgID == "" { + return errors.New("organization ID is required to list OAuth apps") + } + + return c.listOAuthAppsAction(cCtx, orgID) +} + +func (c *viamClient) listOAuthAppsAction(cCtx *cli.Context, orgID string) error { + if err := c.ensureLoggedIn(); err != nil { + return err + } + + resp, err := c.client.ListOAuthApps(cCtx.Context, &apppb.ListOAuthAppsRequest{ + OrgId: orgID, + }) + if err != nil { + return err + } + + if len(resp.ClientIds) == 0 { + printf(cCtx.App.Writer, "No OAuth apps found for organization %q\n", orgID) + return nil + } + + printf(cCtx.App.Writer, "OAuth apps for organization %q:\n", orgID) + for _, id := range resp.ClientIds { + printf(cCtx.App.Writer, " - %s\n", id) + } + return nil +} + // ListLocationsAction is the corresponding Action for 'locations list'. func ListLocationsAction(c *cli.Context, args emptyArgs) error { client, err := newViamClient(c) diff --git a/cli/client_test.go b/cli/client_test.go index 53b0268810f..9496faa80ff 100644 --- a/cli/client_test.go +++ b/cli/client_test.go @@ -352,6 +352,24 @@ func TestGetLogoAction(t *testing.T) { test.That(t, out.messages[0], test.ShouldContainSubstring, "https://logo.com") } +func TestListOAuthAppsAction(t *testing.T) { + listOAuthAppFunc := func(ctx context.Context, in *apppb.ListOAuthAppsRequest, opts ...grpc.CallOption) ( + *apppb.ListOAuthAppsResponse, error, + ) { + return &apppb.ListOAuthAppsResponse{}, nil + } + + asc := &inject.AppServiceClient{ + ListOAuthAppsFunc: listOAuthAppFunc, + } + + cCtx, ac, out, errOut := setup(asc, nil, nil, nil, nil, "token") + test.That(t, ac.listOAuthAppsAction(cCtx, "test-org"), test.ShouldBeNil) + test.That(t, len(errOut.messages), test.ShouldEqual, 0) + test.That(t, len(out.messages), test.ShouldEqual, 1) + test.That(t, out.messages[0], test.ShouldContainSubstring, "No OAuth apps found for organization") +} + func TestDeleteOAuthAppAction(t *testing.T) { deleteOAuthAppFunc := func(ctx context.Context, in *apppb.DeleteOAuthAppRequest, opts ...grpc.CallOption) ( *apppb.DeleteOAuthAppResponse, error, diff --git a/testutils/inject/app_service_client.go b/testutils/inject/app_service_client.go index 5c7884e9af1..4ccaf1f8843 100644 --- a/testutils/inject/app_service_client.go +++ b/testutils/inject/app_service_client.go @@ -56,6 +56,8 @@ type AppServiceClient struct { opts ...grpc.CallOption) (*apppb.OrganizationSetLogoResponse, error) OrganizationGetLogoFunc func(ctx context.Context, in *apppb.OrganizationGetLogoRequest, opts ...grpc.CallOption) (*apppb.OrganizationGetLogoResponse, error) + ListOAuthAppsFunc func(ctx context.Context, in *apppb.ListOAuthAppsRequest, + opts ...grpc.CallOption) (*apppb.ListOAuthAppsResponse, error) DeleteOAuthAppFunc func(ctx context.Context, in *apppb.DeleteOAuthAppRequest, opts ...grpc.CallOption) (*apppb.DeleteOAuthAppResponse, error) CreateLocationFunc func(ctx context.Context, in *apppb.CreateLocationRequest, @@ -405,6 +407,16 @@ func (asc *AppServiceClient) OrganizationGetLogo( return asc.OrganizationGetLogoFunc(ctx, in, opts...) } +// ListOAuthApps calls the injected ListOAuthAppsFunc or the real version. +func (asc *AppServiceClient) ListOAuthApps( + ctx context.Context, in *apppb.ListOAuthAppsRequest, opts ...grpc.CallOption, +) (*apppb.ListOAuthAppsResponse, error) { + if asc.ListOAuthAppsFunc == nil { + return asc.AppServiceClient.ListOAuthApps(ctx, in, opts...) + } + return asc.ListOAuthAppsFunc(ctx, in, opts...) +} + // DeleteOAuthApp calls the injected DeleteOAuthAppFunc or the real version. func (asc *AppServiceClient) DeleteOAuthApp( ctx context.Context, in *apppb.DeleteOAuthAppRequest, opts ...grpc.CallOption,