diff --git a/go.mod b/go.mod index 59cc35cf90e..3ded590b054 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,7 @@ require ( github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 // @grafana/alerting-backend - github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029 // @grafana/identity-access-team + github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569 // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics github.com/grafana/dataplane/sdata v0.0.9 // @grafana/observability-metrics diff --git a/go.sum b/go.sum index 7ce560e11a0..f73dcbe3a18 100644 --- a/go.sum +++ b/go.sum @@ -1511,8 +1511,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 h1:dG/UKAjY/KlKp9fY8aEm+gSQHHRmPm5q+9cea3hRSu8= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= -github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029 h1:0C0a1FEkecxDk60su4uuOozqBzqz/4nmfNFSxXnCViY= -github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= +github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569 h1:bQw6fdcxVdZ6xmZVhMtRgGEKIT1Zc6y+i1PWF8tMX4Q= +github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA= diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 21f6f2dcd56..850a4ea5fd5 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -207,6 +207,7 @@ type HTTPServer struct { tempUserService tempUser.Service loginAttemptService loginAttempt.Service orgService org.Service + idService auth.IDService orgDeletionService org.DeletionService TeamService team.Service accesscontrolService accesscontrol.Service @@ -272,7 +273,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi annotationRepo annotations.Repository, tagService tag.Service, searchv2HTTPService searchV2.SearchHTTPService, oauthTokenService oauthtoken.OAuthTokenService, statsService stats.Service, authnService authn.Service, pluginsCDNService *pluginscdn.Service, promGatherer prometheus.Gatherer, starApi *starApi.API, promRegister prometheus.Registerer, clientConfigProvider grafanaapiserver.DirectRestConfigProvider, anonService anonymous.Service, - userVerifier user.Verifier, pluginPreinstall plugininstaller.Preinstall, + userVerifier user.Verifier, pluginPreinstall plugininstaller.Preinstall, idService auth.IDService, ) (*HTTPServer, error) { web.Env = cfg.Env m := web.New() @@ -360,6 +361,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi tempUserService: tempUserService, loginAttemptService: loginAttemptService, orgService: orgService, + idService: idService, orgDeletionService: orgDeletionService, TeamService: teamService, navTreeService: navTreeService, diff --git a/pkg/api/org_users.go b/pkg/api/org_users.go index 1b83b4e3b77..f19d4138bb8 100644 --- a/pkg/api/org_users.go +++ b/pkg/api/org_users.go @@ -7,9 +7,11 @@ import ( "net/http" "strconv" + claims "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/authn" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" @@ -432,6 +434,10 @@ func (hs *HTTPServer) updateOrgUserHelper(c *contextmodel.ReqContext, cmd org.Up } } + if err := hs.idService.RemoveIDToken(c.Req.Context(), &authn.Identity{ID: strconv.FormatInt(cmd.UserID, 10), Type: claims.TypeUser, OrgID: cmd.OrgID}); err != nil { + return response.Error(http.StatusInternalServerError, "Failed to invalidate the ID token cache", err) + } + if err := hs.orgService.UpdateOrgUser(c.Req.Context(), &cmd); err != nil { if errors.Is(err, org.ErrLastOrgAdmin) { return response.Error(http.StatusBadRequest, "Cannot change role so that there is no organization admin left", nil) diff --git a/pkg/api/org_users_test.go b/pkg/api/org_users_test.go index 59bb47330d6..324e9da2b39 100644 --- a/pkg/api/org_users_test.go +++ b/pkg/api/org_users_test.go @@ -9,9 +9,12 @@ import ( "strings" "testing" + "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/services/auth/idtest" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn/authntest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/api/dtos" @@ -202,11 +205,12 @@ func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) { func TestOrgUsersAPIEndpoint_updateOrgRole(t *testing.T) { type testCase struct { - desc string - SkipOrgRoleSync bool - AuthEnabled bool - AuthModule string - expectedCode int + desc string + SkipOrgRoleSync bool + AuthEnabled bool + AuthModule string + shouldInvalidateIDToken bool + expectedCode int } permissions := []accesscontrol.Permission{ {Action: accesscontrol.ActionOrgUsersRead, Scope: "users:*"}, @@ -216,11 +220,12 @@ func TestOrgUsersAPIEndpoint_updateOrgRole(t *testing.T) { } tests := []testCase{ { - desc: "should be able to change basicRole when skip_org_role_sync true", - SkipOrgRoleSync: true, - AuthEnabled: true, - AuthModule: login.LDAPAuthModule, - expectedCode: http.StatusOK, + desc: "should be able to change basicRole when skip_org_role_sync true", + SkipOrgRoleSync: true, + AuthEnabled: true, + AuthModule: login.LDAPAuthModule, + shouldInvalidateIDToken: true, + expectedCode: http.StatusOK, }, { desc: "should not be able to change basicRole when skip_org_role_sync false", @@ -237,18 +242,20 @@ func TestOrgUsersAPIEndpoint_updateOrgRole(t *testing.T) { expectedCode: http.StatusForbidden, }, { - desc: "should be able to change basicRole with a basic Auth", - SkipOrgRoleSync: false, - AuthEnabled: false, - AuthModule: "", - expectedCode: http.StatusOK, + desc: "should be able to change basicRole with a basic Auth", + SkipOrgRoleSync: false, + AuthEnabled: false, + AuthModule: "", + shouldInvalidateIDToken: true, + expectedCode: http.StatusOK, }, { - desc: "should be able to change basicRole with a basic Auth", - SkipOrgRoleSync: true, - AuthEnabled: true, - AuthModule: "", - expectedCode: http.StatusOK, + desc: "should be able to change basicRole with a basic Auth", + SkipOrgRoleSync: true, + AuthEnabled: true, + AuthModule: "", + shouldInvalidateIDToken: true, + expectedCode: http.StatusOK, }, } @@ -279,6 +286,11 @@ func TestOrgUsersAPIEndpoint_updateOrgRole(t *testing.T) { } hs.userService = &usertest.FakeUserService{ExpectedSignedInUser: userWithPermissions} hs.orgService = &orgtest.FakeOrgService{} + idService := &idtest.MockService{} + if tt.shouldInvalidateIDToken { + idService.On("RemoveIDToken", mock.Anything, mock.Anything).Return(nil) + } + hs.idService = idService hs.SocialService = &socialtest.FakeSocialService{ ExpectedAuthInfoProvider: &social.OAuthInfo{Enabled: tt.AuthEnabled, SkipOrgRoleSync: tt.SkipOrgRoleSync}, } @@ -615,6 +627,7 @@ func TestOrgUsersAPIEndpointWithSetPerms_AccessControl(t *testing.T) { ExpectedUser: &user.User{}, ExpectedSignedInUser: userWithPermissions(1, tt.permissions), } + hs.idService = &idtest.FakeService{} hs.accesscontrolService = &actest.FakeService{} }) @@ -637,16 +650,24 @@ func TestPatchOrgUsersAPIEndpoint_AccessControl(t *testing.T) { name string role org.RoleType permissions []accesscontrol.Permission + setup func(*testing.T, *idtest.MockService) input string expectedCode int } tests := []testCase{ { - name: "user with permissions can update org role", - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersWrite, Scope: "users:*"}}, - role: org.RoleAdmin, - input: `{"role": "Viewer"}`, + name: "user with permissions can update org role", + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersWrite, Scope: "users:*"}}, + role: org.RoleAdmin, + input: `{"role": "Viewer"}`, + setup: func(t *testing.T, idService *idtest.MockService) { + idService.On("RemoveIDToken", mock.Anything, mock.MatchedBy(func(id *authn.Identity) bool { + return id.GetIdentityType() == types.TypeUser && + id.GetID() == "user:1" && + id.GetOrgID() == int64(1) + })).Return(nil) + }, expectedCode: http.StatusOK, }, { @@ -673,6 +694,11 @@ func TestPatchOrgUsersAPIEndpoint_AccessControl(t *testing.T) { AuthModule: "", }, } + idService := &idtest.MockService{} + if tt.setup != nil { + tt.setup(t, idService) + } + hs.idService = idService hs.accesscontrolService = &actest.FakeService{} hs.userService = &usertest.FakeUserService{ ExpectedUser: &user.User{}, diff --git a/pkg/api/user_test.go b/pkg/api/user_test.go index 557af60259a..c452802bba4 100644 --- a/pkg/api/user_test.go +++ b/pkg/api/user_test.go @@ -463,7 +463,7 @@ func setupUpdateEmailTests(t *testing.T, cfg *setting.Cfg) (*user.User, *HTTPSer require.NoError(t, err) nsMock := notifications.MockNotificationService() - verifier := userimpl.ProvideVerifier(cfg, userSvc, tempUserService, nsMock, &idtest.MockService{}) + verifier := userimpl.ProvideVerifier(cfg, userSvc, tempUserService, nsMock, &idtest.FakeService{}) hs := &HTTPServer{ Cfg: cfg, @@ -688,7 +688,7 @@ func TestUser_UpdateEmail(t *testing.T) { hs.tempUserService = tempUserSvc hs.NotificationService = nsMock hs.SecretsService = fakes.NewFakeSecretsService() - hs.userVerifier = userimpl.ProvideVerifier(settings, userSvc, tempUserSvc, nsMock, &idtest.MockService{}) + hs.userVerifier = userimpl.ProvideVerifier(settings, userSvc, tempUserSvc, nsMock, &idtest.FakeService{}) // User is internal hs.authInfoService = &authinfotest.FakeService{ExpectedError: user.ErrUserNotFound} }) diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index f74e3ba70a4..a9074f89305 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/pkg/apimachinery go 1.23.1 require ( - github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029 // @grafana/identity-access-team + github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569 // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c // @grafana/identity-access-team github.com/stretchr/testify v1.10.0 k8s.io/apimachinery v0.32.1 diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 20fc926c23f..852b52d54c3 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -32,8 +32,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029 h1:0C0a1FEkecxDk60su4uuOozqBzqz/4nmfNFSxXnCViY= -github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= +github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569 h1:bQw6fdcxVdZ6xmZVhMtRgGEKIT1Zc6y+i1PWF8tMX4Q= +github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= diff --git a/pkg/services/auth/id.go b/pkg/services/auth/id.go index 18096eee393..b6797004bc6 100644 --- a/pkg/services/auth/id.go +++ b/pkg/services/auth/id.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" ) +//go:generate mockery --name IDService --structname MockService --outpkg idtest --filename mock.go --output ./idtest/ type IDService interface { // SignIdentity signs a id token for provided identity that can be forwarded to plugins and external services SignIdentity(ctx context.Context, id identity.Requester) (string, *authnlib.Claims[authnlib.IDTokenClaims], error) diff --git a/pkg/services/auth/idimpl/service.go b/pkg/services/auth/idimpl/service.go index 1ade33e37f8..a4b89204d7b 100644 --- a/pkg/services/auth/idimpl/service.go +++ b/pkg/services/auth/idimpl/service.go @@ -109,6 +109,10 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri idClaims.Rest.DisplayName = id.GetName() } + if id.GetOrgRole().IsValid() { + idClaims.Rest.Role = string(id.GetOrgRole()) + } + token, err := s.signer.SignIDToken(ctx, idClaims) if err != nil { s.metrics.failedTokenSigningCounter.Inc() diff --git a/pkg/services/auth/idimpl/service_test.go b/pkg/services/auth/idimpl/service_test.go index f0ab27db562..0f3814968e3 100644 --- a/pkg/services/auth/idimpl/service_test.go +++ b/pkg/services/auth/idimpl/service_test.go @@ -34,7 +34,7 @@ func Test_ProvideService(t *testing.T) { } func TestService_SignIdentity(t *testing.T) { - signer := &idtest.MockSigner{ + signer := &idtest.FakeSigner{ SignIDTokenFn: func(_ context.Context, claims *auth.IDClaims) (string, error) { key := []byte("key") s, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: key}, nil) diff --git a/pkg/services/auth/idtest/fake.go b/pkg/services/auth/idtest/fake.go new file mode 100644 index 00000000000..510d5b88687 --- /dev/null +++ b/pkg/services/auth/idtest/fake.go @@ -0,0 +1,42 @@ +package idtest + +import ( + "context" + + authnlib "github.com/grafana/authlib/authn" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/services/auth" +) + +var _ auth.IDService = (*FakeService)(nil) + +type FakeService struct { + SignIdentityFn func(ctx context.Context, identity identity.Requester) (string, *authnlib.Claims[authnlib.IDTokenClaims], error) + RemoveIDTokenFn func(ctx context.Context, identity identity.Requester) error +} + +func (m *FakeService) SignIdentity(ctx context.Context, identity identity.Requester) (string, *authnlib.Claims[authnlib.IDTokenClaims], error) { + if m.SignIdentityFn != nil { + return m.SignIdentityFn(ctx, identity) + } + return "", nil, nil +} + +func (m *FakeService) RemoveIDToken(ctx context.Context, identity identity.Requester) error { + if m.RemoveIDTokenFn != nil { + return m.RemoveIDTokenFn(ctx, identity) + } + return nil +} + +type FakeSigner struct { + SignIDTokenFn func(ctx context.Context, claims *auth.IDClaims) (string, error) +} + +func (s *FakeSigner) SignIDToken(ctx context.Context, claims *auth.IDClaims) (string, error) { + if s.SignIDTokenFn != nil { + return s.SignIDTokenFn(ctx, claims) + } + return "", nil +} diff --git a/pkg/services/auth/idtest/mock.go b/pkg/services/auth/idtest/mock.go index cd506836d13..cb9aeadf316 100644 --- a/pkg/services/auth/idtest/mock.go +++ b/pkg/services/auth/idtest/mock.go @@ -1,42 +1,87 @@ +// Code generated by mockery v2.42.1. DO NOT EDIT. + package idtest import ( - "context" + context "context" - authnlib "github.com/grafana/authlib/authn" + authn "github.com/grafana/authlib/authn" - "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/services/auth" + identity "github.com/grafana/grafana/pkg/apimachinery/identity" + + mock "github.com/stretchr/testify/mock" ) -var _ auth.IDService = (*MockService)(nil) - +// MockService is an autogenerated mock type for the IDService type type MockService struct { - SignIdentityFn func(ctx context.Context, identity identity.Requester) (string, *authnlib.Claims[authnlib.IDTokenClaims], error) - RemoveIDTokenFn func(ctx context.Context, identity identity.Requester) error + mock.Mock } -func (m *MockService) SignIdentity(ctx context.Context, identity identity.Requester) (string, *authnlib.Claims[authnlib.IDTokenClaims], error) { - if m.SignIdentityFn != nil { - return m.SignIdentityFn(ctx, identity) +// RemoveIDToken provides a mock function with given fields: ctx, _a1 +func (_m *MockService) RemoveIDToken(ctx context.Context, _a1 identity.Requester) error { + ret := _m.Called(ctx, _a1) + + if len(ret) == 0 { + panic("no return value specified for RemoveIDToken") } - return "", nil, nil -} -func (m *MockService) RemoveIDToken(ctx context.Context, identity identity.Requester) error { - if m.RemoveIDTokenFn != nil { - return m.RemoveIDTokenFn(ctx, identity) + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) error); ok { + r0 = rf(ctx, _a1) + } else { + r0 = ret.Error(0) } - return nil + + return r0 } -type MockSigner struct { - SignIDTokenFn func(ctx context.Context, claims *auth.IDClaims) (string, error) -} +// SignIdentity provides a mock function with given fields: ctx, id +func (_m *MockService) SignIdentity(ctx context.Context, id identity.Requester) (string, *authn.Claims[authn.IDTokenClaims], error) { + ret := _m.Called(ctx, id) -func (s *MockSigner) SignIDToken(ctx context.Context, claims *auth.IDClaims) (string, error) { - if s.SignIDTokenFn != nil { - return s.SignIDTokenFn(ctx, claims) + if len(ret) == 0 { + panic("no return value specified for SignIdentity") } - return "", nil + + var r0 string + var r1 *authn.Claims[authn.IDTokenClaims] + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) (string, *authn.Claims[authn.IDTokenClaims], error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) string); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context, identity.Requester) *authn.Claims[authn.IDTokenClaims]); ok { + r1 = rf(ctx, id) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*authn.Claims[authn.IDTokenClaims]) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, identity.Requester) error); ok { + r2 = rf(ctx, id) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// NewMockService creates a new instance of MockService. 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 NewMockService(t interface { + mock.TestingT + Cleanup(func()) +}) *MockService { + mock := &MockService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock } diff --git a/pkg/services/serviceaccounts/manager/service.go b/pkg/services/serviceaccounts/manager/service.go index 2bb64c3646a..f53e7b72d9b 100644 --- a/pkg/services/serviceaccounts/manager/service.go +++ b/pkg/services/serviceaccounts/manager/service.go @@ -17,6 +17,8 @@ import ( "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apikey" + "github.com/grafana/grafana/pkg/services/auth" + "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts/database" @@ -42,6 +44,7 @@ type ServiceAccountsService struct { secretScanService secretscan.Checker orgService org.Service serverLock *serverlock.ServerLockService + idService auth.IDService secretScanEnabled bool secretScanInterval time.Duration @@ -58,6 +61,7 @@ func ProvideServiceAccountsService( acService accesscontrol.Service, permissions accesscontrol.ServiceAccountPermissionsService, serverLockService *serverlock.ServerLockService, + idService auth.IDService, ) (*ServiceAccountsService, error) { serviceAccountsStore := database.ProvideServiceAccountsStore( cfg, @@ -77,6 +81,7 @@ func ProvideServiceAccountsService( backgroundLog: log.New("serviceaccounts.background"), orgService: orgService, serverLock: serverLockService, + idService: idService, } if err := RegisterRoles(acService); err != nil { @@ -265,6 +270,11 @@ func (sa *ServiceAccountsService) UpdateServiceAccount(ctx context.Context, orgI if err := validServiceAccountID(serviceAccountID); err != nil { return nil, err } + + if err := sa.idService.RemoveIDToken(ctx, &authn.Identity{ID: strconv.FormatInt(serviceAccountID, 10), Type: claims.TypeServiceAccount, OrgID: orgID}); err != nil { + return nil, err + } + return sa.store.UpdateServiceAccount(ctx, orgID, serviceAccountID, saForm) } diff --git a/pkg/services/user/userimpl/verifier_test.go b/pkg/services/user/userimpl/verifier_test.go index 8f7f17218d6..0bb9ecb2437 100644 --- a/pkg/services/user/userimpl/verifier_test.go +++ b/pkg/services/user/userimpl/verifier_test.go @@ -21,7 +21,7 @@ func TestVerifier_Start(t *testing.T) { ts := &tempusertest.FakeTempUserService{} us := &usertest.FakeUserService{} ns := notifications.MockNotificationService() - is := &idtest.MockService{} + is := &idtest.FakeService{} type calls struct { expireCalled bool @@ -116,7 +116,7 @@ func TestVerifier_Complete(t *testing.T) { ts := &tempusertest.FakeTempUserService{} us := &usertest.FakeUserService{} ns := notifications.MockNotificationService() - is := &idtest.MockService{} + is := &idtest.FakeService{} type calls struct { updateCalled bool diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index 61ab8c11119..a409151a0d4 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -179,7 +179,7 @@ require ( github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 // indirect - github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029 // indirect + github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 // indirect github.com/grafana/grafana-app-sdk/logging v0.30.0 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index 9c7ca47a33a..73e0a03e693 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -561,8 +561,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 h1:dG/UKAjY/KlKp9fY8aEm+gSQHHRmPm5q+9cea3hRSu8= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= -github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029 h1:0C0a1FEkecxDk60su4uuOozqBzqz/4nmfNFSxXnCViY= -github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= +github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569 h1:bQw6fdcxVdZ6xmZVhMtRgGEKIT1Zc6y+i1PWF8tMX4Q= +github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA= diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index b5616a63c8c..3e2c7757443 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -11,7 +11,7 @@ replace ( require ( github.com/fullstorydev/grpchan v1.1.1 github.com/google/uuid v1.6.0 - github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029 + github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569 github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 github.com/grafana/grafana v11.4.0-00010101000000-000000000000+incompatible diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 3bda999b30b..4569d6883a7 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -399,8 +399,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 h1:dG/UKAjY/KlKp9fY8aEm+gSQHHRmPm5q+9cea3hRSu8= github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= -github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029 h1:0C0a1FEkecxDk60su4uuOozqBzqz/4nmfNFSxXnCViY= -github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= +github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569 h1:bQw6fdcxVdZ6xmZVhMtRgGEKIT1Zc6y+i1PWF8tMX4Q= +github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6kE/MWfg7s=