AuthN: Extract from OAuthServer service account management code (#76128)
* Extract code to manage service accounts * Add test with client credentials grants * Fix test with the changed interface * Wire * Fix HandleTokenRequest * Add tests to extsvcaccounts * Rename Retrieve function * Document the interface
This commit is contained in:
@@ -26,7 +26,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/slugify"
|
||||
"github.com/grafana/grafana/pkg/models/roletype"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/extsvcauth"
|
||||
"github.com/grafana/grafana/pkg/services/extsvcauth/oauthserver"
|
||||
@@ -34,8 +33,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/extsvcauth/oauthserver/store"
|
||||
"github.com/grafana/grafana/pkg/services/extsvcauth/oauthserver/utils"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/signingkeys"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@@ -57,14 +54,14 @@ type OAuth2ServiceImpl struct {
|
||||
logger log.Logger
|
||||
accessControl ac.AccessControl
|
||||
acService ac.Service
|
||||
saService serviceaccounts.Service
|
||||
saService extsvcauth.ExtSvcAccountsService
|
||||
userService user.Service
|
||||
teamService team.Service
|
||||
publicKey any
|
||||
}
|
||||
|
||||
func ProvideService(router routing.RouteRegister, db db.DB, cfg *setting.Cfg,
|
||||
svcAccSvc serviceaccounts.Service, accessControl ac.AccessControl, acSvc ac.Service, userSvc user.Service,
|
||||
extSvcAccSvc extsvcauth.ExtSvcAccountsService, accessControl ac.AccessControl, acSvc ac.Service, userSvc user.Service,
|
||||
teamSvc team.Service, keySvc signingkeys.Service, fmgmt *featuremgmt.FeatureManager) (*OAuth2ServiceImpl, error) {
|
||||
if !fmgmt.IsEnabled(featuremgmt.FlagExternalServiceAuth) {
|
||||
return nil, nil
|
||||
@@ -86,7 +83,7 @@ func ProvideService(router routing.RouteRegister, db db.DB, cfg *setting.Cfg,
|
||||
sqlstore: store.NewStore(db),
|
||||
logger: log.New("oauthserver"),
|
||||
userService: userSvc,
|
||||
saService: svcAccSvc,
|
||||
saService: extSvcAccSvc,
|
||||
teamService: teamSvc,
|
||||
}
|
||||
|
||||
@@ -152,15 +149,15 @@ func (s *OAuth2ServiceImpl) GetExternalService(ctx context.Context, id string) (
|
||||
|
||||
// Retrieve self permissions and generate a signed in user
|
||||
s.logger.Debug("GetExternalService: fetch permissions", "client id", id)
|
||||
sa, err := s.saService.RetrieveServiceAccount(ctx, oauthserver.TmpOrgID, client.ServiceAccountID)
|
||||
sa, err := s.saService.RetrieveExtSvcAccount(ctx, oauthserver.TmpOrgID, client.ServiceAccountID)
|
||||
if err != nil {
|
||||
s.logger.Error("GetExternalService: error fetching service account", "id", id, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
client.SignedInUser = &user.SignedInUser{
|
||||
UserID: sa.Id,
|
||||
UserID: sa.ID,
|
||||
OrgID: oauthserver.TmpOrgID,
|
||||
OrgRole: org.RoleType(sa.Role), // Need this to compute the permissions in OSS
|
||||
OrgRole: sa.Role, // Need this to compute the permissions in OSS
|
||||
Login: sa.Login,
|
||||
Name: sa.Name,
|
||||
Permissions: map[int64]map[string][]string{},
|
||||
@@ -225,13 +222,6 @@ func (s *OAuth2ServiceImpl) SaveExternalService(ctx context.Context, registratio
|
||||
return nil, errGenCred
|
||||
}
|
||||
|
||||
s.logger.Debug("Save service account")
|
||||
saID, errSaveServiceAccount := s.saveServiceAccount(ctx, client.Name, client.ServiceAccountID, client.SelfPermissions)
|
||||
if errSaveServiceAccount != nil {
|
||||
return nil, errSaveServiceAccount
|
||||
}
|
||||
client.ServiceAccountID = saID
|
||||
|
||||
grantTypes := s.computeGrantTypes(registration.Self.Enabled, registration.Impersonation.Enabled)
|
||||
client.GrantTypes = strings.Join(grantTypes, ",")
|
||||
|
||||
@@ -254,6 +244,18 @@ func (s *OAuth2ServiceImpl) SaveExternalService(ctx context.Context, registratio
|
||||
}
|
||||
client.Secret = string(hashedSecret)
|
||||
|
||||
s.logger.Debug("Save service account")
|
||||
saID, errSaveServiceAccount := s.saService.ManageExtSvcAccount(ctx, &extsvcauth.ManageExtSvcAccountCmd{
|
||||
ExtSvcSlug: slugify.Slugify(client.Name),
|
||||
Enabled: registration.Self.Enabled,
|
||||
OrgID: oauthserver.TmpOrgID,
|
||||
Permissions: client.SelfPermissions,
|
||||
})
|
||||
if errSaveServiceAccount != nil {
|
||||
return nil, errSaveServiceAccount
|
||||
}
|
||||
client.ServiceAccountID = saID
|
||||
|
||||
err = s.sqlstore.SaveExternalService(ctx, client)
|
||||
if err != nil {
|
||||
s.logger.Error("Error saving external service", "client", client.LogID(), "error", err)
|
||||
@@ -380,95 +382,6 @@ func (s *OAuth2ServiceImpl) handleKeyOptions(ctx context.Context, keyOption *ext
|
||||
return nil, fmt.Errorf("at least one key option must be specified")
|
||||
}
|
||||
|
||||
// saveServiceAccount creates a service account if the service account ID is NoServiceAccountID, otherwise it updates the service account's permissions
|
||||
func (s *OAuth2ServiceImpl) saveServiceAccount(ctx context.Context, extSvcName string, saID int64, permissions []ac.Permission) (int64, error) {
|
||||
if saID == oauthserver.NoServiceAccountID {
|
||||
// Create a service account
|
||||
s.logger.Debug("Create service account", "external service name", extSvcName)
|
||||
return s.createServiceAccount(ctx, extSvcName, permissions)
|
||||
}
|
||||
|
||||
// check if the service account exists
|
||||
s.logger.Debug("Update service account", "external service name", extSvcName)
|
||||
sa, err := s.saService.RetrieveServiceAccount(ctx, oauthserver.TmpOrgID, saID)
|
||||
if err != nil {
|
||||
s.logger.Error("Error retrieving service account", "external service name", extSvcName, "error", err)
|
||||
return oauthserver.NoServiceAccountID, err
|
||||
}
|
||||
|
||||
// update the service account's permissions
|
||||
if len(permissions) > 0 {
|
||||
s.logger.Debug("Update role permissions", "external service name", extSvcName, "saID", saID)
|
||||
if err := s.acService.SaveExternalServiceRole(ctx, ac.SaveExternalServiceRoleCommand{
|
||||
OrgID: ac.GlobalOrgID,
|
||||
Global: true,
|
||||
ExternalServiceID: extSvcName,
|
||||
ServiceAccountID: sa.Id,
|
||||
Permissions: permissions,
|
||||
}); err != nil {
|
||||
return oauthserver.NoServiceAccountID, err
|
||||
}
|
||||
return saID, nil
|
||||
}
|
||||
|
||||
// remove the service account
|
||||
errDelete := s.deleteServiceAccount(ctx, extSvcName, sa.Id)
|
||||
return oauthserver.NoServiceAccountID, errDelete
|
||||
}
|
||||
|
||||
// deleteServiceAccount deletes a service account by ID and removes its associated role
|
||||
func (s *OAuth2ServiceImpl) deleteServiceAccount(ctx context.Context, extSvcName string, saID int64) error {
|
||||
s.logger.Debug("Delete service account", "external service name", extSvcName, "saID", saID)
|
||||
if err := s.saService.DeleteServiceAccount(ctx, oauthserver.TmpOrgID, saID); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.acService.DeleteExternalServiceRole(ctx, extSvcName)
|
||||
}
|
||||
|
||||
// createServiceAccount creates a service account with the given permissions and returns the ID of the service account
|
||||
// When no permission is given, the account isn't created and NoServiceAccountID is returned
|
||||
// This first design does not use a single transaction for the whole service account creation process => database consistency is not guaranteed.
|
||||
// Consider changing this in the future.
|
||||
func (s *OAuth2ServiceImpl) createServiceAccount(ctx context.Context, extSvcName string, permissions []ac.Permission) (int64, error) {
|
||||
if len(permissions) == 0 {
|
||||
// No permission, no service account
|
||||
s.logger.Debug("No permission, no service account", "external service name", extSvcName)
|
||||
return oauthserver.NoServiceAccountID, nil
|
||||
}
|
||||
|
||||
newRole := func(r roletype.RoleType) *roletype.RoleType {
|
||||
return &r
|
||||
}
|
||||
newBool := func(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
slug := slugify.Slugify(extSvcName)
|
||||
|
||||
s.logger.Debug("Generate service account", "external service name", extSvcName, "orgID", oauthserver.TmpOrgID, "name", slug)
|
||||
sa, err := s.saService.CreateServiceAccount(ctx, oauthserver.TmpOrgID, &serviceaccounts.CreateServiceAccountForm{
|
||||
Name: slug,
|
||||
Role: newRole(roletype.RoleNone),
|
||||
IsDisabled: newBool(false),
|
||||
})
|
||||
if err != nil {
|
||||
return oauthserver.NoServiceAccountID, err
|
||||
}
|
||||
|
||||
s.logger.Debug("Create tailored role for service account", "external service name", extSvcName, "name", slug, "service_account_id", sa.Id, "permissions", permissions)
|
||||
if err := s.acService.SaveExternalServiceRole(ctx, ac.SaveExternalServiceRoleCommand{
|
||||
OrgID: ac.GlobalOrgID,
|
||||
Global: true,
|
||||
ExternalServiceID: slug,
|
||||
ServiceAccountID: sa.Id,
|
||||
Permissions: permissions,
|
||||
}); err != nil {
|
||||
return oauthserver.NoServiceAccountID, err
|
||||
}
|
||||
|
||||
return sa.Id, nil
|
||||
}
|
||||
|
||||
// handleRegistrationPermissions parses the registration form to retrieve requested permissions and adds default
|
||||
// permissions when impersonation is requested
|
||||
func (*OAuth2ServiceImpl) handleRegistrationPermissions(registration *extsvcauth.ExternalServiceRegistration) ([]ac.Permission, []ac.Permission) {
|
||||
|
||||
@@ -18,16 +18,15 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models/roletype"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
"github.com/grafana/grafana/pkg/services/extsvcauth"
|
||||
"github.com/grafana/grafana/pkg/services/extsvcauth/extsvcmocks"
|
||||
"github.com/grafana/grafana/pkg/services/extsvcauth/oauthserver"
|
||||
"github.com/grafana/grafana/pkg/services/extsvcauth/oauthserver/oastest"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
sa "github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
satests "github.com/grafana/grafana/pkg/services/serviceaccounts/tests"
|
||||
"github.com/grafana/grafana/pkg/services/signingkeys/signingkeystest"
|
||||
"github.com/grafana/grafana/pkg/services/team/teamtest"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@@ -52,7 +51,7 @@ type TestEnv struct {
|
||||
OAuthStore *oastest.MockStore
|
||||
UserService *usertest.FakeUserService
|
||||
TeamService *teamtest.FakeService
|
||||
SAService *satests.MockServiceAccountService
|
||||
SAService *extsvcmocks.MockExtSvcAccountsService
|
||||
}
|
||||
|
||||
func setupTestEnv(t *testing.T) *TestEnv {
|
||||
@@ -77,7 +76,7 @@ func setupTestEnv(t *testing.T) *TestEnv {
|
||||
OAuthStore: &oastest.MockStore{},
|
||||
UserService: usertest.NewUserServiceFake(),
|
||||
TeamService: teamtest.NewFakeService(),
|
||||
SAService: &satests.MockServiceAccountService{},
|
||||
SAService: extsvcmocks.NewMockExtSvcAccountsService(t),
|
||||
}
|
||||
env.S = &OAuth2ServiceImpl{
|
||||
cache: localcache.New(cacheExpirationTime, cacheCleanupInterval),
|
||||
@@ -106,22 +105,6 @@ func setupTestEnv(t *testing.T) *TestEnv {
|
||||
func TestOAuth2ServiceImpl_SaveExternalService(t *testing.T) {
|
||||
const serviceName = "my-ext-service"
|
||||
|
||||
sa1 := sa.ServiceAccountDTO{Id: 1, Name: serviceName, Login: serviceName, OrgId: oauthserver.TmpOrgID, IsDisabled: false, Role: "None"}
|
||||
sa1Profile := sa.ServiceAccountProfileDTO{Id: 1, Name: serviceName, Login: serviceName, OrgId: oauthserver.TmpOrgID, IsDisabled: false, Role: "None"}
|
||||
prevSaID := int64(3)
|
||||
// Using a function to prevent modifying the same object in the tests
|
||||
client1 := func() *oauthserver.OAuthExternalService {
|
||||
return &oauthserver.OAuthExternalService{
|
||||
Name: serviceName,
|
||||
ClientID: "RANDOMID",
|
||||
Secret: "RANDOMSECRET",
|
||||
GrantTypes: "client_credentials",
|
||||
PublicPem: []byte("-----BEGIN PUBLIC KEY-----"),
|
||||
ServiceAccountID: prevSaID,
|
||||
SelfPermissions: []ac.Permission{{Action: "users:impersonate", Scope: "users:*"}},
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
init func(*TestEnv)
|
||||
@@ -135,6 +118,9 @@ func TestOAuth2ServiceImpl_SaveExternalService(t *testing.T) {
|
||||
// No client at the beginning
|
||||
env.OAuthStore.On("GetExternalServiceByName", mock.Anything, mock.Anything).Return(nil, oauthserver.ErrClientNotFound(serviceName))
|
||||
env.OAuthStore.On("SaveExternalService", mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
// Return a service account ID
|
||||
env.SAService.On("ManageExtSvcAccount", mock.Anything, mock.Anything).Return(int64(0), nil)
|
||||
},
|
||||
cmd: &extsvcauth.ExternalServiceRegistration{
|
||||
Name: serviceName,
|
||||
@@ -152,99 +138,38 @@ func TestOAuth2ServiceImpl_SaveExternalService(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should create a service account",
|
||||
name: "should allow client credentials grant with correct permissions",
|
||||
init: func(env *TestEnv) {
|
||||
// No client at the beginning
|
||||
env.OAuthStore.On("GetExternalServiceByName", mock.Anything, mock.Anything).Return(nil, oauthserver.ErrClientNotFound(serviceName))
|
||||
env.OAuthStore.On("SaveExternalService", mock.Anything, mock.Anything).Return(nil)
|
||||
// Service account and permission creation
|
||||
env.SAService.On("CreateServiceAccount", mock.Anything, mock.Anything, mock.Anything).Return(&sa1, nil)
|
||||
env.AcStore.On("SaveExternalServiceRole", mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
// Return a service account ID
|
||||
env.SAService.On("ManageExtSvcAccount", mock.Anything, mock.Anything).Return(int64(10), nil)
|
||||
},
|
||||
cmd: &extsvcauth.ExternalServiceRegistration{
|
||||
Name: serviceName,
|
||||
OAuthProviderCfg: &extsvcauth.OAuthProviderCfg{Key: &extsvcauth.KeyOption{Generate: true}},
|
||||
Name: serviceName,
|
||||
Self: extsvcauth.SelfCfg{
|
||||
Enabled: true,
|
||||
Permissions: []ac.Permission{{Action: "users:read", Scope: "users:*"}},
|
||||
Permissions: []ac.Permission{{Action: ac.ActionUsersRead, Scope: ac.ScopeUsersAll}},
|
||||
},
|
||||
},
|
||||
mockChecks: func(t *testing.T, env *TestEnv) {
|
||||
// Check that the client has a service account and the correct grant type
|
||||
env.OAuthStore.AssertCalled(t, "SaveExternalService", mock.Anything, mock.MatchedBy(func(client *oauthserver.OAuthExternalService) bool {
|
||||
return client.Name == serviceName &&
|
||||
client.GrantTypes == "client_credentials" && client.ServiceAccountID == sa1.Id
|
||||
}))
|
||||
// Check that the service account is created in the correct org with the correct role
|
||||
env.SAService.AssertCalled(t, "CreateServiceAccount", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == oauthserver.TmpOrgID }),
|
||||
mock.MatchedBy(func(cmd *sa.CreateServiceAccountForm) bool {
|
||||
return cmd.Name == serviceName && *cmd.Role == roletype.RoleNone
|
||||
}),
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should delete the service account",
|
||||
init: func(env *TestEnv) {
|
||||
// Existing client (with a service account hence a role)
|
||||
env.OAuthStore.On("GetExternalServiceByName", mock.Anything, mock.Anything).Return(client1(), nil)
|
||||
env.OAuthStore.On("SaveExternalService", mock.Anything, mock.Anything).Return(nil)
|
||||
env.SAService.On("RetrieveServiceAccount", mock.Anything, mock.Anything, mock.Anything).Return(&sa1Profile, nil)
|
||||
// No permission anymore will trigger deletion of the service account and its role
|
||||
env.SAService.On("DeleteServiceAccount", mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
env.AcStore.On("DeleteExternalServiceRole", mock.Anything, mock.Anything).Return(nil)
|
||||
},
|
||||
cmd: &extsvcauth.ExternalServiceRegistration{
|
||||
Name: serviceName,
|
||||
OAuthProviderCfg: &extsvcauth.OAuthProviderCfg{Key: &extsvcauth.KeyOption{Generate: true}},
|
||||
Self: extsvcauth.SelfCfg{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
mockChecks: func(t *testing.T, env *TestEnv) {
|
||||
// Check that the service has no service account anymore
|
||||
env.OAuthStore.AssertCalled(t, "SaveExternalService", mock.Anything, mock.MatchedBy(func(client *oauthserver.OAuthExternalService) bool {
|
||||
return client.Name == serviceName && client.ServiceAccountID == oauthserver.NoServiceAccountID
|
||||
env.OAuthStore.AssertCalled(t, "GetExternalServiceByName", mock.Anything, mock.MatchedBy(func(name string) bool {
|
||||
return name == serviceName
|
||||
}))
|
||||
// Check that the service account is retrieved with the correct ID
|
||||
env.SAService.AssertCalled(t, "RetrieveServiceAccount", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == oauthserver.TmpOrgID }),
|
||||
mock.MatchedBy(func(saID int64) bool { return saID == prevSaID }))
|
||||
// Check that the service account is deleted in the correct org
|
||||
env.SAService.AssertCalled(t, "DeleteServiceAccount", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == oauthserver.TmpOrgID }),
|
||||
mock.MatchedBy(func(saID int64) bool { return saID == sa1.Id }))
|
||||
// Check that the associated role is deleted
|
||||
env.AcStore.AssertCalled(t, "DeleteExternalServiceRole", mock.Anything,
|
||||
mock.MatchedBy(func(extSvcName string) bool { return extSvcName == serviceName }))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should update the service account",
|
||||
init: func(env *TestEnv) {
|
||||
// Existing client (with a service account hence a role)
|
||||
env.OAuthStore.On("GetExternalServiceByName", mock.Anything, mock.Anything).Return(client1(), nil)
|
||||
env.OAuthStore.On("SaveExternalService", mock.Anything, mock.Anything).Return(nil)
|
||||
env.SAService.On("RetrieveServiceAccount", mock.Anything, mock.Anything, mock.Anything).Return(&sa1Profile, nil)
|
||||
// Update the service account permissions
|
||||
env.AcStore.On("SaveExternalServiceRole", mock.Anything, mock.Anything).Return(nil)
|
||||
},
|
||||
cmd: &extsvcauth.ExternalServiceRegistration{
|
||||
Name: serviceName,
|
||||
OAuthProviderCfg: &extsvcauth.OAuthProviderCfg{Key: &extsvcauth.KeyOption{Generate: true}},
|
||||
Self: extsvcauth.SelfCfg{
|
||||
Enabled: true,
|
||||
Permissions: []ac.Permission{{Action: "dashboards:create", Scope: "folders:uid:general"}},
|
||||
},
|
||||
},
|
||||
mockChecks: func(t *testing.T, env *TestEnv) {
|
||||
// Ensure new permissions are in place
|
||||
env.AcStore.AssertCalled(t, "SaveExternalServiceRole", mock.Anything,
|
||||
mock.MatchedBy(func(cmd ac.SaveExternalServiceRoleCommand) bool {
|
||||
return cmd.ServiceAccountID == sa1.Id && cmd.ExternalServiceID == client1().Name &&
|
||||
cmd.OrgID == int64(ac.GlobalOrgID) && len(cmd.Permissions) == 1 &&
|
||||
cmd.Permissions[0] == ac.Permission{Action: "dashboards:create", Scope: "folders:uid:general"}
|
||||
env.OAuthStore.AssertCalled(t, "SaveExternalService", mock.Anything, mock.MatchedBy(func(client *oauthserver.OAuthExternalService) bool {
|
||||
return client.Name == serviceName && len(client.ClientID) > 0 && len(client.Secret) > 0 &&
|
||||
client.GrantTypes == string(fosite.GrantTypeClientCredentials) &&
|
||||
len(client.PublicPem) > 0 && client.ServiceAccountID == 10 &&
|
||||
len(client.ImpersonatePermissions) == 0 &&
|
||||
len(client.SelfPermissions) > 0
|
||||
}))
|
||||
// Check that despite no credential_grants the service account still has a permission to impersonate users
|
||||
env.SAService.AssertCalled(t, "ManageExtSvcAccount", mock.Anything,
|
||||
mock.MatchedBy(func(cmd *extsvcauth.ManageExtSvcAccountCmd) bool {
|
||||
return len(cmd.Permissions) == 1 && cmd.Permissions[0] == ac.Permission{Action: ac.ActionUsersRead, Scope: ac.ScopeUsersAll}
|
||||
}))
|
||||
},
|
||||
},
|
||||
@@ -255,8 +180,7 @@ func TestOAuth2ServiceImpl_SaveExternalService(t *testing.T) {
|
||||
env.OAuthStore.On("GetExternalServiceByName", mock.Anything, mock.Anything).Return(nil, oauthserver.ErrClientNotFound(serviceName))
|
||||
env.OAuthStore.On("SaveExternalService", mock.Anything, mock.Anything).Return(nil)
|
||||
// The service account needs to be created with a permission to impersonate users
|
||||
env.SAService.On("CreateServiceAccount", mock.Anything, mock.Anything, mock.Anything).Return(&sa1, nil)
|
||||
env.AcStore.On("SaveExternalServiceRole", mock.Anything, mock.Anything).Return(nil)
|
||||
env.SAService.On("ManageExtSvcAccount", mock.Anything, mock.Anything).Return(int64(10), nil)
|
||||
},
|
||||
cmd: &extsvcauth.ExternalServiceRegistration{
|
||||
Name: serviceName,
|
||||
@@ -277,8 +201,8 @@ func TestOAuth2ServiceImpl_SaveExternalService(t *testing.T) {
|
||||
slices.Contains(impPerm, ac.Permission{Action: ac.ActionTeamsRead, Scope: oauthserver.ScopeTeamsSelf})
|
||||
}))
|
||||
// Check that despite no credential_grants the service account still has a permission to impersonate users
|
||||
env.AcStore.AssertCalled(t, "SaveExternalServiceRole", mock.Anything,
|
||||
mock.MatchedBy(func(cmd ac.SaveExternalServiceRoleCommand) bool {
|
||||
env.SAService.AssertCalled(t, "ManageExtSvcAccount", mock.Anything,
|
||||
mock.MatchedBy(func(cmd *extsvcauth.ManageExtSvcAccountCmd) bool {
|
||||
return len(cmd.Permissions) == 1 && cmd.Permissions[0] == ac.Permission{Action: ac.ActionUsersImpersonate, Scope: ac.ScopeUsersAll}
|
||||
}))
|
||||
},
|
||||
@@ -396,11 +320,11 @@ func TestOAuth2ServiceImpl_GetExternalService(t *testing.T) {
|
||||
name: "should return error when the service account was not found",
|
||||
init: func(env *TestEnv) {
|
||||
env.OAuthStore.On("GetExternalService", mock.Anything, mock.Anything).Return(dummyClient(), nil)
|
||||
env.SAService.On("RetrieveServiceAccount", mock.Anything, int64(1), int64(1)).Return(&sa.ServiceAccountProfileDTO{}, sa.ErrServiceAccountNotFound)
|
||||
env.SAService.On("RetrieveExtSvcAccount", mock.Anything, int64(1), int64(1)).Return(&extsvcauth.ExtSvcAccount{}, sa.ErrServiceAccountNotFound)
|
||||
},
|
||||
mockChecks: func(t *testing.T, env *TestEnv) {
|
||||
env.OAuthStore.AssertCalled(t, "GetExternalService", mock.Anything, mock.Anything)
|
||||
env.SAService.AssertCalled(t, "RetrieveServiceAccount", mock.Anything, 1, 1)
|
||||
env.SAService.AssertCalled(t, "RetrieveExtSvcAccount", mock.Anything, 1, 1)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -408,12 +332,12 @@ func TestOAuth2ServiceImpl_GetExternalService(t *testing.T) {
|
||||
name: "should return error when the service account has no permissions",
|
||||
init: func(env *TestEnv) {
|
||||
env.OAuthStore.On("GetExternalService", mock.Anything, mock.Anything).Return(dummyClient(), nil)
|
||||
env.SAService.On("RetrieveServiceAccount", mock.Anything, int64(1), int64(1)).Return(&sa.ServiceAccountProfileDTO{}, nil)
|
||||
env.SAService.On("RetrieveExtSvcAccount", mock.Anything, int64(1), int64(1)).Return(&extsvcauth.ExtSvcAccount{}, nil)
|
||||
env.AcStore.On("GetUserPermissions", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("some error"))
|
||||
},
|
||||
mockChecks: func(t *testing.T, env *TestEnv) {
|
||||
env.OAuthStore.AssertCalled(t, "GetExternalService", mock.Anything, mock.Anything)
|
||||
env.SAService.AssertCalled(t, "RetrieveServiceAccount", mock.Anything, 1, 1)
|
||||
env.SAService.AssertCalled(t, "RetrieveExtSvcAccount", mock.Anything, 1, 1)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -421,12 +345,12 @@ func TestOAuth2ServiceImpl_GetExternalService(t *testing.T) {
|
||||
name: "should return correctly",
|
||||
init: func(env *TestEnv) {
|
||||
env.OAuthStore.On("GetExternalService", mock.Anything, mock.Anything).Return(dummyClient(), nil)
|
||||
env.SAService.On("RetrieveServiceAccount", mock.Anything, int64(1), int64(1)).Return(&sa.ServiceAccountProfileDTO{Id: 1}, nil)
|
||||
env.SAService.On("RetrieveExtSvcAccount", mock.Anything, int64(1), int64(1)).Return(&extsvcauth.ExtSvcAccount{ID: 1}, nil)
|
||||
env.AcStore.On("GetUserPermissions", mock.Anything, mock.Anything).Return([]ac.Permission{{Action: ac.ActionUsersImpersonate, Scope: ac.ScopeUsersAll}}, nil)
|
||||
},
|
||||
mockChecks: func(t *testing.T, env *TestEnv) {
|
||||
env.OAuthStore.AssertCalled(t, "GetExternalService", mock.Anything, mock.Anything)
|
||||
env.SAService.AssertCalled(t, "RetrieveServiceAccount", mock.Anything, int64(1), int64(1))
|
||||
env.SAService.AssertCalled(t, "RetrieveExtSvcAccount", mock.Anything, int64(1), int64(1))
|
||||
},
|
||||
wantPerm: []ac.Permission{{Action: "users:impersonate", Scope: "users:*"}},
|
||||
},
|
||||
|
||||
@@ -22,8 +22,8 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/models/roletype"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/extsvcauth"
|
||||
"github.com/grafana/grafana/pkg/services/extsvcauth/oauthserver"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
)
|
||||
@@ -703,15 +703,13 @@ func setupHandleTokenRequestEnv(t *testing.T, env *TestEnv, opt func(*oauthserve
|
||||
opt(client1)
|
||||
}
|
||||
|
||||
sa1 := &serviceaccounts.ServiceAccountProfileDTO{
|
||||
Id: client1.ServiceAccountID,
|
||||
sa1 := &extsvcauth.ExtSvcAccount{
|
||||
ID: client1.ServiceAccountID,
|
||||
Name: client1.Name,
|
||||
Login: client1.Name,
|
||||
OrgId: oauthserver.TmpOrgID,
|
||||
OrgID: oauthserver.TmpOrgID,
|
||||
IsDisabled: false,
|
||||
Created: now,
|
||||
Updated: now,
|
||||
Role: "Viewer",
|
||||
Role: roletype.RoleNone,
|
||||
}
|
||||
|
||||
user56 := &user.User{
|
||||
@@ -735,7 +733,7 @@ func setupHandleTokenRequestEnv(t *testing.T, env *TestEnv, opt func(*oauthserve
|
||||
// To retrieve the Client, its publicKey and its permissions
|
||||
env.OAuthStore.On("GetExternalService", mock.Anything, client1.ClientID).Return(client1, nil)
|
||||
env.OAuthStore.On("GetExternalServicePublicKey", mock.Anything, client1.ClientID).Return(&jose.JSONWebKey{Key: Client1Key.Public(), Algorithm: "RS256"}, nil)
|
||||
env.SAService.On("RetrieveServiceAccount", mock.Anything, oauthserver.TmpOrgID, client1.ServiceAccountID).Return(sa1, nil)
|
||||
env.SAService.On("RetrieveExtSvcAccount", mock.Anything, oauthserver.TmpOrgID, client1.ServiceAccountID).Return(sa1, nil)
|
||||
env.AcStore.On("GetUserPermissions", mock.Anything, mock.Anything).Return(client1.SelfPermissions, nil)
|
||||
// To retrieve the user to impersonate, its permissions and its teams
|
||||
env.AcStore.On("SearchUsersPermissions", mock.Anything, mock.Anything, mock.Anything).Return(map[int64][]ac.Permission{
|
||||
|
||||
Reference in New Issue
Block a user