Auth: Move Service Account service to SignedInUser Interface (#73142)

* move service account service to identity interface

* Update pkg/services/auth/identity/requester.go
This commit is contained in:
Jo 2023-08-10 14:20:58 +02:00 committed by GitHub
parent 8c2f439cd7
commit 67de18ff06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 66 additions and 27 deletions

View File

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

View File

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

View File

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

View File

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

View File

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