From e328b1f288e8816ea77c4b8c936b4f38bfab6642 Mon Sep 17 00:00:00 2001 From: Jean Pierre Date: Wed, 23 Nov 2022 07:39:48 +0000 Subject: [PATCH] [public-api] delete personal access token Co-authored-by: Milan Pavlik Co-authored-by: mustard --- .../gitpod-db/go/personal_access_token.go | 28 +++++++++++++ .../go/personal_access_token_test.go | 40 +++++++++++++++++++ .../public-api-server/pkg/apiv1/tokens.go | 11 +++-- .../pkg/apiv1/tokens_test.go | 29 ++++++++++---- 4 files changed, 98 insertions(+), 10 deletions(-) diff --git a/components/gitpod-db/go/personal_access_token.go b/components/gitpod-db/go/personal_access_token.go index d557cc1eb1201a..32b1988fa5ebc9 100644 --- a/components/gitpod-db/go/personal_access_token.go +++ b/components/gitpod-db/go/personal_access_token.go @@ -119,6 +119,34 @@ func UpdatePersonalAccessTokenHash(ctx context.Context, conn *gorm.DB, tokenID u return GetPersonalAccessTokenForUser(ctx, conn, tokenID, userID) } +func DeletePersonalAccessTokenForUser(ctx context.Context, conn *gorm.DB, tokenID uuid.UUID, userID uuid.UUID) (int64, error) { + if tokenID == uuid.Nil { + return 0, fmt.Errorf("Invalid or empty tokenID") + } + + if userID == uuid.Nil { + return 0, fmt.Errorf("Invalid or empty userID") + } + + db := conn.WithContext(ctx) + + db = db. + Table((&PersonalAccessToken{}).TableName()). + Where("id = ?", tokenID). + Where("userId = ?", userID). + Where("deleted = ?", 0). + Update("deleted", 1) + if db.Error != nil { + return 0, fmt.Errorf("failed to delete token (ID: %s): %v", tokenID.String(), db.Error) + } + + if db.RowsAffected == 0 { + return 0, fmt.Errorf("token (ID: %s) for user (ID: %s) does not exist: %w", tokenID, userID, ErrorNotFound) + } + + return db.RowsAffected, nil +} + func ListPersonalAccessTokensForUser(ctx context.Context, conn *gorm.DB, userID uuid.UUID, pagination Pagination) (*PaginatedResult[PersonalAccessToken], error) { if userID == uuid.Nil { return nil, fmt.Errorf("user ID is a required argument to list personal access tokens for user, got nil") diff --git a/components/gitpod-db/go/personal_access_token_test.go b/components/gitpod-db/go/personal_access_token_test.go index 55454fd23823dd..4401c638e25bb7 100644 --- a/components/gitpod-db/go/personal_access_token_test.go +++ b/components/gitpod-db/go/personal_access_token_test.go @@ -119,6 +119,46 @@ func TestPersonalAccessToken_UpdateHash(t *testing.T) { }) } +func TestPersonalAccessToken_Delete(t *testing.T) { + conn := dbtest.ConnectForTests(t) + + firstUserId := uuid.New() + secondUserId := uuid.New() + + token := dbtest.NewPersonalAccessToken(t, db.PersonalAccessToken{UserID: firstUserId}) + token2 := dbtest.NewPersonalAccessToken(t, db.PersonalAccessToken{UserID: secondUserId}) + + tokenEntries := []db.PersonalAccessToken{token, token2} + + dbtest.CreatePersonalAccessTokenRecords(t, conn, tokenEntries...) + + t.Run("not matching user", func(t *testing.T) { + count, err := db.DeletePersonalAccessTokenForUser(context.Background(), conn, token.ID, token2.UserID) + require.Error(t, err, db.ErrorNotFound) + require.Equal(t, int64(0), count) + }) + + t.Run("not matching token", func(t *testing.T) { + count, err := db.DeletePersonalAccessTokenForUser(context.Background(), conn, token2.ID, token.UserID) + require.Error(t, err, db.ErrorNotFound) + require.Equal(t, int64(0), count) + }) + + t.Run("both token and user don't exist in the DB", func(t *testing.T) { + count, err := db.DeletePersonalAccessTokenForUser(context.Background(), conn, uuid.New(), uuid.New()) + require.Error(t, err, db.ErrorNotFound) + require.Equal(t, int64(0), count) + }) + + t.Run("valid", func(t *testing.T) { + count, err := db.DeletePersonalAccessTokenForUser(context.Background(), conn, token.ID, token.UserID) + require.NoError(t, err) + require.Equal(t, int64(1), count) + _, err = db.GetPersonalAccessTokenForUser(context.Background(), conn, token.ID, token.UserID) + require.Error(t, err, db.ErrorNotFound) + }) +} + func TestListPersonalAccessTokensForUser(t *testing.T) { ctx := context.Background() conn := dbtest.ConnectForTests(t) diff --git a/components/public-api-server/pkg/apiv1/tokens.go b/components/public-api-server/pkg/apiv1/tokens.go index b9a461916ee74a..d450c48cf175fc 100644 --- a/components/public-api-server/pkg/apiv1/tokens.go +++ b/components/public-api-server/pkg/apiv1/tokens.go @@ -227,13 +227,18 @@ func (s *TokensService) DeletePersonalAccessToken(ctx context.Context, req *conn return nil, err } - _, _, err = s.getUser(ctx, conn) + _, userID, err := s.getUser(ctx, conn) if err != nil { return nil, err } - log.Infof("Handling DeletePersonalAccessToken request for Token ID '%s'", tokenID.String()) - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("gitpod.experimental.v1.TokensService.DeletePersonalAccessToken is not implemented")) + _, err = db.DeletePersonalAccessTokenForUser(ctx, s.dbConn, tokenID, userID) + if err != nil { + log.WithError(err).Errorf("failed to delete personal access token (ID: %s) for user %s", tokenID.String(), userID.String()) + return nil, connect.NewError(connect.CodeInternal, errors.New("Failed to delete personal access token.")) + } + + return connect.NewResponse(&v1.DeletePersonalAccessTokenResponse{}), nil } func (s *TokensService) getUser(ctx context.Context, conn protocol.APIInterface) (*protocol.User, uuid.UUID, error) { diff --git a/components/public-api-server/pkg/apiv1/tokens_test.go b/components/public-api-server/pkg/apiv1/tokens_test.go index 08fd2899ee94eb..aaf8e9a57079f0 100644 --- a/components/public-api-server/pkg/apiv1/tokens_test.go +++ b/components/public-api-server/pkg/apiv1/tokens_test.go @@ -513,16 +513,31 @@ func TestTokensService_DeletePersonalAccessToken(t *testing.T) { require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) }) - t.Run("unimplemented when feature flag enabled", func(t *testing.T) { - serverMock, _, client := setupTokensService(t, withTokenFeatureEnabled) + t.Run("delete token", func(t *testing.T) { + serverMock, dbConn, client := setupTokensService(t, withTokenFeatureEnabled) - serverMock.EXPECT().GetLoggedInUser(gomock.Any()).Return(user, nil) + tokens := dbtest.CreatePersonalAccessTokenRecords(t, dbConn, + dbtest.NewPersonalAccessToken(t, db.PersonalAccessToken{ + UserID: uuid.MustParse(user.ID), + }), + ) - _, err := client.DeletePersonalAccessToken(context.Background(), connect.NewRequest(&v1.DeletePersonalAccessTokenRequest{ - Id: uuid.New().String(), + serverMock.EXPECT().GetLoggedInUser(gomock.Any()).Return(user, nil).Times(3) + + _, err := client.GetPersonalAccessToken(context.Background(), connect.NewRequest(&v1.GetPersonalAccessTokenRequest{ + Id: tokens[0].ID.String(), })) + require.NoError(t, err) - require.Equal(t, connect.CodeUnimplemented, connect.CodeOf(err)) + _, err = client.DeletePersonalAccessToken(context.Background(), connect.NewRequest(&v1.DeletePersonalAccessTokenRequest{ + Id: tokens[0].ID.String(), + })) + require.NoError(t, err) + + _, err = client.GetPersonalAccessToken(context.Background(), connect.NewRequest(&v1.GetPersonalAccessTokenRequest{ + Id: tokens[0].ID.String(), + })) + require.Error(t, err, fmt.Errorf("Token with ID %s does not exist: not found", tokens[0].ID.String())) }) } @@ -578,7 +593,7 @@ func TestTokensService_Workflow(t *testing.T) { _, err = client.DeletePersonalAccessToken(ctx, connect.NewRequest(&v1.DeletePersonalAccessTokenRequest{ Id: secondTokenResponse.Msg.GetToken().GetId(), })) - require.Error(t, err, "currently unimplemented") + require.NoError(t, err) } func setupTokensService(t *testing.T, expClient experiments.Client) (*protocol.MockAPIInterface, *gorm.DB, v1connect.TokensServiceClient) {