mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Auth: Refactor for revoking user tokens within last hours (#74616)
* fix: revoked tokens within last hours adds check for unlimited sessions out of index adds a function for specifing the hours to look back when revoking users tokens, otherwise we "assume" the clean up takes care of them adds a index for the `user_auth_token` - `revoked_at` for faster queries when using `revoked_at` * fix: sqllite datetime conversion with unixtimestamps * fix: postgres dialect * fix: mysql dialect * fix: mysql dialect missing closing ) * refactor: delete revoked tokens directly * fix: tests for sqlite * AuthToken: Simplify DeleteUserRevokedTokens and add test * fix: linting newline * Reset get time after test * fix: test order by revoked * fix: order by different db * ascending * test with seen at --------- Co-authored-by: Karl Persson <kalle.persson@grafana.com>
This commit is contained in:
parent
a12c224cc0
commit
b00f3216c1
@ -551,11 +551,29 @@ func (s *UserAuthTokenService) ActiveTokenCount(ctx context.Context, userID *int
|
|||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UserAuthTokenService) DeleteUserRevokedTokens(ctx context.Context, userID int64, window time.Duration) error {
|
||||||
|
return s.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||||
|
query := "DELETE FROM user_auth_token WHERE user_id = ? AND revoked_at > 0 AND revoked_at <= ?"
|
||||||
|
res, err := sess.Exec(query, userID, time.Now().Add(-window).Unix())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.log.FromContext(ctx).Debug("Deleted user revoked tokens", "userId", userID, "count", rows)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (s *UserAuthTokenService) GetUserRevokedTokens(ctx context.Context, userId int64) ([]*auth.UserToken, error) {
|
func (s *UserAuthTokenService) GetUserRevokedTokens(ctx context.Context, userId int64) ([]*auth.UserToken, error) {
|
||||||
result := []*auth.UserToken{}
|
result := []*auth.UserToken{}
|
||||||
err := s.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error {
|
err := s.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error {
|
||||||
var tokens []*userAuthToken
|
var tokens []*userAuthToken
|
||||||
err := dbSession.Where("user_id = ? AND revoked_at > 0", userId).Find(&tokens)
|
err := dbSession.Where("user_id = ? AND revoked_at > 0", userId).Asc("seen_at").Find(&tokens)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,7 @@ import (
|
|||||||
|
|
||||||
func TestIntegrationUserAuthToken(t *testing.T) {
|
func TestIntegrationUserAuthToken(t *testing.T) {
|
||||||
ctx := createTestContext(t)
|
ctx := createTestContext(t)
|
||||||
user := &user.User{ID: int64(10)}
|
usr := &user.User{ID: int64(10)}
|
||||||
// userID := user.Id
|
|
||||||
|
|
||||||
now := time.Date(2018, 12, 13, 13, 45, 0, 0, time.UTC)
|
now := time.Date(2018, 12, 13, 13, 45, 0, 0, time.UTC)
|
||||||
getTime = func() time.Time { return now }
|
getTime = func() time.Time { return now }
|
||||||
@ -32,7 +31,7 @@ func TestIntegrationUserAuthToken(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("When creating token", func(t *testing.T) {
|
t.Run("When creating token", func(t *testing.T) {
|
||||||
createToken := func() *auth.UserToken {
|
createToken := func() *auth.UserToken {
|
||||||
userToken, err := ctx.tokenService.CreateToken(context.Background(), user,
|
userToken, err := ctx.tokenService.CreateToken(context.Background(), usr,
|
||||||
net.ParseIP("192.168.10.11"), "some user agent")
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.NotNil(t, userToken)
|
require.NotNil(t, userToken)
|
||||||
@ -56,7 +55,7 @@ func TestIntegrationUserAuthToken(t *testing.T) {
|
|||||||
userToken, err := ctx.tokenService.LookupToken(context.Background(), userToken.UnhashedToken)
|
userToken, err := ctx.tokenService.LookupToken(context.Background(), userToken.UnhashedToken)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.NotNil(t, userToken)
|
require.NotNil(t, userToken)
|
||||||
require.Equal(t, user.ID, userToken.UserId)
|
require.Equal(t, usr.ID, userToken.UserId)
|
||||||
require.True(t, userToken.AuthTokenSeen)
|
require.True(t, userToken.AuthTokenSeen)
|
||||||
|
|
||||||
storedAuthToken, err := ctx.getAuthTokenByID(userToken.Id)
|
storedAuthToken, err := ctx.getAuthTokenByID(userToken.Id)
|
||||||
@ -105,27 +104,27 @@ func TestIntegrationUserAuthToken(t *testing.T) {
|
|||||||
userToken = createToken()
|
userToken = createToken()
|
||||||
|
|
||||||
t.Run("When creating an additional token", func(t *testing.T) {
|
t.Run("When creating an additional token", func(t *testing.T) {
|
||||||
userToken2, err := ctx.tokenService.CreateToken(context.Background(), user,
|
userToken2, err := ctx.tokenService.CreateToken(context.Background(), usr,
|
||||||
net.ParseIP("192.168.10.11"), "some user agent")
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.NotNil(t, userToken2)
|
require.NotNil(t, userToken2)
|
||||||
|
|
||||||
t.Run("Can get first user token", func(t *testing.T) {
|
t.Run("Can get first user token", func(t *testing.T) {
|
||||||
token, err := ctx.tokenService.GetUserToken(context.Background(), user.ID, userToken.Id)
|
token, err := ctx.tokenService.GetUserToken(context.Background(), usr.ID, userToken.Id)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.NotNil(t, token)
|
require.NotNil(t, token)
|
||||||
require.Equal(t, userToken.Id, token.Id)
|
require.Equal(t, userToken.Id, token.Id)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Can get second user token", func(t *testing.T) {
|
t.Run("Can get second user token", func(t *testing.T) {
|
||||||
token, err := ctx.tokenService.GetUserToken(context.Background(), user.ID, userToken2.Id)
|
token, err := ctx.tokenService.GetUserToken(context.Background(), usr.ID, userToken2.Id)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.NotNil(t, token)
|
require.NotNil(t, token)
|
||||||
require.Equal(t, userToken2.Id, token.Id)
|
require.Equal(t, userToken2.Id, token.Id)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Can get user tokens", func(t *testing.T) {
|
t.Run("Can get user tokens", func(t *testing.T) {
|
||||||
tokens, err := ctx.tokenService.GetUserTokens(context.Background(), user.ID)
|
tokens, err := ctx.tokenService.GetUserTokens(context.Background(), usr.ID)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Equal(t, 2, len(tokens))
|
require.Equal(t, 2, len(tokens))
|
||||||
require.Equal(t, userToken.Id, tokens[0].Id)
|
require.Equal(t, userToken.Id, tokens[0].Id)
|
||||||
@ -133,7 +132,7 @@ func TestIntegrationUserAuthToken(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Can revoke all user tokens", func(t *testing.T) {
|
t.Run("Can revoke all user tokens", func(t *testing.T) {
|
||||||
err := ctx.tokenService.RevokeAllUserTokens(context.Background(), user.ID)
|
err := ctx.tokenService.RevokeAllUserTokens(context.Background(), usr.ID)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
model, err := ctx.getAuthTokenByID(userToken.Id)
|
model, err := ctx.getAuthTokenByID(userToken.Id)
|
||||||
@ -150,9 +149,9 @@ func TestIntegrationUserAuthToken(t *testing.T) {
|
|||||||
t.Run("Can revoke all users tokens", func(t *testing.T) {
|
t.Run("Can revoke all users tokens", func(t *testing.T) {
|
||||||
userIds := []int64{}
|
userIds := []int64{}
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
userId := user.ID + int64(i+1)
|
userId := usr.ID + int64(i+1)
|
||||||
userIds = append(userIds, userId)
|
userIds = append(userIds, userId)
|
||||||
_, err := ctx.tokenService.CreateToken(context.Background(), user,
|
_, err := ctx.tokenService.CreateToken(context.Background(), usr,
|
||||||
net.ParseIP("192.168.10.11"), "some user agent")
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
}
|
}
|
||||||
@ -171,7 +170,7 @@ func TestIntegrationUserAuthToken(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("expires correctly", func(t *testing.T) {
|
t.Run("expires correctly", func(t *testing.T) {
|
||||||
ctx := createTestContext(t)
|
ctx := createTestContext(t)
|
||||||
userToken, err := ctx.tokenService.CreateToken(context.Background(), user,
|
userToken, err := ctx.tokenService.CreateToken(context.Background(), usr,
|
||||||
net.ParseIP("192.168.10.11"), "some user agent")
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
@ -256,7 +255,7 @@ func TestIntegrationUserAuthToken(t *testing.T) {
|
|||||||
t.Run("can properly rotate tokens", func(t *testing.T) {
|
t.Run("can properly rotate tokens", func(t *testing.T) {
|
||||||
getTime = func() time.Time { return now }
|
getTime = func() time.Time { return now }
|
||||||
ctx := createTestContext(t)
|
ctx := createTestContext(t)
|
||||||
userToken, err := ctx.tokenService.CreateToken(context.Background(), user,
|
userToken, err := ctx.tokenService.CreateToken(context.Background(), usr,
|
||||||
net.ParseIP("192.168.10.11"), "some user agent")
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
@ -340,7 +339,7 @@ func TestIntegrationUserAuthToken(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("keeps prev token valid for 1 minute after it is confirmed", func(t *testing.T) {
|
t.Run("keeps prev token valid for 1 minute after it is confirmed", func(t *testing.T) {
|
||||||
getTime = func() time.Time { return now }
|
getTime = func() time.Time { return now }
|
||||||
userToken, err := ctx.tokenService.CreateToken(context.Background(), user,
|
userToken, err := ctx.tokenService.CreateToken(context.Background(), usr,
|
||||||
net.ParseIP("192.168.10.11"), "some user agent")
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.NotNil(t, userToken)
|
require.NotNil(t, userToken)
|
||||||
@ -371,7 +370,7 @@ func TestIntegrationUserAuthToken(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("will not mark token unseen when prev and current are the same", func(t *testing.T) {
|
t.Run("will not mark token unseen when prev and current are the same", func(t *testing.T) {
|
||||||
userToken, err := ctx.tokenService.CreateToken(context.Background(), user,
|
userToken, err := ctx.tokenService.CreateToken(context.Background(), usr,
|
||||||
net.ParseIP("192.168.10.11"), "some user agent")
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.NotNil(t, userToken)
|
require.NotNil(t, userToken)
|
||||||
@ -393,7 +392,7 @@ func TestIntegrationUserAuthToken(t *testing.T) {
|
|||||||
t.Run("TryRotateToken", func(t *testing.T) {
|
t.Run("TryRotateToken", func(t *testing.T) {
|
||||||
t.Run("Should rotate current token and previous token when auth token seen", func(t *testing.T) {
|
t.Run("Should rotate current token and previous token when auth token seen", func(t *testing.T) {
|
||||||
getTime = func() time.Time { return now }
|
getTime = func() time.Time { return now }
|
||||||
userToken, err := ctx.tokenService.CreateToken(context.Background(), user,
|
userToken, err := ctx.tokenService.CreateToken(context.Background(), usr,
|
||||||
net.ParseIP("192.168.10.11"), "some user agent")
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.NotNil(t, userToken)
|
require.NotNil(t, userToken)
|
||||||
@ -445,7 +444,7 @@ func TestIntegrationUserAuthToken(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Should rotate current token, but keep previous token when auth token not seen", func(t *testing.T) {
|
t.Run("Should rotate current token, but keep previous token when auth token not seen", func(t *testing.T) {
|
||||||
getTime = func() time.Time { return now }
|
getTime = func() time.Time { return now }
|
||||||
userToken, err := ctx.tokenService.CreateToken(context.Background(), user,
|
userToken, err := ctx.tokenService.CreateToken(context.Background(), usr,
|
||||||
net.ParseIP("192.168.10.11"), "some user agent")
|
net.ParseIP("192.168.10.11"), "some user agent")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.NotNil(t, userToken)
|
require.NotNil(t, userToken)
|
||||||
@ -473,7 +472,7 @@ func TestIntegrationUserAuthToken(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("RotateToken", func(t *testing.T) {
|
t.Run("RotateToken", func(t *testing.T) {
|
||||||
var prev string
|
var prev string
|
||||||
token, err := ctx.tokenService.CreateToken(context.Background(), user, nil, "")
|
token, err := ctx.tokenService.CreateToken(context.Background(), usr, nil, "")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Run("should rotate token when called with current auth token", func(t *testing.T) {
|
t.Run("should rotate token when called with current auth token", func(t *testing.T) {
|
||||||
prev = token.UnhashedToken
|
prev = token.UnhashedToken
|
||||||
@ -496,7 +495,7 @@ func TestIntegrationUserAuthToken(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should return error when token is revoked", func(t *testing.T) {
|
t.Run("should return error when token is revoked", func(t *testing.T) {
|
||||||
revokedToken, err := ctx.tokenService.CreateToken(context.Background(), user, nil, "")
|
revokedToken, err := ctx.tokenService.CreateToken(context.Background(), usr, nil, "")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// mark token as revoked
|
// mark token as revoked
|
||||||
err = ctx.sqlstore.WithDbSession(context.Background(), func(sess *db.Session) error {
|
err = ctx.sqlstore.WithDbSession(context.Background(), func(sess *db.Session) error {
|
||||||
@ -510,7 +509,7 @@ func TestIntegrationUserAuthToken(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should return error when token has expired", func(t *testing.T) {
|
t.Run("should return error when token has expired", func(t *testing.T) {
|
||||||
expiredToken, err := ctx.tokenService.CreateToken(context.Background(), user, nil, "")
|
expiredToken, err := ctx.tokenService.CreateToken(context.Background(), usr, nil, "")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// mark token as expired
|
// mark token as expired
|
||||||
err = ctx.sqlstore.WithDbSession(context.Background(), func(sess *db.Session) error {
|
err = ctx.sqlstore.WithDbSession(context.Background(), func(sess *db.Session) error {
|
||||||
@ -522,6 +521,38 @@ func TestIntegrationUserAuthToken(t *testing.T) {
|
|||||||
_, err = ctx.tokenService.RotateToken(context.Background(), auth.RotateCommand{UnHashedToken: expiredToken.UnhashedToken})
|
_, err = ctx.tokenService.RotateToken(context.Background(), auth.RotateCommand{UnHashedToken: expiredToken.UnhashedToken})
|
||||||
assert.ErrorIs(t, err, auth.ErrInvalidSessionToken)
|
assert.ErrorIs(t, err, auth.ErrInvalidSessionToken)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("should only delete revoked tokens that are outside on specified window", func(t *testing.T) {
|
||||||
|
usr := &user.User{ID: 100}
|
||||||
|
token1, err := ctx.tokenService.CreateToken(context.Background(), usr, nil, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
token2, err := ctx.tokenService.CreateToken(context.Background(), usr, nil, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
getTime = func() time.Time {
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
// revoked token1 with time now
|
||||||
|
err = ctx.tokenService.RevokeToken(context.Background(), token1, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
getTime = func() time.Time {
|
||||||
|
return time.Now().Add(-25 * time.Hour)
|
||||||
|
}
|
||||||
|
// revoked token1 with time at 25 hours ago
|
||||||
|
err = ctx.tokenService.RevokeToken(context.Background(), token2, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = ctx.tokenService.DeleteUserRevokedTokens(context.Background(), usr.ID, 24*time.Hour)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
revokedTokens, err := ctx.tokenService.GetUserRevokedTokens(context.Background(), usr.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, revokedTokens, 1)
|
||||||
|
|
||||||
|
getTime = time.Now
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("When populating userAuthToken from UserToken should copy all properties", func(t *testing.T) {
|
t.Run("When populating userAuthToken from UserToken should copy all properties", func(t *testing.T) {
|
||||||
|
@ -44,4 +44,8 @@ func addUserAuthTokenMigrations(mg *Migrator) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
mg.AddMigration("add index user_auth_token.revoked_at", NewAddIndexMigration(userAuthTokenV1, &Index{
|
||||||
|
Cols: []string{"revoked_at"},
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user