Skip to content

Commit 6c8676b

Browse files
jeanp413mustard-mh
authored andcommitted
WIP: public api regenerate
1 parent 28ba48f commit 6c8676b

File tree

5 files changed

+158
-9
lines changed

5 files changed

+158
-9
lines changed

components/gitpod-db/go/dbtest/conn.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func ConnectForTests(t *testing.T) *gorm.DB {
3737
Host: "localhost:23306",
3838
Database: "gitpod",
3939
})
40-
require.NoError(t, err, "Failed to establish connection to In a workspace, run `leeway build components/gitpod-db/go:init-testdb` once to bootstrap the ")
40+
require.NoError(t, err, "Failed to establish connection to In a workspace, run `leeway build components/gitpod-db/go:init-testdb` once to bootstrap the db")
4141

4242
return conn
4343
}

components/gitpod-db/go/personal_access_token.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,38 @@ func CreatePersonalAccessToken(ctx context.Context, conn *gorm.DB, req PersonalA
8787
return token, nil
8888
}
8989

90+
func UpdatePersonalAccessTokenHash(ctx context.Context, conn *gorm.DB, tokenID uuid.UUID, userID uuid.UUID, hash string, expirationTime time.Time) (PersonalAccessToken, error) {
91+
if tokenID == uuid.Nil {
92+
return PersonalAccessToken{}, fmt.Errorf("Invalid or empty tokenID")
93+
}
94+
if userID == uuid.Nil {
95+
return PersonalAccessToken{}, fmt.Errorf("Invalid or empty userID")
96+
}
97+
if hash == "" {
98+
return PersonalAccessToken{}, fmt.Errorf("Token hash required")
99+
}
100+
if expirationTime.IsZero() {
101+
return PersonalAccessToken{}, fmt.Errorf("Expiration time required")
102+
}
103+
104+
db := conn.WithContext(ctx)
105+
106+
err := db.
107+
Where("id = ?", tokenID).
108+
Where("userId = ?", userID).
109+
Where("deleted = ?", 0).
110+
Select("hash", "expirationTime").Updates(PersonalAccessToken{Hash: hash, ExpirationTime: expirationTime}).
111+
Error
112+
if err != nil {
113+
if errors.Is(db.Error, gorm.ErrRecordNotFound) {
114+
return PersonalAccessToken{}, fmt.Errorf("Token with ID %s does not exist: %w", tokenID, ErrorNotFound)
115+
}
116+
return PersonalAccessToken{}, fmt.Errorf("Failed to update token: %v", db.Error)
117+
}
118+
119+
return GetPersonalAccessTokenForUser(ctx, conn, tokenID, userID)
120+
}
121+
90122
func ListPersonalAccessTokensForUser(ctx context.Context, conn *gorm.DB, userID uuid.UUID, pagination Pagination) (*PaginatedResult[PersonalAccessToken], error) {
91123
if userID == uuid.Nil {
92124
return nil, fmt.Errorf("user ID is a required argument to list personal access tokens for user, got nil")

components/gitpod-db/go/personal_access_token_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,51 @@ func TestPersonalAccessToken_Create(t *testing.T) {
7474
require.Equal(t, request.ID, result.ID)
7575
}
7676

77+
func TestPersonalAccessToken_UpdateHash(t *testing.T) {
78+
conn := dbtest.ConnectForTests(t)
79+
80+
firstUserId := uuid.New()
81+
secondUserId := uuid.New()
82+
83+
token := dbtest.NewPersonalAccessToken(t, db.PersonalAccessToken{UserID: firstUserId})
84+
token2 := dbtest.NewPersonalAccessToken(t, db.PersonalAccessToken{UserID: secondUserId})
85+
86+
tokenEntries := []db.PersonalAccessToken{token, token2}
87+
88+
dbtest.CreatePersonalAccessTokenRecords(t, conn, tokenEntries...)
89+
90+
var newHash = "another-secure-hash"
91+
var newExpirationTime = time.Now().Add(24 * time.Hour).UTC().Truncate(time.Millisecond)
92+
93+
t.Run("not matching user", func(t *testing.T) {
94+
_, err := db.UpdatePersonalAccessTokenHash(context.Background(), conn, token.ID, token2.UserID, newHash, newExpirationTime)
95+
require.Error(t, err, db.ErrorNotFound)
96+
})
97+
98+
t.Run("not matching token", func(t *testing.T) {
99+
_, err := db.UpdatePersonalAccessTokenHash(context.Background(), conn, token2.ID, token.UserID, newHash, newExpirationTime)
100+
require.Error(t, err, db.ErrorNotFound)
101+
})
102+
103+
t.Run("both token and user don't exist in the DB", func(t *testing.T) {
104+
_, err := db.UpdatePersonalAccessTokenHash(context.Background(), conn, uuid.New(), uuid.New(), newHash, newExpirationTime)
105+
require.Error(t, err, db.ErrorNotFound)
106+
})
107+
108+
t.Run("valid", func(t *testing.T) {
109+
returned, err := db.UpdatePersonalAccessTokenHash(context.Background(), conn, token.ID, token.UserID, newHash, newExpirationTime)
110+
require.NoError(t, err)
111+
require.Equal(t, token.ID, returned.ID)
112+
require.Equal(t, token.UserID, returned.UserID)
113+
require.Equal(t, newHash, returned.Hash)
114+
require.Equal(t, token.Name, returned.Name)
115+
require.Equal(t, token.Description, returned.Description)
116+
require.Equal(t, token.Scopes, returned.Scopes)
117+
require.Equal(t, newExpirationTime, returned.ExpirationTime)
118+
require.Equal(t, token.CreatedAt, returned.CreatedAt)
119+
})
120+
}
121+
77122
func TestListPersonalAccessTokensForUser(t *testing.T) {
78123
ctx := context.Background()
79124
conn := dbtest.ConnectForTests(t)

components/public-api-server/pkg/apiv1/tokens.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,18 +159,41 @@ func (s *TokensService) RegeneratePersonalAccessToken(ctx context.Context, req *
159159
return nil, err
160160
}
161161

162+
expiry := req.Msg.GetExpirationTime()
163+
if !expiry.IsValid() {
164+
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("Received invalid Expiration Time, it is a required parameter."))
165+
}
166+
162167
conn, err := getConnection(ctx, s.connectionPool)
163168
if err != nil {
164169
return nil, err
165170
}
166171

167-
_, _, err = s.getUser(ctx, conn)
172+
_, userID, err := s.getUser(ctx, conn)
168173
if err != nil {
169174
return nil, err
170175
}
176+
pat, err := auth.GeneratePersonalAccessToken(s.signer)
177+
if err != nil {
178+
log.WithError(err).Errorf("Failed to regenerate personal access token for user %s", userID.String())
179+
return nil, connect.NewError(connect.CodeInternal, errors.New("Failed to regenerate personal access token."))
180+
}
171181

172-
log.Infof("Handling RegeneratePersonalAccessToken request for Token ID '%s'", tokenID.String())
173-
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("gitpod.experimental.v1.TokensService.RegeneratePersonalAccessToken is not implemented"))
182+
hash, err := pat.ValueHash()
183+
if err != nil {
184+
log.WithError(err).Errorf("Failed to regenerate personal access token value hash for user %s", userID.String())
185+
return nil, connect.NewError(connect.CodeInternal, errors.New("Failed to compute personal access token hash."))
186+
}
187+
188+
token, err := db.UpdatePersonalAccessTokenHash(ctx, s.dbConn, tokenID, userID, hash, expiry.AsTime().UTC())
189+
if err != nil {
190+
log.WithError(err).Errorf("Failed to store personal access token for user %s", userID.String())
191+
return nil, connect.NewError(connect.CodeInternal, errors.New("Failed to store personal access token."))
192+
}
193+
194+
return connect.NewResponse(&v1.RegeneratePersonalAccessTokenResponse{
195+
Token: personalAccessTokenToAPI(token, pat.String()),
196+
}), nil
174197
}
175198

176199
func (s *TokensService) UpdatePersonalAccessToken(ctx context.Context, req *connect.Request[v1.UpdatePersonalAccessTokenRequest]) (*connect.Response[v1.UpdatePersonalAccessTokenResponse], error) {

components/public-api-server/pkg/apiv1/tokens_test.go

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -332,14 +332,16 @@ func TestTokensService_ListPersonalAccessTokens(t *testing.T) {
332332

333333
func TestTokensService_RegeneratePersonalAccessToken(t *testing.T) {
334334
user := newUser(&protocol.User{})
335+
user2 := newUser(&protocol.User{})
335336

336337
t.Run("permission denied when feature flag is disabled", func(t *testing.T) {
337338
serverMock, _, client := setupTokensService(t, withTokenFeatureDisabled)
338339

339340
serverMock.EXPECT().GetLoggedInUser(gomock.Any()).Return(user, nil)
340341

341342
_, err := client.RegeneratePersonalAccessToken(context.Background(), connect.NewRequest(&v1.RegeneratePersonalAccessTokenRequest{
342-
Id: uuid.New().String(),
343+
Id: uuid.New().String(),
344+
ExpirationTime: timestamppb.Now(),
343345
}))
344346

345347
require.Error(t, err, "This feature is currently in beta. If you would like to be part of the beta, please contact us.")
@@ -364,16 +366,63 @@ func TestTokensService_RegeneratePersonalAccessToken(t *testing.T) {
364366
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
365367
})
366368

367-
t.Run("unimplemented when feature flag enabled", func(t *testing.T) {
368-
serverMock, _, client := setupTokensService(t, withTokenFeatureEnabled)
369+
t.Run("responds with not found when token is not found", func(t *testing.T) {
370+
serverMock, dbConn, client := setupTokensService(t, withTokenFeatureEnabled)
371+
372+
someTokenId := uuid.New().String()
373+
374+
dbtest.CreatePersonalAccessTokenRecords(t, dbConn,
375+
dbtest.NewPersonalAccessToken(t, db.PersonalAccessToken{
376+
UserID: uuid.MustParse(user.ID),
377+
}),
378+
dbtest.NewPersonalAccessToken(t, db.PersonalAccessToken{
379+
UserID: uuid.MustParse(user2.ID),
380+
}),
381+
)
369382

370383
serverMock.EXPECT().GetLoggedInUser(gomock.Any()).Return(user, nil)
371384

385+
newTimestamp := timestamppb.New(time.Date(2023, 1, 2, 15, 4, 5, 0, time.UTC))
372386
_, err := client.RegeneratePersonalAccessToken(context.Background(), connect.NewRequest(&v1.RegeneratePersonalAccessTokenRequest{
373-
Id: uuid.New().String(),
387+
Id: someTokenId,
388+
ExpirationTime: newTimestamp,
374389
}))
390+
require.Error(t, err, fmt.Errorf("Token with ID %s does not exist: not found", someTokenId))
391+
})
375392

376-
require.Equal(t, connect.CodeUnimplemented, connect.CodeOf(err))
393+
t.Run("regenerate correct token", func(t *testing.T) {
394+
serverMock, dbConn, client := setupTokensService(t, withTokenFeatureEnabled)
395+
396+
tokens := dbtest.CreatePersonalAccessTokenRecords(t, dbConn,
397+
dbtest.NewPersonalAccessToken(t, db.PersonalAccessToken{
398+
UserID: uuid.MustParse(user.ID),
399+
}),
400+
dbtest.NewPersonalAccessToken(t, db.PersonalAccessToken{
401+
UserID: uuid.MustParse(user2.ID),
402+
}),
403+
)
404+
405+
serverMock.EXPECT().GetLoggedInUser(gomock.Any()).Return(user, nil).MaxTimes(2)
406+
407+
origResponse, err := client.GetPersonalAccessToken(context.Background(), connect.NewRequest(&v1.GetPersonalAccessTokenRequest{
408+
Id: tokens[0].ID.String(),
409+
}))
410+
require.NoError(t, err)
411+
412+
newTimestamp := timestamppb.New(time.Now().Add(24 * time.Hour).UTC().Truncate(time.Millisecond))
413+
response, err := client.RegeneratePersonalAccessToken(context.Background(), connect.NewRequest(&v1.RegeneratePersonalAccessTokenRequest{
414+
Id: tokens[0].ID.String(),
415+
ExpirationTime: newTimestamp,
416+
}))
417+
require.NoError(t, err)
418+
419+
require.Equal(t, origResponse.Msg.Token.Id, response.Msg.Token.Id)
420+
require.NotEqual(t, "", response.Msg.Token.Value)
421+
require.Equal(t, origResponse.Msg.Token.Name, response.Msg.Token.Name)
422+
require.Equal(t, origResponse.Msg.Token.Description, response.Msg.Token.Description)
423+
require.Equal(t, origResponse.Msg.Token.Scopes, response.Msg.Token.Scopes)
424+
require.Equal(t, newTimestamp.AsTime(), response.Msg.Token.ExpirationTime.AsTime())
425+
require.Equal(t, origResponse.Msg.Token.CreatedAt, response.Msg.Token.CreatedAt)
377426
})
378427
}
379428

0 commit comments

Comments
 (0)