Auth: Add OrgRole to ID token (#100383)

* Changes for Users and ServiceAccounts

* Align tests
pull/98609/head
Misi 3 months ago committed by GitHub
parent a5c8b5ed83
commit ee0a1391df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      go.mod
  2. 4
      go.sum
  3. 4
      pkg/api/http_server.go
  4. 6
      pkg/api/org_users.go
  5. 74
      pkg/api/org_users_test.go
  6. 4
      pkg/api/user_test.go
  7. 2
      pkg/apimachinery/go.mod
  8. 4
      pkg/apimachinery/go.sum
  9. 1
      pkg/services/auth/id.go
  10. 4
      pkg/services/auth/idimpl/service.go
  11. 2
      pkg/services/auth/idimpl/service_test.go
  12. 42
      pkg/services/auth/idtest/fake.go
  13. 93
      pkg/services/auth/idtest/mock.go
  14. 10
      pkg/services/serviceaccounts/manager/service.go
  15. 4
      pkg/services/user/userimpl/verifier_test.go
  16. 2
      pkg/storage/unified/apistore/go.mod
  17. 4
      pkg/storage/unified/apistore/go.sum
  18. 2
      pkg/storage/unified/resource/go.mod
  19. 4
      pkg/storage/unified/resource/go.sum

@ -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

@ -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=

@ -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,

@ -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)

@ -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{},

@ -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}
})

@ -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

@ -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=

@ -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)

@ -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()

@ -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)

@ -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
}

@ -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"
var _ auth.IDService = (*MockService)(nil)
mock "github.com/stretchr/testify/mock"
)
// 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
}
type MockSigner struct {
SignIDTokenFn func(ctx context.Context, claims *auth.IDClaims) (string, error)
return r0
}
func (s *MockSigner) SignIDToken(ctx context.Context, claims *auth.IDClaims) (string, error) {
if s.SignIDTokenFn != nil {
return s.SignIDTokenFn(ctx, claims)
// 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)
if len(ret) == 0 {
panic("no return value specified for SignIdentity")
}
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)
}
return "", nil
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
}

@ -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)
}

@ -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

@ -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

@ -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=

@ -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

@ -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=

Loading…
Cancel
Save