Skip to content

Commit 4ab854f

Browse files
committed
WIP: public api regenerate
1 parent d14c82a commit 4ab854f

File tree

4 files changed

+113
-10
lines changed

4 files changed

+113
-10
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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/google/uuid"
1616
"gorm.io/gorm"
17+
"gorm.io/gorm/clause"
1718
)
1819

1920
type PersonalAccessToken struct {
@@ -87,6 +88,40 @@ func CreatePersonalAccessToken(ctx context.Context, conn *gorm.DB, req PersonalA
8788
return token, nil
8889
}
8990

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

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.UpdatePersonalAccessToken(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: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ import (
2222
protocol "github.com/gitpod-io/gitpod/gitpod-protocol"
2323
"github.com/gitpod-io/gitpod/public-api-server/pkg/auth"
2424
"github.com/golang/mock/gomock"
25+
"github.com/google/go-cmp/cmp"
2526
"github.com/google/uuid"
2627
"github.com/stretchr/testify/require"
28+
"google.golang.org/protobuf/testing/protocmp"
2729
"google.golang.org/protobuf/types/known/timestamppb"
2830
"gorm.io/gorm"
2931
)
@@ -332,14 +334,16 @@ func TestTokensService_ListPersonalAccessTokens(t *testing.T) {
332334

333335
func TestTokensService_RegeneratePersonalAccessToken(t *testing.T) {
334336
user := newUser(&protocol.User{})
337+
user2 := newUser(&protocol.User{})
335338

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

339342
serverMock.EXPECT().GetLoggedInUser(gomock.Any()).Return(user, nil)
340343

341344
_, err := client.RegeneratePersonalAccessToken(context.Background(), connect.NewRequest(&v1.RegeneratePersonalAccessTokenRequest{
342-
Id: uuid.New().String(),
345+
Id: uuid.New().String(),
346+
ExpirationTime: timestamppb.Now(),
343347
}))
344348

345349
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 +368,48 @@ func TestTokensService_RegeneratePersonalAccessToken(t *testing.T) {
364368
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
365369
})
366370

367-
t.Run("unimplemented when feature flag enabled", func(t *testing.T) {
368-
serverMock, _, client := setupTokensService(t, withTokenFeatureEnabled)
371+
t.Run("regenerate correct token", func(t *testing.T) {
372+
serverMock, dbConn, client := setupTokensService(t, withTokenFeatureEnabled)
373+
374+
tokens := 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

372-
_, err := client.RegeneratePersonalAccessToken(context.Background(), connect.NewRequest(&v1.RegeneratePersonalAccessTokenRequest{
373-
Id: uuid.New().String(),
385+
origResponse, err := client.GetPersonalAccessToken(context.Background(), connect.NewRequest(&v1.GetPersonalAccessTokenRequest{
386+
Id: tokens[0].ID.String(),
374387
}))
375388

376-
require.Equal(t, connect.CodeUnimplemented, connect.CodeOf(err))
389+
require.NoError(t, err)
390+
391+
newTimestamp := timestamppb.Now()
392+
response, err := client.RegeneratePersonalAccessToken(context.Background(), connect.NewRequest(&v1.RegeneratePersonalAccessTokenRequest{
393+
Id: tokens[0].ID.String(),
394+
ExpirationTime: newTimestamp,
395+
}))
396+
397+
require.NoError(t, err)
398+
399+
// require.Equal(t, response.Msg.Token.ExpirationTime, newTimestamp)
400+
// require.NotEqual(t, response.Msg.Token.Value, origResponse.Msg.Token.Value)
401+
402+
requireNotEqualProto(t, &v1.RegeneratePersonalAccessTokenResponse{
403+
Token: personalAccessTokenToAPI(tokens[0], origResponse.Msg.Token.Value),
404+
}, response.Msg)
405+
406+
// fake code
407+
tokens[0].Hash = response.Msg.Token.Value
408+
tokens[0].ExpirationTime = response.Msg.Token.GetExpirationTime().AsTime()
409+
410+
requireEqualProto(t, &v1.RegeneratePersonalAccessTokenResponse{
411+
Token: personalAccessTokenToAPI(tokens[0], ""),
412+
}, response.Msg)
377413
})
378414
}
379415

@@ -500,3 +536,12 @@ func setupTokensService(t *testing.T, expClient experiments.Client) (*protocol.M
500536

501537
return serverMock, dbConn, client
502538
}
539+
540+
func requireNotEqualProto(t *testing.T, expected interface{}, actual interface{}) {
541+
t.Helper()
542+
543+
diff := cmp.Diff(expected, actual, protocmp.Transform())
544+
if diff == "" {
545+
require.Fail(t, diff, "they should not equal")
546+
}
547+
}

0 commit comments

Comments
 (0)