diff --git a/components/gitpod-db/go/personal_access_token.go b/components/gitpod-db/go/personal_access_token.go index 6df7410c2f7fda..dc9c5c0b01b99f 100644 --- a/components/gitpod-db/go/personal_access_token.go +++ b/components/gitpod-db/go/personal_access_token.go @@ -36,14 +36,17 @@ func (d *PersonalAccessToken) TableName() string { return "d_b_personal_access_token" } -func GetToken(ctx context.Context, conn *gorm.DB, id uuid.UUID) (PersonalAccessToken, error) { +func GetPersonalAccessTokenForUser(ctx context.Context, conn *gorm.DB, tokenID uuid.UUID, userID uuid.UUID) (PersonalAccessToken, error) { var token PersonalAccessToken db := conn.WithContext(ctx) - db = db.Where("id = ?", id).First(&token) + db = db.Where("id = ?", tokenID).Where("userId = ?", userID).Where("deleted = ?", 0).First(&token) if db.Error != nil { - return PersonalAccessToken{}, fmt.Errorf("Failed to retrieve token: %w", db.Error) + if errors.Is(db.Error, gorm.ErrRecordNotFound) { + return PersonalAccessToken{}, fmt.Errorf("Token with ID %s does not exist: %w", tokenID, ErrorNotFound) + } + return PersonalAccessToken{}, fmt.Errorf("Failed to retrieve token: %v", db.Error) } return token, nil @@ -95,6 +98,7 @@ func ListPersonalAccessTokensForUser(ctx context.Context, conn *gorm.DB, userID WithContext(ctx). Table((&PersonalAccessToken{}).TableName()). Where("userId = ?", userID). + Where("deleted = ?", 0). Order("createdAt"). Scopes(Paginate(pagination)). Find(&results) diff --git a/components/gitpod-db/go/personal_access_token_test.go b/components/gitpod-db/go/personal_access_token_test.go index 0ef84e888812e7..748fe3500b6f8d 100644 --- a/components/gitpod-db/go/personal_access_token_test.go +++ b/components/gitpod-db/go/personal_access_token_test.go @@ -19,24 +19,38 @@ import ( func TestPersonalAccessToken_Get(t *testing.T) { conn := dbtest.ConnectForTests(t) - token := db.PersonalAccessToken{ - ID: uuid.New(), - UserID: uuid.New(), - Hash: "some-secure-hash", - Name: "some-name", - Description: "some-description", - Scopes: []string{"read", "write"}, - ExpirationTime: time.Now().Add(5), - CreatedAt: time.Now(), - LastModified: time.Now(), - } + firstUserId := uuid.New() + secondUserId := uuid.New() - tx := conn.Create(token) - require.NoError(t, tx.Error) + 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) { + _, err := db.GetPersonalAccessTokenForUser(context.Background(), conn, token.ID, token2.UserID) + require.Error(t, err, db.ErrorNotFound) + }) + + t.Run("not matching token", func(t *testing.T) { + _, err := db.GetPersonalAccessTokenForUser(context.Background(), conn, token2.ID, token.UserID) + require.Error(t, err, db.ErrorNotFound) + }) + + t.Run("both token and user don't exist in the DB", func(t *testing.T) { + _, err := db.GetPersonalAccessTokenForUser(context.Background(), conn, uuid.New(), uuid.New()) + require.Error(t, err, db.ErrorNotFound) + }) + + t.Run("valid", func(t *testing.T) { + returned, err := db.GetPersonalAccessTokenForUser(context.Background(), conn, token.ID, token.UserID) + require.NoError(t, err) + require.Equal(t, token.ID, returned.ID) + require.Equal(t, token.UserID, returned.UserID) + }) - result, err := db.GetToken(context.Background(), conn, token.ID) - require.NoError(t, err) - require.Equal(t, token.ID, result.ID) } func TestPersonalAccessToken_Create(t *testing.T) { diff --git a/components/public-api-server/pkg/apiv1/tokens.go b/components/public-api-server/pkg/apiv1/tokens.go index c58bc9210a3b2d..67e85ecb48fafd 100644 --- a/components/public-api-server/pkg/apiv1/tokens.go +++ b/components/public-api-server/pkg/apiv1/tokens.go @@ -10,7 +10,7 @@ import ( "fmt" "strings" - "github.com/bufbuild/connect-go" + connect "github.com/bufbuild/connect-go" "github.com/gitpod-io/gitpod/common-go/experiments" "github.com/gitpod-io/gitpod/common-go/log" db "github.com/gitpod-io/gitpod/components/gitpod-db/go" @@ -113,13 +113,22 @@ func (s *TokensService) GetPersonalAccessToken(ctx context.Context, req *connect return nil, err } - _, _, err = s.getUser(ctx, conn) + _, userId, err := s.getUser(ctx, conn) if err != nil { + if errors.Is(err, db.ErrorNotFound) { + return nil, connect.NewError(connect.CodeNotFound, err) + } return nil, err } log.Infof("Handling GetPersonalAccessToken request for Token ID '%s'", tokenID.String()) - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("gitpod.experimental.v1.TokensService.GetPersonalAccessToken is not implemented")) + + token, err := db.GetPersonalAccessTokenForUser(ctx, s.dbConn, tokenID, userId) + if err != nil { + return nil, err + } + + return connect.NewResponse(&v1.GetPersonalAccessTokenResponse{Token: personalAccessTokenToAPI(token, "")}), nil } func (s *TokensService) ListPersonalAccessTokens(ctx context.Context, req *connect.Request[v1.ListPersonalAccessTokensRequest]) (*connect.Response[v1.ListPersonalAccessTokensResponse], error) { diff --git a/components/public-api-server/pkg/apiv1/tokens_test.go b/components/public-api-server/pkg/apiv1/tokens_test.go index b8a17d56bfa34c..8813f1d0d2925d 100644 --- a/components/public-api-server/pkg/apiv1/tokens_test.go +++ b/components/public-api-server/pkg/apiv1/tokens_test.go @@ -6,6 +6,7 @@ package apiv1 import ( "context" + "fmt" "net/http" "net/http/httptest" "testing" @@ -127,7 +128,7 @@ func TestTokensService_CreatePersonalAccessTokenWithoutFeatureFlag(t *testing.T) require.NoError(t, err) // token must exist in the DB, with the User ID of the requestor - storedInDB, err := db.GetToken(context.Background(), dbConn, uuid.MustParse(created.GetId())) + storedInDB, err := db.GetPersonalAccessTokenForUser(context.Background(), dbConn, uuid.MustParse(created.GetId()), uuid.MustParse(user.ID)) require.NoError(t, err) require.Equal(t, user.ID, storedInDB.UserID.String()) }) @@ -135,18 +136,31 @@ func TestTokensService_CreatePersonalAccessTokenWithoutFeatureFlag(t *testing.T) func TestTokensService_GetPersonalAccessToken(t *testing.T) { user := newUser(&protocol.User{}) + user2 := newUser(&protocol.User{}) - t.Run("permission denied when feature flag is disabled", func(t *testing.T) { - serverMock, _, client := setupTokensService(t, withTokenFeatureDisabled) + t.Run("get correct token", func(t *testing.T) { + serverMock, dbConn, client := setupTokensService(t, withTokenFeatureEnabled) + + tokens := dbtest.CreatePersonalAccessTokenRecords(t, dbConn, + dbtest.NewPersonalAccessToken(t, db.PersonalAccessToken{ + UserID: uuid.MustParse(user.ID), + }), + dbtest.NewPersonalAccessToken(t, db.PersonalAccessToken{ + UserID: uuid.MustParse(user2.ID), + }), + ) serverMock.EXPECT().GetLoggedInUser(gomock.Any()).Return(user, nil) - _, err := client.GetPersonalAccessToken(context.Background(), connect.NewRequest(&v1.GetPersonalAccessTokenRequest{ - Id: uuid.New().String(), + response, err := client.GetPersonalAccessToken(context.Background(), connect.NewRequest(&v1.GetPersonalAccessTokenRequest{ + Id: tokens[0].ID.String(), })) - require.Error(t, err, "This feature is currently in beta. If you would like to be part of the beta, please contact us.") - require.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err)) + require.NoError(t, err) + + requireEqualProto(t, &v1.GetPersonalAccessTokenResponse{ + Token: personalAccessTokenToAPI(tokens[0], ""), + }, response.Msg) }) t.Run("invalid argument when Token ID is empty", func(t *testing.T) { @@ -167,16 +181,43 @@ func TestTokensService_GetPersonalAccessToken(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("responds with not found when token is not found", func(t *testing.T) { + serverMock, dbConn, client := setupTokensService(t, withTokenFeatureEnabled) + + someTokenId := uuid.New().String() + + dbtest.CreatePersonalAccessTokenRecords(t, dbConn, + dbtest.NewPersonalAccessToken(t, db.PersonalAccessToken{ + UserID: uuid.MustParse(user.ID), + }), + ) serverMock.EXPECT().GetLoggedInUser(gomock.Any()).Return(user, nil) _, err := client.GetPersonalAccessToken(context.Background(), connect.NewRequest(&v1.GetPersonalAccessTokenRequest{ - Id: uuid.New().String(), + Id: someTokenId, })) - require.Equal(t, connect.CodeUnimplemented, connect.CodeOf(err)) + require.Error(t, err, fmt.Errorf("Token with ID %s does not exist: not found", someTokenId)) + }) + + t.Run("permission denied when feature flag disabled", func(t *testing.T) { + serverMock, dbConn, client := setupTokensService(t, withTokenFeatureDisabled) + + tokens := dbtest.CreatePersonalAccessTokenRecords(t, dbConn, + dbtest.NewPersonalAccessToken(t, db.PersonalAccessToken{ + UserID: uuid.MustParse(user.ID), + }), + ) + + serverMock.EXPECT().GetLoggedInUser(gomock.Any()).Return(user, nil) + + _, err := client.GetPersonalAccessToken(context.Background(), connect.NewRequest(&v1.GetPersonalAccessTokenRequest{ + Id: tokens[0].ID.String(), + })) + + require.Error(t, err, "This feature is currently in beta. If you would like to be part of the beta, please contact us.") + require.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err)) }) }