From 72d0c6b428846fe7b86a2c529fb37c45bc3b8e83 Mon Sep 17 00:00:00 2001 From: Eric Leijonmarck Date: Fri, 4 Nov 2022 12:39:54 +0000 Subject: [PATCH] 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 --- pkg/services/contexthandler/contexthandler.go | 3 ++- pkg/services/ngalert/schedule/schedule.go | 1 + pkg/services/querylibrary/tests/common.go | 11 ++++++----- .../serviceaccounts/serviceaccounts.go | 18 +++++++++++++++++- pkg/services/user/model.go | 15 +++++++++++++-- pkg/services/user/userimpl/store.go | 3 ++- 6 files changed, 41 insertions(+), 10 deletions(-) diff --git a/pkg/services/contexthandler/contexthandler.go b/pkg/services/contexthandler/contexthandler.go index bc8836bad31..a19c8c2d7fb 100644 --- a/pkg/services/contexthandler/contexthandler.go +++ b/pkg/services/contexthandler/contexthandler.go @@ -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 - //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.OrgRole = apikey.Role reqContext.ApiKeyID = apikey.Id diff --git a/pkg/services/ngalert/schedule/schedule.go b/pkg/services/ngalert/schedule/schedule.go index 23b01d13b6c..a072b2dae2d 100644 --- a/pkg/services/ngalert/schedule/schedule.go +++ b/pkg/services/ngalert/schedule/schedule.go @@ -328,6 +328,7 @@ func (sch *schedule) ruleRoutine(grafanaCtx context.Context, key ngmodels.AlertR start := sch.clock.Now() schedulerUser := &user.SignedInUser{ + // FIXME: add is service account and refactor to a service account instead of a user UserID: -1, Login: "grafana_scheduler", OrgID: e.rule.OrgID, diff --git a/pkg/services/querylibrary/tests/common.go b/pkg/services/querylibrary/tests/common.go index c3aaf3734d9..f8cfe67ccfa 100644 --- a/pkg/services/querylibrary/tests/common.go +++ b/pkg/services/querylibrary/tests/common.go @@ -38,11 +38,12 @@ func createServiceAccountAdminToken(t *testing.T, name string, env *server.TestE }) return keyGen.ClientSecret, &user.SignedInUser{ - UserID: account.ID, - Email: account.Email, - Name: account.Name, - Login: account.Login, - OrgID: account.OrgID, + UserID: account.ID, + Email: account.Email, + Name: account.Name, + Login: account.Login, + OrgID: account.OrgID, + IsServiceAccount: true, } } diff --git a/pkg/services/serviceaccounts/serviceaccounts.go b/pkg/services/serviceaccounts/serviceaccounts.go index 63e3cd336a0..940a9cb3ce7 100644 --- a/pkg/services/serviceaccounts/serviceaccounts.go +++ b/pkg/services/serviceaccounts/serviceaccounts.go @@ -7,13 +7,29 @@ import ( "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 { CreateServiceAccount(ctx context.Context, orgID int64, saForm *CreateServiceAccountForm) (*ServiceAccountDTO, error) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID 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 { CreateServiceAccount(ctx context.Context, orgID int64, saForm *CreateServiceAccountForm) (*ServiceAccountDTO, error) SearchOrgServiceAccounts(ctx context.Context, orgID int64, query string, filter ServiceAccountFilter, page int, limit int, diff --git a/pkg/services/user/model.go b/pkg/services/user/model.go index 4172ae78918..4dd8f9dc8a3 100644 --- a/pkg/services/user/model.go +++ b/pkg/services/user/model.go @@ -203,6 +203,7 @@ type SignedInUser struct { Name string Email string ApiKeyID int64 `xorm:"api_key_id"` + IsServiceAccount bool `xorm:"is_service_account"` OrgCount int IsGrafanaAdmin bool IsAnonymous bool @@ -276,16 +277,26 @@ func (u *SignedInUser) HasRole(role roletype.RoleType) bool { 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 { - 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 { 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 { - return u.IsRealUser() || u.IsApiKeyUser() + return u.IsRealUser() || u.IsApiKeyUser() || u.IsServiceAccountUser() } func (u *SignedInUser) GetCacheKey() (string, error) { diff --git a/pkg/services/user/userimpl/store.go b/pkg/services/user/userimpl/store.go index a27850bfb4d..c368be1389c 100644 --- a/pkg/services/user/userimpl/store.go +++ b/pkg/services/user/userimpl/store.go @@ -344,7 +344,8 @@ func (ss *sqlStore) GetSignedInUser(ctx context.Context, query *user.GetSignedIn user_auth.auth_id as external_auth_id, org.name as org_name, 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 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