diff --git a/cli/app.go b/cli/app.go index 2458b0c1c6d..defe4a537b7 100644 --- a/cli/app.go +++ b/cli/app.go @@ -130,6 +130,7 @@ const ( authApplicationFlagOriginURIs = "origin-uris" authApplicationFlagRedirectURIs = "redirect-uris" authApplicationFlagLogoutURI = "logout-uri" + authApplicationFlagClientID = "client-id" cpFlagRecursive = "recursive" cpFlagPreserve = "preserve" @@ -425,6 +426,37 @@ var app = &cli.App{ Usage: "work with organizations", HideHelpCommand: true, Subcommands: []*cli.Command{ + { + Name: "auth-service", + Usage: "manage auth-service", + Subcommands: []*cli.Command{ + { + Name: "oauth-app", + Usage: "manage the OAuth applications for an organization", + Subcommands: []*cli.Command{ + { + Name: "delete", + Usage: "delete an OAuth application", + UsageText: createUsageText("delete", []string{generalFlagOrgID, authApplicationFlagClientID}, true), + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: generalFlagOrgID, + Required: true, + Usage: "organization ID tied to the OAuth application", + }, + &cli.StringFlag{ + Name: authApplicationFlagClientID, + Required: true, + Usage: "client ID of the OAuth application to delete", + }, + }, + Before: createCommandWithT[deleteOAuthAppArgs](DeleteOAuthAppConfirmation), + Action: createCommandWithT[deleteOAuthAppArgs](DeleteOAuthAppAction), + }, + }, + }, + }, + }, { Name: "list", Usage: "list organizations for the current user", diff --git a/cli/client.go b/cli/client.go index 6ffc908bddc..54ee2ad63a7 100644 --- a/cli/client.go +++ b/cli/client.go @@ -2,6 +2,7 @@ package cli import ( + "bufio" "context" "encoding/json" "fmt" @@ -2050,3 +2051,70 @@ func logEntryFieldsToString(fields []*structpb.Struct) (string, error) { } return message + "}", nil } + +type deleteOAuthAppArgs struct { + OrgID string + ClientID string +} + +// DeleteOAuthAppConfirmation is the Before action for 'organizations auth-service oauth-app delete'. +// It asks for the user to confirm that they want to delete the oauth app. +func DeleteOAuthAppConfirmation(c *cli.Context, args deleteOAuthAppArgs) error { + if args.OrgID == "" { + return errors.New("cannot delete oauth app without an organization ID") + } + + if args.ClientID == "" { + return errors.New("cannot delete oauth app without a client ID") + } + + yellow := "\033[1;33m%s\033[0m" + printf(c.App.Writer, yellow, "WARNING!!\n") + printf(c.App.Writer, yellow, fmt.Sprintf("You are trying to delete an OAuth application with client ID %s. "+ + "Once deleted, any existing apps that rely on this OAuth application will no longer be able to authenticate users.\n", args.ClientID)) + printf(c.App.Writer, yellow, "Do you want to continue?") + printf(c.App.Writer, "Continue: y/n") + if err := c.Err(); err != nil { + return err + } + + rawInput, err := bufio.NewReader(c.App.Reader).ReadString('\n') + if err != nil { + return err + } + + input := strings.ToUpper(strings.TrimSpace(rawInput)) + if input != "Y" { + return errors.New("aborted") + } + return nil +} + +// DeleteOAuthAppAction is the corresponding action for 'oauth-app delete'. +func DeleteOAuthAppAction(c *cli.Context, args deleteOAuthAppArgs) error { + client, err := newViamClient(c) + if err != nil { + return err + } + + return client.deleteOAuthAppAction(c, args.OrgID, args.ClientID) +} + +func (c *viamClient) deleteOAuthAppAction(cCtx *cli.Context, orgID, clientID string) error { + if err := c.ensureLoggedIn(); err != nil { + return err + } + + req := &apppb.DeleteOAuthAppRequest{ + OrgId: orgID, + ClientId: clientID, + } + + _, err := c.client.DeleteOAuthApp(c.c.Context, req) + if err != nil { + return err + } + + printf(cCtx.App.Writer, "Successfully deleted OAuth application") + return nil +} diff --git a/cli/client_test.go b/cli/client_test.go index 1402ef6d2cc..ff2e6a58fd5 100644 --- a/cli/client_test.go +++ b/cli/client_test.go @@ -359,6 +359,24 @@ func TestGetLogoAction(t *testing.T) { test.That(t, out.messages[0], test.ShouldContainSubstring, "https://logo.com") } +func TestDeleteOAuthAppAction(t *testing.T) { + deleteOAuthAppFunc := func(ctx context.Context, in *apppb.DeleteOAuthAppRequest, opts ...grpc.CallOption) ( + *apppb.DeleteOAuthAppResponse, error, + ) { + return &apppb.DeleteOAuthAppResponse{}, nil + } + + asc := &inject.AppServiceClient{ + DeleteOAuthAppFunc: deleteOAuthAppFunc, + } + + cCtx, ac, out, errOut := setup(asc, nil, nil, nil, nil, "token") + test.That(t, ac.deleteOAuthAppAction(cCtx, "test-org", "client-id"), 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, "Successfully deleted OAuth application") +} + func TestUpdateBillingServiceAction(t *testing.T) { updateConfigFunc := func(ctx context.Context, in *apppb.UpdateBillingServiceRequest, opts ...grpc.CallOption) ( *apppb.UpdateBillingServiceResponse, error, diff --git a/testutils/inject/app_service_client.go b/testutils/inject/app_service_client.go index 45f35604723..5c7884e9af1 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) + DeleteOAuthAppFunc func(ctx context.Context, in *apppb.DeleteOAuthAppRequest, + opts ...grpc.CallOption) (*apppb.DeleteOAuthAppResponse, error) CreateLocationFunc func(ctx context.Context, in *apppb.CreateLocationRequest, opts ...grpc.CallOption) (*apppb.CreateLocationResponse, error) GetLocationFunc func(ctx context.Context, in *apppb.GetLocationRequest, @@ -403,6 +405,16 @@ func (asc *AppServiceClient) OrganizationGetLogo( return asc.OrganizationGetLogoFunc(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, +) (*apppb.DeleteOAuthAppResponse, error) { + if asc.DeleteOAuthAppFunc == nil { + return asc.AppServiceClient.DeleteOAuthApp(ctx, in, opts...) + } + return asc.DeleteOAuthAppFunc(ctx, in, opts...) +} + // CreateLocation calls the injected CreateLocationFunc or the real version. func (asc *AppServiceClient) CreateLocation( ctx context.Context, in *apppb.CreateLocationRequest, opts ...grpc.CallOption,