Auth: add IsServiceAccount to IsRealUser (#58015)

* add: IsServiceAccount to SignedInUser and IsRealUser

* fix: linting error

* refactor: add function IsServiceAccountUser()

By adding the function IsServiceAccountUser() we use it to identify for
ServiceAccounts in the HasUniqueID() since caching is built up on having
a uniqueID, see comment: https://github.com/grafana/grafana/pull/58015#discussion_r1011361880
This commit is contained in:
Eric Leijonmarck
2022-11-04 12:39:54 +00:00
committed by GitHub
parent b7efd46e2d
commit 72d0c6b428
6 changed files with 41 additions and 10 deletions

View File

@@ -312,7 +312,8 @@ func (h *ContextHandler) initContextWithAPIKey(reqContext *models.ReqContext) bo
} }
if apikey.ServiceAccountId == nil || *apikey.ServiceAccountId < 1 { //There is no service account attached to the apikey if apikey.ServiceAccountId == nil || *apikey.ServiceAccountId < 1 { //There is no service account attached to the apikey
//Use the old APIkey method. This provides backwards compatibility. // Use the old APIkey method. This provides backwards compatibility.
// will probably have to be supported for a long time.
reqContext.SignedInUser = &user.SignedInUser{} reqContext.SignedInUser = &user.SignedInUser{}
reqContext.OrgRole = apikey.Role reqContext.OrgRole = apikey.Role
reqContext.ApiKeyID = apikey.Id reqContext.ApiKeyID = apikey.Id

View File

@@ -328,6 +328,7 @@ func (sch *schedule) ruleRoutine(grafanaCtx context.Context, key ngmodels.AlertR
start := sch.clock.Now() start := sch.clock.Now()
schedulerUser := &user.SignedInUser{ schedulerUser := &user.SignedInUser{
// FIXME: add is service account and refactor to a service account instead of a user
UserID: -1, UserID: -1,
Login: "grafana_scheduler", Login: "grafana_scheduler",
OrgID: e.rule.OrgID, OrgID: e.rule.OrgID,

View File

@@ -38,11 +38,12 @@ func createServiceAccountAdminToken(t *testing.T, name string, env *server.TestE
}) })
return keyGen.ClientSecret, &user.SignedInUser{ return keyGen.ClientSecret, &user.SignedInUser{
UserID: account.ID, UserID: account.ID,
Email: account.Email, Email: account.Email,
Name: account.Name, Name: account.Name,
Login: account.Login, Login: account.Login,
OrgID: account.OrgID, OrgID: account.OrgID,
IsServiceAccount: true,
} }
} }

View File

@@ -7,13 +7,29 @@ import (
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
) )
// this should reflect the api /*
ServiceAccountService is the service that manages service accounts.
Service accounts are used to authenticate API requests. They are not users and
do not have a password.
*/
type Service interface { type Service interface {
CreateServiceAccount(ctx context.Context, orgID int64, saForm *CreateServiceAccountForm) (*ServiceAccountDTO, error) CreateServiceAccount(ctx context.Context, orgID int64, saForm *CreateServiceAccountForm) (*ServiceAccountDTO, error)
DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error
RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error) RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error)
} }
/*
Store is the database store for service accounts.
migration from apikeys to service accounts:
HideApiKeyTab is used to hide the api key tab in the UI.
MigrateApiKeysToServiceAccounts migrates all API keys to service accounts.
MigrateApiKey migrates a single API key to a service account.
// only used for interal api calls
RevertApiKey reverts a single service account to an API key.
*/
type Store interface { type Store interface {
CreateServiceAccount(ctx context.Context, orgID int64, saForm *CreateServiceAccountForm) (*ServiceAccountDTO, error) CreateServiceAccount(ctx context.Context, orgID int64, saForm *CreateServiceAccountForm) (*ServiceAccountDTO, error)
SearchOrgServiceAccounts(ctx context.Context, orgID int64, query string, filter ServiceAccountFilter, page int, limit int, SearchOrgServiceAccounts(ctx context.Context, orgID int64, query string, filter ServiceAccountFilter, page int, limit int,

View File

@@ -203,6 +203,7 @@ type SignedInUser struct {
Name string Name string
Email string Email string
ApiKeyID int64 `xorm:"api_key_id"` ApiKeyID int64 `xorm:"api_key_id"`
IsServiceAccount bool `xorm:"is_service_account"`
OrgCount int OrgCount int
IsGrafanaAdmin bool IsGrafanaAdmin bool
IsAnonymous bool IsAnonymous bool
@@ -276,16 +277,26 @@ func (u *SignedInUser) HasRole(role roletype.RoleType) bool {
return u.OrgRole.Includes(role) return u.OrgRole.Includes(role)
} }
// IsRealUser returns true if the user is a real user and not a service account
func (u *SignedInUser) IsRealUser() bool { func (u *SignedInUser) IsRealUser() bool {
return u.UserID > 0 // backwards compatibility
// checking if userId the user is a real user
// previously we used to check if the UserId was 0 or -1
// and not a service account
return u.UserID > 0 && !u.IsServiceAccountUser()
} }
func (u *SignedInUser) IsApiKeyUser() bool { func (u *SignedInUser) IsApiKeyUser() bool {
return u.ApiKeyID > 0 return u.ApiKeyID > 0
} }
// IsServiceAccountUser returns true if the user is a service account
func (u *SignedInUser) IsServiceAccountUser() bool {
return u.IsServiceAccount
}
func (u *SignedInUser) HasUniqueId() bool { func (u *SignedInUser) HasUniqueId() bool {
return u.IsRealUser() || u.IsApiKeyUser() return u.IsRealUser() || u.IsApiKeyUser() || u.IsServiceAccountUser()
} }
func (u *SignedInUser) GetCacheKey() (string, error) { func (u *SignedInUser) GetCacheKey() (string, error) {

View File

@@ -344,7 +344,8 @@ func (ss *sqlStore) GetSignedInUser(ctx context.Context, query *user.GetSignedIn
user_auth.auth_id as external_auth_id, user_auth.auth_id as external_auth_id,
org.name as org_name, org.name as org_name,
org_user.role as org_role, org_user.role as org_role,
org.id as org_id org.id as org_id,
u.is_service_account as is_service_account
FROM ` + ss.dialect.Quote("user") + ` as u FROM ` + ss.dialect.Quote("user") + ` as u
LEFT OUTER JOIN user_auth on user_auth.user_id = u.id LEFT OUTER JOIN user_auth on user_auth.user_id = u.id
LEFT OUTER JOIN org_user on org_user.org_id = ` + orgId + ` and org_user.user_id = u.id LEFT OUTER JOIN org_user on org_user.org_id = ` + orgId + ` and org_user.user_id = u.id