mirror of https://github.com/grafana/grafana
Auth: Attach external session info to Grafana session (#93849)
* initial from poc changes * wip * Remove public external session service * Update swagger * Fix merge * Cleanup * Add backgroud service for cleanup * Add auth_module to user_external_session * Add tests for token revocation functions * Add secret migration capabilities for user_external_session fields * Cleanup, refactor to address feedback * Fix testpull/94377/head
parent
9eea0e99fc
commit
bd7850853e
@ -0,0 +1,244 @@ |
||||
package authimpl |
||||
|
||||
import ( |
||||
"context" |
||||
"crypto/sha256" |
||||
"encoding/base64" |
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db" |
||||
"github.com/grafana/grafana/pkg/infra/tracing" |
||||
"github.com/grafana/grafana/pkg/services/auth" |
||||
"github.com/grafana/grafana/pkg/services/secrets" |
||||
) |
||||
|
||||
var _ auth.ExternalSessionStore = (*store)(nil) |
||||
|
||||
type store struct { |
||||
sqlStore db.DB |
||||
secretsService secrets.Service |
||||
tracer tracing.Tracer |
||||
} |
||||
|
||||
func provideExternalSessionStore(sqlStore db.DB, secretService secrets.Service, tracer tracing.Tracer) auth.ExternalSessionStore { |
||||
return &store{ |
||||
sqlStore: sqlStore, |
||||
secretsService: secretService, |
||||
tracer: tracer, |
||||
} |
||||
} |
||||
|
||||
func (s *store) Get(ctx context.Context, extSessionID int64) (*auth.ExternalSession, error) { |
||||
ctx, span := s.tracer.Start(ctx, "externalsession.Get") |
||||
defer span.End() |
||||
|
||||
externalSession := &auth.ExternalSession{ID: extSessionID} |
||||
|
||||
err := s.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { |
||||
found, err := sess.Get(externalSession) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if !found { |
||||
return auth.ErrExternalSessionNotFound |
||||
} |
||||
return nil |
||||
}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
err = s.decryptSecrets(externalSession) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return externalSession, nil |
||||
} |
||||
|
||||
func (s *store) List(ctx context.Context, query *auth.ListExternalSessionQuery) ([]*auth.ExternalSession, error) { |
||||
ctx, span := s.tracer.Start(ctx, "externalsession.List") |
||||
defer span.End() |
||||
|
||||
externalSession := &auth.ExternalSession{} |
||||
if query.ID != 0 { |
||||
externalSession.ID = query.ID |
||||
} |
||||
|
||||
hash := sha256.New() |
||||
|
||||
if query.SessionID != "" { |
||||
hash.Write([]byte(query.SessionID)) |
||||
externalSession.SessionIDHash = base64.RawStdEncoding.EncodeToString(hash.Sum(nil)) |
||||
} |
||||
|
||||
if query.NameID != "" { |
||||
hash.Reset() |
||||
hash.Write([]byte(query.NameID)) |
||||
externalSession.NameIDHash = base64.RawStdEncoding.EncodeToString(hash.Sum(nil)) |
||||
} |
||||
|
||||
queryResult := make([]*auth.ExternalSession, 0) |
||||
err := s.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { |
||||
return sess.Find(&queryResult, externalSession) |
||||
}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
for _, extSession := range queryResult { |
||||
err := s.decryptSecrets(extSession) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
return queryResult, nil |
||||
} |
||||
|
||||
func (s *store) Create(ctx context.Context, extSession *auth.ExternalSession) error { |
||||
ctx, span := s.tracer.Start(ctx, "externalsession.Create") |
||||
defer span.End() |
||||
|
||||
var err error |
||||
clone := extSession.Clone() |
||||
|
||||
clone.AccessToken, err = s.encryptAndEncode(extSession.AccessToken) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
clone.RefreshToken, err = s.encryptAndEncode(extSession.RefreshToken) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
clone.IDToken, err = s.encryptAndEncode(extSession.IDToken) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if extSession.NameID != "" { |
||||
hash := sha256.New() |
||||
hash.Write([]byte(extSession.NameID)) |
||||
clone.NameIDHash = base64.RawStdEncoding.EncodeToString(hash.Sum(nil)) |
||||
} |
||||
|
||||
clone.NameID, err = s.encryptAndEncode(extSession.NameID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if extSession.SessionID != "" { |
||||
hash := sha256.New() |
||||
hash.Write([]byte(extSession.SessionID)) |
||||
clone.SessionIDHash = base64.RawStdEncoding.EncodeToString(hash.Sum(nil)) |
||||
} |
||||
|
||||
clone.SessionID, err = s.encryptAndEncode(extSession.SessionID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
err = s.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { |
||||
_, err := sess.Insert(clone) |
||||
return err |
||||
}) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
extSession.ID = clone.ID |
||||
return nil |
||||
} |
||||
|
||||
func (s *store) Delete(ctx context.Context, ID int64) error { |
||||
ctx, span := s.tracer.Start(ctx, "externalsession.Delete") |
||||
defer span.End() |
||||
|
||||
externalSession := &auth.ExternalSession{ID: ID} |
||||
err := s.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { |
||||
_, err := sess.Delete(externalSession) |
||||
return err |
||||
}) |
||||
return err |
||||
} |
||||
|
||||
func (s *store) DeleteExternalSessionsByUserID(ctx context.Context, userID int64) error { |
||||
ctx, span := s.tracer.Start(ctx, "externalsession.DeleteExternalSessionsByUserID") |
||||
defer span.End() |
||||
|
||||
externalSession := &auth.ExternalSession{UserID: userID} |
||||
err := s.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { |
||||
_, err := sess.Delete(externalSession) |
||||
return err |
||||
}) |
||||
return err |
||||
} |
||||
|
||||
func (s *store) BatchDeleteExternalSessionsByUserIDs(ctx context.Context, userIDs []int64) error { |
||||
ctx, span := s.tracer.Start(ctx, "externalsession.BatchDeleteExternalSessionsByUserIDs") |
||||
defer span.End() |
||||
|
||||
externalSession := &auth.ExternalSession{} |
||||
err := s.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { |
||||
_, err := sess.In("user_id", userIDs).Delete(externalSession) |
||||
return err |
||||
}) |
||||
return err |
||||
} |
||||
|
||||
func (s *store) decryptSecrets(extSession *auth.ExternalSession) error { |
||||
var err error |
||||
extSession.AccessToken, err = s.decodeAndDecrypt(extSession.AccessToken) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
extSession.RefreshToken, err = s.decodeAndDecrypt(extSession.RefreshToken) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
extSession.IDToken, err = s.decodeAndDecrypt(extSession.IDToken) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
extSession.NameID, err = s.decodeAndDecrypt(extSession.NameID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
extSession.SessionID, err = s.decodeAndDecrypt(extSession.SessionID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (s *store) encryptAndEncode(str string) (string, error) { |
||||
if str == "" { |
||||
return "", nil |
||||
} |
||||
|
||||
encrypted, err := s.secretsService.Encrypt(context.Background(), []byte(str), secrets.WithoutScope()) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
return base64.StdEncoding.EncodeToString(encrypted), nil |
||||
} |
||||
|
||||
func (s *store) decodeAndDecrypt(str string) (string, error) { |
||||
// Bail out if empty string since it'll cause a segfault in Decrypt
|
||||
if str == "" { |
||||
return "", nil |
||||
} |
||||
decoded, err := base64.StdEncoding.DecodeString(str) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
decrypted, err := s.secretsService.Decrypt(context.Background(), decoded) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
return string(decrypted), nil |
||||
} |
@ -0,0 +1,228 @@ |
||||
package authimpl |
||||
|
||||
import ( |
||||
"context" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db" |
||||
"github.com/grafana/grafana/pkg/infra/tracing" |
||||
"github.com/grafana/grafana/pkg/services/auth" |
||||
"github.com/grafana/grafana/pkg/services/secrets/fakes" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestGetExternalSession(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("skipping integration test") |
||||
} |
||||
t.Run("returns existing external session", func(t *testing.T) { |
||||
store := setupTest(t) |
||||
|
||||
extSession := &auth.ExternalSession{ |
||||
AccessToken: "access-token", |
||||
} |
||||
|
||||
err := store.Create(context.Background(), extSession) |
||||
require.NoError(t, err) |
||||
|
||||
actual, err := store.Get(context.Background(), extSession.ID) |
||||
require.NoError(t, err) |
||||
require.EqualValues(t, extSession.ID, actual.ID) |
||||
require.EqualValues(t, extSession.AccessToken, actual.AccessToken) |
||||
}) |
||||
|
||||
t.Run("returns not found if the external session is missing", func(t *testing.T) { |
||||
store := setupTest(t) |
||||
|
||||
_, err := store.Get(context.Background(), 999) |
||||
require.ErrorIs(t, err, auth.ErrExternalSessionNotFound) |
||||
}) |
||||
} |
||||
|
||||
func TestFindExternalSessions(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("skipping integration test") |
||||
} |
||||
|
||||
t.Run("returns external sessions by ID", func(t *testing.T) { |
||||
store := setupTest(t) |
||||
|
||||
extSession := &auth.ExternalSession{ |
||||
AccessToken: "access-token", |
||||
} |
||||
|
||||
err := store.Create(context.Background(), extSession) |
||||
require.NoError(t, err) |
||||
|
||||
query := &auth.ListExternalSessionQuery{ID: extSession.ID} |
||||
actual, err := store.List(context.Background(), query) |
||||
require.NoError(t, err) |
||||
require.Len(t, actual, 1) |
||||
require.EqualValues(t, extSession.ID, actual[0].ID) |
||||
require.EqualValues(t, extSession.AccessToken, actual[0].AccessToken) |
||||
}) |
||||
|
||||
t.Run("returns external sessions by SessionID", func(t *testing.T) { |
||||
store := setupTest(t) |
||||
|
||||
extSession := &auth.ExternalSession{ |
||||
SessionID: "session-index", |
||||
} |
||||
err := store.Create(context.Background(), extSession) |
||||
require.NoError(t, err) |
||||
|
||||
query := &auth.ListExternalSessionQuery{SessionID: extSession.SessionID} |
||||
actual, err := store.List(context.Background(), query) |
||||
require.NoError(t, err) |
||||
require.Len(t, actual, 1) |
||||
require.EqualValues(t, extSession.ID, actual[0].ID) |
||||
require.EqualValues(t, extSession.SessionID, actual[0].SessionID) |
||||
}) |
||||
|
||||
t.Run("returns external sessions by NameID", func(t *testing.T) { |
||||
store := setupTest(t) |
||||
|
||||
extSession := &auth.ExternalSession{ |
||||
NameID: "name-id", |
||||
} |
||||
|
||||
err := store.Create(context.Background(), extSession) |
||||
require.NoError(t, err) |
||||
|
||||
query := &auth.ListExternalSessionQuery{NameID: extSession.NameID} |
||||
actual, err := store.List(context.Background(), query) |
||||
require.NoError(t, err) |
||||
require.Len(t, actual, 1) |
||||
require.EqualValues(t, extSession.ID, actual[0].ID) |
||||
require.EqualValues(t, extSession.NameID, actual[0].NameID) |
||||
}) |
||||
|
||||
t.Run("returns empty result if no external sessions match the query", func(t *testing.T) { |
||||
store := setupTest(t) |
||||
|
||||
query := &auth.ListExternalSessionQuery{ID: 999} |
||||
actual, err := store.List(context.Background(), query) |
||||
require.NoError(t, err) |
||||
require.Len(t, actual, 0) |
||||
}) |
||||
} |
||||
|
||||
func TestDeleteExternalSessionsByUserID(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("skipping integration test") |
||||
} |
||||
|
||||
t.Run("deletes all external sessions for a given user ID", func(t *testing.T) { |
||||
store := setupTest(t) |
||||
|
||||
userID := int64(1) |
||||
extSession1 := &auth.ExternalSession{ |
||||
UserID: userID, |
||||
AccessToken: "access-token-1", |
||||
} |
||||
extSession2 := &auth.ExternalSession{ |
||||
UserID: userID, |
||||
AccessToken: "access-token-2", |
||||
} |
||||
|
||||
err := store.Create(context.Background(), extSession1) |
||||
require.NoError(t, err) |
||||
err = store.Create(context.Background(), extSession2) |
||||
require.NoError(t, err) |
||||
|
||||
err = store.DeleteExternalSessionsByUserID(context.Background(), userID) |
||||
require.NoError(t, err) |
||||
|
||||
query := &auth.ListExternalSessionQuery{} |
||||
actual, err := store.List(context.Background(), query) |
||||
require.NoError(t, err) |
||||
require.Len(t, actual, 0) |
||||
}) |
||||
|
||||
t.Run("returns no error if no external sessions exist for the given user ID", func(t *testing.T) { |
||||
store := setupTest(t) |
||||
|
||||
userID := int64(999) |
||||
err := store.DeleteExternalSessionsByUserID(context.Background(), userID) |
||||
require.NoError(t, err) |
||||
}) |
||||
} |
||||
|
||||
func TestDeleteExternalSession(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("skipping integration test") |
||||
} |
||||
|
||||
t.Run("deletes an existing external session", func(t *testing.T) { |
||||
store := setupTest(t) |
||||
|
||||
extSession := &auth.ExternalSession{ |
||||
AccessToken: "access-token", |
||||
} |
||||
|
||||
err := store.Create(context.Background(), extSession) |
||||
require.NoError(t, err) |
||||
|
||||
err = store.Delete(context.Background(), extSession.ID) |
||||
require.NoError(t, err) |
||||
|
||||
_, err = store.Get(context.Background(), extSession.ID) |
||||
require.ErrorIs(t, err, auth.ErrExternalSessionNotFound) |
||||
}) |
||||
|
||||
t.Run("returns no error if the external session does not exist", func(t *testing.T) { |
||||
store := setupTest(t) |
||||
|
||||
err := store.Delete(context.Background(), 999) |
||||
require.NoError(t, err) |
||||
}) |
||||
} |
||||
|
||||
func TestBatchDeleteExternalSessionsByUserIDs(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("skipping integration test") |
||||
} |
||||
|
||||
t.Run("deletes all external sessions for given user IDs", func(t *testing.T) { |
||||
store := setupTest(t) |
||||
|
||||
userID1 := int64(1) |
||||
userID2 := int64(2) |
||||
extSession1 := &auth.ExternalSession{ |
||||
UserID: userID1, |
||||
AccessToken: "access-token-1", |
||||
} |
||||
extSession2 := &auth.ExternalSession{ |
||||
UserID: userID2, |
||||
AccessToken: "access-token-2", |
||||
} |
||||
|
||||
err := store.Create(context.Background(), extSession1) |
||||
require.NoError(t, err) |
||||
err = store.Create(context.Background(), extSession2) |
||||
require.NoError(t, err) |
||||
|
||||
err = store.BatchDeleteExternalSessionsByUserIDs(context.Background(), []int64{userID1, userID2}) |
||||
require.NoError(t, err) |
||||
|
||||
query := &auth.ListExternalSessionQuery{} |
||||
actual, err := store.List(context.Background(), query) |
||||
require.NoError(t, err) |
||||
require.Len(t, actual, 0) |
||||
}) |
||||
|
||||
t.Run("returns no error if no external sessions exist for the given user IDs", func(t *testing.T) { |
||||
store := setupTest(t) |
||||
|
||||
err := store.BatchDeleteExternalSessionsByUserIDs(context.Background(), []int64{999, 1000}) |
||||
require.NoError(t, err) |
||||
}) |
||||
} |
||||
|
||||
func setupTest(t *testing.T) *store { |
||||
sqlStore := db.InitTestDB(t) |
||||
secretService := fakes.NewFakeSecretsService() |
||||
tracer := tracing.InitializeTracerForTest() |
||||
externalSessionStore := provideExternalSessionStore(sqlStore, secretService, tracer).(*store) |
||||
return externalSessionStore |
||||
} |
@ -0,0 +1,162 @@ |
||||
// Code generated by mockery v2.42.1. DO NOT EDIT.
|
||||
|
||||
package authtest |
||||
|
||||
import ( |
||||
context "context" |
||||
|
||||
auth "github.com/grafana/grafana/pkg/services/auth" |
||||
|
||||
mock "github.com/stretchr/testify/mock" |
||||
) |
||||
|
||||
// MockExternalSessionStore is an autogenerated mock type for the ExternalSessionStore type
|
||||
type MockExternalSessionStore struct { |
||||
mock.Mock |
||||
} |
||||
|
||||
// BatchDeleteExternalSessionsByUserIDs provides a mock function with given fields: ctx, userIDs
|
||||
func (_m *MockExternalSessionStore) BatchDeleteExternalSessionsByUserIDs(ctx context.Context, userIDs []int64) error { |
||||
ret := _m.Called(ctx, userIDs) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for BatchDeleteExternalSessionsByUserIDs") |
||||
} |
||||
|
||||
var r0 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, []int64) error); ok { |
||||
r0 = rf(ctx, userIDs) |
||||
} else { |
||||
r0 = ret.Error(0) |
||||
} |
||||
|
||||
return r0 |
||||
} |
||||
|
||||
// Create provides a mock function with given fields: ctx, extSesion
|
||||
func (_m *MockExternalSessionStore) Create(ctx context.Context, extSesion *auth.ExternalSession) error { |
||||
ret := _m.Called(ctx, extSesion) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for Create") |
||||
} |
||||
|
||||
var r0 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, *auth.ExternalSession) error); ok { |
||||
r0 = rf(ctx, extSesion) |
||||
} else { |
||||
r0 = ret.Error(0) |
||||
} |
||||
|
||||
return r0 |
||||
} |
||||
|
||||
// Delete provides a mock function with given fields: ctx, ID
|
||||
func (_m *MockExternalSessionStore) Delete(ctx context.Context, ID int64) error { |
||||
ret := _m.Called(ctx, ID) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for Delete") |
||||
} |
||||
|
||||
var r0 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { |
||||
r0 = rf(ctx, ID) |
||||
} else { |
||||
r0 = ret.Error(0) |
||||
} |
||||
|
||||
return r0 |
||||
} |
||||
|
||||
// DeleteExternalSessionsByUserID provides a mock function with given fields: ctx, userID
|
||||
func (_m *MockExternalSessionStore) DeleteExternalSessionsByUserID(ctx context.Context, userID int64) error { |
||||
ret := _m.Called(ctx, userID) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for DeleteExternalSessionsByUserID") |
||||
} |
||||
|
||||
var r0 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { |
||||
r0 = rf(ctx, userID) |
||||
} else { |
||||
r0 = ret.Error(0) |
||||
} |
||||
|
||||
return r0 |
||||
} |
||||
|
||||
// Get provides a mock function with given fields: ctx, ID
|
||||
func (_m *MockExternalSessionStore) Get(ctx context.Context, ID int64) (*auth.ExternalSession, error) { |
||||
ret := _m.Called(ctx, ID) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for Get") |
||||
} |
||||
|
||||
var r0 *auth.ExternalSession |
||||
var r1 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) (*auth.ExternalSession, error)); ok { |
||||
return rf(ctx, ID) |
||||
} |
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) *auth.ExternalSession); ok { |
||||
r0 = rf(ctx, ID) |
||||
} else { |
||||
if ret.Get(0) != nil { |
||||
r0 = ret.Get(0).(*auth.ExternalSession) |
||||
} |
||||
} |
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { |
||||
r1 = rf(ctx, ID) |
||||
} else { |
||||
r1 = ret.Error(1) |
||||
} |
||||
|
||||
return r0, r1 |
||||
} |
||||
|
||||
// List provides a mock function with given fields: ctx, query
|
||||
func (_m *MockExternalSessionStore) List(ctx context.Context, query *auth.ListExternalSessionQuery) ([]*auth.ExternalSession, error) { |
||||
ret := _m.Called(ctx, query) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for List") |
||||
} |
||||
|
||||
var r0 []*auth.ExternalSession |
||||
var r1 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, *auth.ListExternalSessionQuery) ([]*auth.ExternalSession, error)); ok { |
||||
return rf(ctx, query) |
||||
} |
||||
if rf, ok := ret.Get(0).(func(context.Context, *auth.ListExternalSessionQuery) []*auth.ExternalSession); ok { |
||||
r0 = rf(ctx, query) |
||||
} else { |
||||
if ret.Get(0) != nil { |
||||
r0 = ret.Get(0).([]*auth.ExternalSession) |
||||
} |
||||
} |
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *auth.ListExternalSessionQuery) error); ok { |
||||
r1 = rf(ctx, query) |
||||
} else { |
||||
r1 = ret.Error(1) |
||||
} |
||||
|
||||
return r0, r1 |
||||
} |
||||
|
||||
// NewMockExternalSessionStore creates a new instance of MockExternalSessionStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockExternalSessionStore(t interface { |
||||
mock.TestingT |
||||
Cleanup(func()) |
||||
}) *MockExternalSessionStore { |
||||
mock := &MockExternalSessionStore{} |
||||
mock.Mock.Test(t) |
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) }) |
||||
|
||||
return mock |
||||
} |
@ -0,0 +1,66 @@ |
||||
package auth |
||||
|
||||
import ( |
||||
"context" |
||||
"time" |
||||
) |
||||
|
||||
type ExternalSession struct { |
||||
ID int64 `xorm:"pk autoincr 'id'"` |
||||
UserID int64 `xorm:"user_id"` |
||||
UserAuthID int64 `xorm:"user_auth_id"` |
||||
AuthModule string `xorm:"auth_module"` |
||||
AccessToken string `xorm:"access_token"` |
||||
IDToken string `xorm:"id_token"` |
||||
RefreshToken string `xorm:"refresh_token"` |
||||
SessionID string `xorm:"session_id"` |
||||
SessionIDHash string `xorm:"session_id_hash"` |
||||
NameID string `xorm:"name_id"` |
||||
NameIDHash string `xorm:"name_id_hash"` |
||||
ExpiresAt time.Time `xorm:"expires_at"` |
||||
CreatedAt time.Time `xorm:"created 'created_at'"` |
||||
} |
||||
|
||||
func (e *ExternalSession) TableName() string { |
||||
return "user_external_session" |
||||
} |
||||
|
||||
func (e *ExternalSession) Clone() *ExternalSession { |
||||
return &ExternalSession{ |
||||
ID: e.ID, |
||||
UserID: e.UserID, |
||||
UserAuthID: e.UserAuthID, |
||||
AuthModule: e.AuthModule, |
||||
AccessToken: e.AccessToken, |
||||
IDToken: e.IDToken, |
||||
RefreshToken: e.RefreshToken, |
||||
SessionID: e.SessionID, |
||||
SessionIDHash: e.SessionIDHash, |
||||
NameID: e.NameID, |
||||
NameIDHash: e.NameIDHash, |
||||
ExpiresAt: e.ExpiresAt, |
||||
CreatedAt: e.CreatedAt, |
||||
} |
||||
} |
||||
|
||||
type ListExternalSessionQuery struct { |
||||
ID int64 |
||||
NameID string |
||||
SessionID string |
||||
} |
||||
|
||||
//go:generate mockery --name ExternalSessionStore --structname MockExternalSessionStore --outpkg authtest --filename external_session_store_mock.go --output ./authtest/
|
||||
type ExternalSessionStore interface { |
||||
// Get returns the external session
|
||||
Get(ctx context.Context, ID int64) (*ExternalSession, error) |
||||
// List returns all external sessions fπor the given query
|
||||
List(ctx context.Context, query *ListExternalSessionQuery) ([]*ExternalSession, error) |
||||
// Create creates a new external session for a user
|
||||
Create(ctx context.Context, extSesion *ExternalSession) error |
||||
// Delete deletes an external session
|
||||
Delete(ctx context.Context, ID int64) error |
||||
// DeleteExternalSessionsByUserID deletes an external session
|
||||
DeleteExternalSessionsByUserID(ctx context.Context, userID int64) error |
||||
// BatchDeleteExternalSessionsByUserIDs deletes external sessions by user IDs
|
||||
BatchDeleteExternalSessionsByUserIDs(ctx context.Context, userIDs []int64) error |
||||
} |
@ -0,0 +1,31 @@ |
||||
package externalsession |
||||
|
||||
import "github.com/grafana/grafana/pkg/services/sqlstore/migrator" |
||||
|
||||
func AddMigration(mg *migrator.Migrator) { |
||||
externalSessionV1 := migrator.Table{ |
||||
Name: "user_external_session", |
||||
Columns: []*migrator.Column{ |
||||
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, |
||||
{Name: "user_auth_id", Type: migrator.DB_BigInt, Nullable: false}, |
||||
{Name: "user_id", Type: migrator.DB_BigInt, Nullable: false}, |
||||
{Name: "auth_module", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, |
||||
{Name: "access_token", Type: migrator.DB_Text, Nullable: true}, |
||||
{Name: "id_token", Type: migrator.DB_Text, Nullable: true}, |
||||
{Name: "refresh_token", Type: migrator.DB_Text, Nullable: true}, |
||||
{Name: "session_id", Type: migrator.DB_NVarchar, Length: 255, Nullable: true}, |
||||
{Name: "session_id_hash", Type: migrator.DB_Char, Length: 44, Nullable: true}, |
||||
{Name: "name_id", Type: migrator.DB_NVarchar, Length: 255, Nullable: true}, |
||||
{Name: "name_id_hash", Type: migrator.DB_Char, Length: 44, Nullable: true}, |
||||
{Name: "expires_at", Type: migrator.DB_DateTime, Nullable: true}, |
||||
{Name: "created_at", Type: migrator.DB_DateTime, Nullable: false}, |
||||
}, |
||||
Indices: []*migrator.Index{ |
||||
{Cols: []string{"user_id"}}, |
||||
{Cols: []string{"session_id_hash"}}, |
||||
{Cols: []string{"name_id_hash"}}, |
||||
}, |
||||
} |
||||
|
||||
mg.AddMigration("create user_external_session table", migrator.NewAddTableMigration(externalSessionV1)) |
||||
} |
Loading…
Reference in new issue