diff --git a/pkg/services/auth/identity/requester.go b/pkg/services/auth/identity/requester.go index a384e8152d6..066d4936a1e 100644 --- a/pkg/services/auth/identity/requester.go +++ b/pkg/services/auth/identity/requester.go @@ -1,6 +1,12 @@ package identity -import "github.com/grafana/grafana/pkg/models/roletype" +import ( + "errors" + "fmt" + "strconv" + + "github.com/grafana/grafana/pkg/models/roletype" +) const ( NamespaceUser = "user" @@ -10,6 +16,9 @@ const ( NamespaceRenderService = "render" ) +var ErrNotIntIdentifier = errors.New("identifier is not an int64") +var ErrIdentifierNotInitialized = errors.New("identifier is not initialized") + type Requester interface { // GetDisplayName returns the display name of the active entity. // The display name is the name if it is set, otherwise the login or email. @@ -50,3 +59,24 @@ type Requester interface { // HasUniqueId returns true if the entity has a unique id HasUniqueId() bool } + +// IntIdentifier converts a string identifier to an int64. +// Applicable for users, service accounts, api keys and renderer service. +// Errors if the identifier is not initialized or if namespace is not recognized. +func IntIdentifier(namespace, identifier string) (int64, error) { + switch namespace { + case NamespaceUser, NamespaceAPIKey, NamespaceServiceAccount, NamespaceRenderService: + id, err := strconv.ParseInt(identifier, 10, 64) + if err != nil { + return 0, fmt.Errorf("unrecognized format for valid namespace %s: %w", namespace, err) + } + + if id < 1 { + return 0, ErrIdentifierNotInitialized + } + + return id, nil + } + + return 0, ErrNotIntIdentifier +} diff --git a/pkg/services/serviceaccounts/api/api.go b/pkg/services/serviceaccounts/api/api.go index fd79210cbe5..4bbd635e88d 100644 --- a/pkg/services/serviceaccounts/api/api.go +++ b/pkg/services/serviceaccounts/api/api.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apikey" + "github.com/grafana/grafana/pkg/services/auth/identity" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/serviceaccounts" @@ -101,17 +102,26 @@ func (api *ServiceAccountsAPI) CreateServiceAccount(c *contextmodel.ReqContext) return response.Error(http.StatusBadRequest, "Bad request data", err) } - if err := api.validateRole(cmd.Role, &c.OrgRole); err != nil { + if err := api.validateRole(cmd.Role, c.SignedInUser.GetOrgRole()); err != nil { return response.ErrOrFallback(http.StatusInternalServerError, "failed to create service account", err) } - serviceAccount, err := api.service.CreateServiceAccount(c.Req.Context(), c.OrgID, &cmd) + serviceAccount, err := api.service.CreateServiceAccount(c.Req.Context(), c.SignedInUser.GetOrgID(), &cmd) if err != nil { return response.ErrOrFallback(http.StatusInternalServerError, "Failed to create service account", err) } - if c.SignedInUser.IsRealUser() { - if _, err := api.permissionService.SetUserPermission(c.Req.Context(), c.OrgID, accesscontrol.User{ID: c.SignedInUser.UserID}, strconv.FormatInt(serviceAccount.Id, 10), "Admin"); err != nil { + namespace, identifier := c.SignedInUser.GetNamespacedID() + + if namespace == identity.NamespaceUser { + userID, err := identity.IntIdentifier(namespace, identifier) + if err != nil { + return response.Error(http.StatusInternalServerError, "Failed to parse user id", err) + } + + if _, err := api.permissionService.SetUserPermission(c.Req.Context(), + c.SignedInUser.GetOrgID(), accesscontrol.User{ID: userID}, + strconv.FormatInt(serviceAccount.Id, 10), "Admin"); err != nil { return response.Error(http.StatusInternalServerError, "Failed to set permissions for service account creator", err) } } @@ -143,7 +153,7 @@ func (api *ServiceAccountsAPI) RetrieveServiceAccount(ctx *contextmodel.ReqConte return response.Error(http.StatusBadRequest, "Service Account ID is invalid", err) } - serviceAccount, err := api.service.RetrieveServiceAccount(ctx.Req.Context(), ctx.OrgID, scopeID) + serviceAccount, err := api.service.RetrieveServiceAccount(ctx.Req.Context(), ctx.SignedInUser.GetOrgID(), scopeID) if err != nil { return response.ErrOrFallback(http.StatusInternalServerError, "Failed to retrieve service account", err) } @@ -190,11 +200,11 @@ func (api *ServiceAccountsAPI) UpdateServiceAccount(c *contextmodel.ReqContext) return response.Error(http.StatusBadRequest, "Bad request data", err) } - if err := api.validateRole(cmd.Role, &c.OrgRole); err != nil { + if err := api.validateRole(cmd.Role, c.SignedInUser.GetOrgRole()); err != nil { return response.ErrOrFallback(http.StatusInternalServerError, "failed to update service account", err) } - resp, err := api.service.UpdateServiceAccount(c.Req.Context(), c.OrgID, scopeID, &cmd) + resp, err := api.service.UpdateServiceAccount(c.Req.Context(), c.SignedInUser.GetOrgID(), scopeID, &cmd) if err != nil { return response.ErrOrFallback(http.StatusInternalServerError, "Failed update service account", err) } @@ -212,7 +222,7 @@ func (api *ServiceAccountsAPI) UpdateServiceAccount(c *contextmodel.ReqContext) }) } -func (api *ServiceAccountsAPI) validateRole(r *org.RoleType, orgRole *org.RoleType) error { +func (api *ServiceAccountsAPI) validateRole(r *org.RoleType, orgRole org.RoleType) error { if r != nil && !r.IsValid() { return serviceaccounts.ErrServiceAccountInvalidRole.Errorf("invalid role specified") } @@ -240,7 +250,7 @@ func (api *ServiceAccountsAPI) DeleteServiceAccount(ctx *contextmodel.ReqContext if err != nil { return response.Error(http.StatusBadRequest, "Service account ID is invalid", err) } - err = api.service.DeleteServiceAccount(ctx.Req.Context(), ctx.OrgID, scopeID) + err = api.service.DeleteServiceAccount(ctx.Req.Context(), ctx.SignedInUser.GetOrgID(), scopeID) if err != nil { return response.Error(http.StatusInternalServerError, "Service account deletion error", err) } @@ -280,7 +290,7 @@ func (api *ServiceAccountsAPI) SearchOrgServiceAccountsWithPaging(c *contextmode filter = serviceaccounts.FilterOnlyDisabled } q := serviceaccounts.SearchOrgServiceAccountsQuery{ - OrgID: c.OrgID, + OrgID: c.SignedInUser.GetOrgID(), Query: c.Query("query"), Page: page, Limit: perPage, @@ -315,7 +325,7 @@ func (api *ServiceAccountsAPI) SearchOrgServiceAccountsWithPaging(c *contextmode // POST /api/serviceaccounts/migrate func (api *ServiceAccountsAPI) MigrateApiKeysToServiceAccounts(ctx *contextmodel.ReqContext) response.Response { - results, err := api.service.MigrateApiKeysToServiceAccounts(ctx.Req.Context(), ctx.OrgID) + results, err := api.service.MigrateApiKeysToServiceAccounts(ctx.Req.Context(), ctx.SignedInUser.GetOrgID()) if err != nil { return response.JSON(http.StatusInternalServerError, results) } @@ -330,7 +340,7 @@ func (api *ServiceAccountsAPI) ConvertToServiceAccount(ctx *contextmodel.ReqCont return response.Error(http.StatusBadRequest, "Key ID is invalid", err) } - if err := api.service.MigrateApiKey(ctx.Req.Context(), ctx.OrgID, keyId); err != nil { + if err := api.service.MigrateApiKey(ctx.Req.Context(), ctx.SignedInUser.GetOrgID(), keyId); err != nil { return response.Error(http.StatusInternalServerError, "Error converting API key", err) } @@ -342,15 +352,11 @@ func (api *ServiceAccountsAPI) getAccessControlMetadata(c *contextmodel.ReqConte return map[string]accesscontrol.Metadata{} } - if c.SignedInUser.Permissions == nil { - return map[string]accesscontrol.Metadata{} - } - - permissions, ok := c.SignedInUser.Permissions[c.OrgID] - if !ok { + if len(c.SignedInUser.GetPermissions()) == 0 { return map[string]accesscontrol.Metadata{} } + permissions := c.SignedInUser.GetPermissions() return accesscontrol.GetResourcesMetadata(c.Req.Context(), permissions, "serviceaccounts:id:", saIDs) } diff --git a/pkg/services/serviceaccounts/api/api_test.go b/pkg/services/serviceaccounts/api/api_test.go index 8903d013a56..636acfc0ba6 100644 --- a/pkg/services/serviceaccounts/api/api_test.go +++ b/pkg/services/serviceaccounts/api/api_test.go @@ -85,7 +85,9 @@ func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) { a.service = &fakeServiceAccountService{ExpectedServiceAccount: tt.expectedSA, ExpectedErr: tt.expectedErr} }) req := server.NewRequest(http.MethodPost, "/api/serviceaccounts/", strings.NewReader(tt.body)) - webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgRole: tt.basicRole, OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}) + webtest.RequestWithSignedInUser(req, &user.SignedInUser{ + OrgRole: tt.basicRole, OrgID: 1, IsAnonymous: true, + Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}) res, err := server.SendJSON(req) require.NoError(t, err) diff --git a/pkg/services/serviceaccounts/api/token.go b/pkg/services/serviceaccounts/api/token.go index 58900bc9646..d486882acd2 100644 --- a/pkg/services/serviceaccounts/api/token.go +++ b/pkg/services/serviceaccounts/api/token.go @@ -69,8 +69,9 @@ func (api *ServiceAccountsAPI) ListTokens(ctx *contextmodel.ReqContext) response return response.Error(http.StatusBadRequest, "Service Account ID is invalid", err) } + orgID := ctx.SignedInUser.GetOrgID() saTokens, err := api.service.ListTokens(ctx.Req.Context(), &serviceaccounts.GetSATokensQuery{ - OrgID: &ctx.OrgID, + OrgID: &orgID, ServiceAccountID: &saID, }) if err != nil { @@ -131,7 +132,7 @@ func (api *ServiceAccountsAPI) CreateToken(c *contextmodel.ReqContext) response. } // confirm service account exists - if _, err = api.service.RetrieveServiceAccount(c.Req.Context(), c.OrgID, saID); err != nil { + if _, err = api.service.RetrieveServiceAccount(c.Req.Context(), c.SignedInUser.GetOrgID(), saID); err != nil { return response.ErrOrFallback(http.StatusInternalServerError, "Failed to retrieve service account", err) } @@ -141,7 +142,7 @@ func (api *ServiceAccountsAPI) CreateToken(c *contextmodel.ReqContext) response. } // Force affected service account to be the one referenced in the URL - cmd.OrgId = c.OrgID + cmd.OrgId = c.SignedInUser.GetOrgID() if api.cfg.ApiKeyMaxSecondsToLive != -1 { if cmd.SecondsToLive == 0 { @@ -204,7 +205,7 @@ func (api *ServiceAccountsAPI) DeleteToken(c *contextmodel.ReqContext) response. } // confirm service account exists - if _, err := api.service.RetrieveServiceAccount(c.Req.Context(), c.OrgID, saID); err != nil { + if _, err := api.service.RetrieveServiceAccount(c.Req.Context(), c.SignedInUser.GetOrgID(), saID); err != nil { return response.ErrOrFallback(http.StatusInternalServerError, "Failed to retrieve service account", err) } @@ -213,7 +214,7 @@ func (api *ServiceAccountsAPI) DeleteToken(c *contextmodel.ReqContext) response. return response.Error(http.StatusBadRequest, "Token ID is invalid", err) } - if err = api.service.DeleteServiceAccountToken(c.Req.Context(), c.OrgID, saID, tokenID); err != nil { + if err = api.service.DeleteServiceAccountToken(c.Req.Context(), c.SignedInUser.GetOrgID(), saID, tokenID); err != nil { return response.ErrOrFallback(http.StatusInternalServerError, failedToDeleteMsg, err) } diff --git a/pkg/services/serviceaccounts/models.go b/pkg/services/serviceaccounts/models.go index 876817b23a8..3418d8e41fd 100644 --- a/pkg/services/serviceaccounts/models.go +++ b/pkg/services/serviceaccounts/models.go @@ -4,8 +4,8 @@ import ( "time" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/org" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util/errutil" ) @@ -106,7 +106,7 @@ type SearchOrgServiceAccountsQuery struct { Filter ServiceAccountFilter Page int Limit int - SignedInUser *user.SignedInUser + SignedInUser identity.Requester } func (q *SearchOrgServiceAccountsQuery) SetDefaults() {