From c4310be7de8450eb7bc997613dd7b0af4f72fd84 Mon Sep 17 00:00:00 2001 From: Simone Gotti Date: Fri, 5 Apr 2019 15:01:57 +0200 Subject: [PATCH] Implement user token delete --- cmd/agola/cmd/userladelete.go | 6 +- cmd/agola/cmd/usertokendelete.go | 70 +++++++++++++++++++ internal/services/configstore/api/client.go | 4 ++ internal/services/configstore/api/user.go | 21 ++++++ .../services/configstore/command/command.go | 59 ++++++++++++++++ internal/services/configstore/configstore.go | 2 + internal/services/gateway/api/client.go | 4 ++ internal/services/gateway/api/user.go | 23 ++++++ internal/services/gateway/gateway.go | 2 + 9 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 cmd/agola/cmd/usertokendelete.go diff --git a/cmd/agola/cmd/userladelete.go b/cmd/agola/cmd/userladelete.go index f77ef82..4b404b2 100644 --- a/cmd/agola/cmd/userladelete.go +++ b/cmd/agola/cmd/userladelete.go @@ -17,9 +17,9 @@ package cmd import ( "context" - "github.com/pkg/errors" "github.com/sorintlab/agola/internal/services/gateway/api" + "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -58,13 +58,13 @@ func userLADelete(cmd *cobra.Command, args []string) error { userName := userLADeleteOpts.userName laID := userLADeleteOpts.laID - log.Infof("deleting linked account %s for user %q", userName) + log.Infof("deleting linked account %q for user %q", laID, userName) _, err := gwclient.DeleteUserLA(context.TODO(), userName, laID) if err != nil { return errors.Wrapf(err, "failed to delete linked account") } - log.Infof("linked account %q for user %q deleted", userName, laID) + log.Infof("linked account %q for user %q deleted", laID, userName) return nil } diff --git a/cmd/agola/cmd/usertokendelete.go b/cmd/agola/cmd/usertokendelete.go new file mode 100644 index 0000000..a9b4350 --- /dev/null +++ b/cmd/agola/cmd/usertokendelete.go @@ -0,0 +1,70 @@ +// Copyright 2019 Sorint.lab +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + + "github.com/sorintlab/agola/internal/services/gateway/api" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var cmdUserTokenDelete = &cobra.Command{ + Use: "delete", + Short: "delete a user token", + Run: func(cmd *cobra.Command, args []string) { + if err := userTokenDelete(cmd, args); err != nil { + log.Fatalf("err: %v", err) + } + }, +} + +type userTokenDeleteOptions struct { + userName string + tokenName string +} + +var userTokenDeleteOpts userTokenDeleteOptions + +func init() { + flags := cmdUserTokenDelete.Flags() + + flags.StringVarP(&userTokenDeleteOpts.userName, "username", "n", "", "user name") + flags.StringVarP(&userTokenDeleteOpts.tokenName, "tokenname", "t", "", "token name") + + cmdUserTokenDelete.MarkFlagRequired("username") + cmdUserTokenDelete.MarkFlagRequired("tokenname") + + cmdUserToken.AddCommand(cmdUserTokenDelete) +} + +func userTokenDelete(cmd *cobra.Command, args []string) error { + gwclient := api.NewClient(gatewayURL, token) + + userName := userTokenDeleteOpts.userName + tokenName := userTokenDeleteOpts.tokenName + + log.Infof("deleting token %q for user %q", tokenName, userName) + _, err := gwclient.DeleteUserToken(context.TODO(), userName, tokenName) + if err != nil { + return errors.Wrapf(err, "failed to delete user token") + } + + log.Infof("token %q for user %q deleted", tokenName, userName) + + return nil +} diff --git a/internal/services/configstore/api/client.go b/internal/services/configstore/api/client.go index 78f7c19..0a1d759 100644 --- a/internal/services/configstore/api/client.go +++ b/internal/services/configstore/api/client.go @@ -383,6 +383,10 @@ func (c *Client) CreateUserToken(ctx context.Context, userName string, req *Crea return tresp, resp, err } +func (c *Client) DeleteUserToken(ctx context.Context, userName, tokenName string) (*http.Response, error) { + return c.getResponse(ctx, "DELETE", fmt.Sprintf("/users/%s/tokens/%s", userName, tokenName), nil, jsonContent, nil) +} + func (c *Client) GetRemoteSource(ctx context.Context, rsID string) (*types.RemoteSource, *http.Response, error) { rs := new(types.RemoteSource) resp, err := c.getParsedResponse(ctx, "GET", fmt.Sprintf("/remotesource/%s", rsID), nil, jsonContent, nil, rs) diff --git a/internal/services/configstore/api/user.go b/internal/services/configstore/api/user.go index f294b1d..2d5d7d7 100644 --- a/internal/services/configstore/api/user.go +++ b/internal/services/configstore/api/user.go @@ -471,3 +471,24 @@ func (h *CreateUserTokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques return } } + +type DeleteUserTokenHandler struct { + log *zap.SugaredLogger + ch *command.CommandHandler +} + +func NewDeleteUserTokenHandler(logger *zap.Logger, ch *command.CommandHandler) *DeleteUserTokenHandler { + return &DeleteUserTokenHandler{log: logger.Sugar(), ch: ch} +} + +func (h *DeleteUserTokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + vars := mux.Vars(r) + userName := vars["username"] + tokenName := vars["tokenname"] + + err := h.ch.DeleteUserToken(ctx, userName, tokenName) + if httpError(w, err) { + h.log.Errorf("err: %+v", err) + } +} diff --git a/internal/services/configstore/command/command.go b/internal/services/configstore/command/command.go index 61e6450..55e0cd2 100644 --- a/internal/services/configstore/command/command.go +++ b/internal/services/configstore/command/command.go @@ -686,6 +686,65 @@ func (s *CommandHandler) CreateUserToken(ctx context.Context, userName, tokenNam return token, err } +func (s *CommandHandler) DeleteUserToken(ctx context.Context, userName, tokenName string) error { + if userName == "" { + return util.NewErrBadRequest(errors.Errorf("user name required")) + } + if tokenName == "" { + return util.NewErrBadRequest(errors.Errorf("token name required")) + } + + var user *types.User + + var cgt *wal.ChangeGroupsUpdateToken + + // must do all the check in a single transaction to avoid concurrent changes + err := s.readDB.Do(func(tx *db.Tx) error { + var err error + user, err = s.readDB.GetUserByName(tx, userName) + if err != nil { + return err + } + if user == nil { + return util.NewErrBadRequest(errors.Errorf("user %q doesn't exist", userName)) + } + + cgNames := []string{user.ID} + cgt, err = s.readDB.GetChangeGroupsUpdateTokens(tx, cgNames) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return err + } + + _, ok := user.Tokens[tokenName] + if !ok { + return util.NewErrBadRequest(errors.Errorf("token %q for user %q doesn't exist", tokenName, userName)) + } + + delete(user.Tokens, tokenName) + + userj, err := json.Marshal(user) + if err != nil { + return errors.Wrapf(err, "failed to marshal user") + } + actions := []*wal.Action{ + { + ActionType: wal.ActionTypePut, + DataType: string(types.ConfigTypeUser), + ID: user.ID, + Data: userj, + }, + } + + _, err = s.wal.WriteWal(ctx, actions, cgt) + return err +} + func (s *CommandHandler) CreateRemoteSource(ctx context.Context, remoteSource *types.RemoteSource) (*types.RemoteSource, error) { if remoteSource.Name == "" { return nil, util.NewErrBadRequest(errors.Errorf("remotesource name required")) diff --git a/internal/services/configstore/configstore.go b/internal/services/configstore/configstore.go index 374b995..87fe200 100644 --- a/internal/services/configstore/configstore.go +++ b/internal/services/configstore/configstore.go @@ -144,6 +144,7 @@ func (s *ConfigStore) Run(ctx context.Context) error { updateUserLAHandler := api.NewUpdateUserLAHandler(logger, s.ch) createUserTokenHandler := api.NewCreateUserTokenHandler(logger, s.ch) + deleteUserTokenHandler := api.NewDeleteUserTokenHandler(logger, s.ch) orgHandler := api.NewOrgHandler(logger, s.readDB) orgsHandler := api.NewOrgsHandler(logger, s.readDB) @@ -193,6 +194,7 @@ func (s *ConfigStore) Run(ctx context.Context) error { apirouter.Handle("/users/{username}/linkedaccounts/{laid}", deleteUserLAHandler).Methods("DELETE") apirouter.Handle("/users/{username}/linkedaccounts/{laid}", updateUserLAHandler).Methods("PUT") apirouter.Handle("/users/{username}/tokens", createUserTokenHandler).Methods("PUT") + apirouter.Handle("/users/{username}/tokens/{tokenname}", deleteUserTokenHandler).Methods("DELETE") apirouter.Handle("/org/{orgid}", orgHandler).Methods("GET") apirouter.Handle("/orgs", orgsHandler).Methods("GET") diff --git a/internal/services/gateway/api/client.go b/internal/services/gateway/api/client.go index 8713819..2f0f123 100644 --- a/internal/services/gateway/api/client.go +++ b/internal/services/gateway/api/client.go @@ -306,6 +306,10 @@ func (c *Client) CreateUserToken(ctx context.Context, userName string, req *Crea return tresp, resp, err } +func (c *Client) DeleteUserToken(ctx context.Context, userName, tokenName string) (*http.Response, error) { + return c.getResponse(ctx, "DELETE", fmt.Sprintf("/users/%s/tokens/%s", userName, tokenName), nil, jsonContent, nil) +} + func (c *Client) GetRun(ctx context.Context, runID string) (*RunResponse, *http.Response, error) { run := new(RunResponse) resp, err := c.getParsedResponse(ctx, "GET", fmt.Sprintf("/run/%s", runID), nil, jsonContent, nil, run) diff --git a/internal/services/gateway/api/user.go b/internal/services/gateway/api/user.go index bbf6612..c559393 100644 --- a/internal/services/gateway/api/user.go +++ b/internal/services/gateway/api/user.go @@ -416,6 +416,29 @@ func (h *CreateUserTokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques } } +type DeleteUserTokenHandler struct { + log *zap.SugaredLogger + configstoreClient *csapi.Client +} + +func NewDeleteUserTokenHandler(logger *zap.Logger, configstoreClient *csapi.Client) *DeleteUserTokenHandler { + return &DeleteUserTokenHandler{log: logger.Sugar(), configstoreClient: configstoreClient} +} + +func (h *DeleteUserTokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + vars := mux.Vars(r) + userName := vars["username"] + tokenName := vars["tokenname"] + + h.log.Infof("deleting user %q token %q", userName, tokenName) + _, err := h.configstoreClient.DeleteUserToken(ctx, userName, tokenName) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + type RegisterUserRequest struct { CreateUserRequest CreateUserLARequest diff --git a/internal/services/gateway/gateway.go b/internal/services/gateway/gateway.go index 9b7c01c..775d0f5 100644 --- a/internal/services/gateway/gateway.go +++ b/internal/services/gateway/gateway.go @@ -175,6 +175,7 @@ func (g *Gateway) Run(ctx context.Context) error { createUserLAHandler := api.NewCreateUserLAHandler(logger, g.ch) deleteUserLAHandler := api.NewDeleteUserLAHandler(logger, g.configstoreClient) createUserTokenHandler := api.NewCreateUserTokenHandler(logger, g.configstoreClient) + deleteUserTokenHandler := api.NewDeleteUserTokenHandler(logger, g.configstoreClient) remoteSourceHandler := api.NewRemoteSourceHandler(logger, g.configstoreClient) createRemoteSourceHandler := api.NewCreateRemoteSourceHandler(logger, g.configstoreClient) @@ -247,6 +248,7 @@ func (g *Gateway) Run(ctx context.Context) error { apirouter.Handle("/users/{username}/linkedaccounts", authForcedHandler(createUserLAHandler)).Methods("PUT") apirouter.Handle("/users/{username}/linkedaccounts/{laid}", authForcedHandler(deleteUserLAHandler)).Methods("DELETE") apirouter.Handle("/users/{username}/tokens", authForcedHandler(createUserTokenHandler)).Methods("PUT") + apirouter.Handle("/users/{username}/tokens/{tokenname}", authForcedHandler(deleteUserTokenHandler)).Methods("DELETE") apirouter.Handle("/remotesource/{id}", authForcedHandler(remoteSourceHandler)).Methods("GET") apirouter.Handle("/remotesources", authForcedHandler(createRemoteSourceHandler)).Methods("PUT")