mirror of
https://github.com/grafana/grafana.git
synced 2026-08-19 01:34:54 -05:00
Service Accounts: Managed permissions for service accounts (#51818)
* backend changes * frontend changes * linting * nit * import order * allow SA creator to access the SA page * fix merge * tests * fix frontend tests Co-authored-by: alexanderzobnin alexanderzobnin@gmail.com
This commit is contained in:
co-authored by
alexanderzobnin alexanderzobnin@gmail.com
parent
2af5feb147
commit
d85df0a560
@@ -20,12 +20,13 @@ import (
|
||||
)
|
||||
|
||||
type ServiceAccountsAPI struct {
|
||||
cfg *setting.Cfg
|
||||
service serviceaccounts.Service
|
||||
accesscontrol accesscontrol.AccessControl
|
||||
RouterRegister routing.RouteRegister
|
||||
store serviceaccounts.Store
|
||||
log log.Logger
|
||||
cfg *setting.Cfg
|
||||
service serviceaccounts.Service
|
||||
accesscontrol accesscontrol.AccessControl
|
||||
RouterRegister routing.RouteRegister
|
||||
store serviceaccounts.Store
|
||||
log log.Logger
|
||||
permissionService accesscontrol.ServiceAccountPermissionsService
|
||||
}
|
||||
|
||||
func NewServiceAccountsAPI(
|
||||
@@ -34,14 +35,16 @@ func NewServiceAccountsAPI(
|
||||
accesscontrol accesscontrol.AccessControl,
|
||||
routerRegister routing.RouteRegister,
|
||||
store serviceaccounts.Store,
|
||||
permissionService accesscontrol.ServiceAccountPermissionsService,
|
||||
) *ServiceAccountsAPI {
|
||||
return &ServiceAccountsAPI{
|
||||
cfg: cfg,
|
||||
service: service,
|
||||
accesscontrol: accesscontrol,
|
||||
RouterRegister: routerRegister,
|
||||
store: store,
|
||||
log: log.New("serviceaccounts.api"),
|
||||
cfg: cfg,
|
||||
service: service,
|
||||
accesscontrol: accesscontrol,
|
||||
RouterRegister: routerRegister,
|
||||
store: store,
|
||||
log: log.New("serviceaccounts.api"),
|
||||
permissionService: permissionService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +106,14 @@ func (api *ServiceAccountsAPI) CreateServiceAccount(c *models.ReqContext) respon
|
||||
return response.Error(http.StatusInternalServerError, "Failed to create service account", err)
|
||||
}
|
||||
|
||||
if !api.accesscontrol.IsDisabled() {
|
||||
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 {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to set permissions for service account creator", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusCreated, serviceAccount)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
@@ -15,8 +16,11 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
acDatabase "github.com/grafana/grafana/pkg/services/accesscontrol/database"
|
||||
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/database"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/tests"
|
||||
@@ -35,7 +39,7 @@ var (
|
||||
func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) {
|
||||
store := sqlstore.InitTestDB(t)
|
||||
kvStore := kvstore.ProvideService(store)
|
||||
saStore := database.NewServiceAccountsStore(store, kvStore)
|
||||
saStore := database.ProvideServiceAccountsStore(store, kvStore)
|
||||
svcmock := tests.ServiceAccountMock{}
|
||||
|
||||
autoAssignOrg := store.Cfg.AutoAssignOrg
|
||||
@@ -150,7 +154,7 @@ func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
serviceAccountRequestScenario(t, http.MethodPost, serviceAccountPath, testUser, func(httpmethod string, endpoint string, user *tests.TestUser) {
|
||||
server, _ := setupTestServer(t, &svcmock, routing.NewRouteRegister(), tc.acmock, store, saStore)
|
||||
server, api := setupTestServer(t, &svcmock, routing.NewRouteRegister(), tc.acmock, store, saStore)
|
||||
marshalled, err := json.Marshal(tc.body)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -166,9 +170,25 @@ func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) {
|
||||
require.Equal(t, tc.expectedCode, actualCode, actualBody)
|
||||
|
||||
if actualCode == http.StatusCreated {
|
||||
assert.NotEmpty(t, actualBody["id"])
|
||||
assert.Equal(t, tc.body["name"], actualBody["name"].(string))
|
||||
assert.Equal(t, tc.wantID, actualBody["login"].(string))
|
||||
sa := serviceaccounts.ServiceAccountDTO{}
|
||||
err = json.Unmarshal(actual.Body.Bytes(), &sa)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, sa.Id)
|
||||
assert.Equal(t, tc.body["name"], sa.Name)
|
||||
assert.Equal(t, tc.wantID, sa.Login)
|
||||
tempUser := &models.SignedInUser{
|
||||
OrgId: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: {
|
||||
serviceaccounts.ActionRead: []string{serviceaccounts.ScopeAll},
|
||||
},
|
||||
},
|
||||
}
|
||||
perms, err := api.permissionService.GetPermissions(context.Background(), tempUser, strconv.FormatInt(sa.Id, 10))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(perms), "should have added managed permissions for SA creator")
|
||||
assert.Equal(t, int64(1), perms[0].ID)
|
||||
assert.Equal(t, int64(1), perms[0].UserId)
|
||||
} else if actualCode == http.StatusBadRequest {
|
||||
assert.Contains(t, tc.wantError, actualBody["error"].(string))
|
||||
}
|
||||
@@ -182,7 +202,7 @@ func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) {
|
||||
func TestServiceAccountsAPI_DeleteServiceAccount(t *testing.T) {
|
||||
store := sqlstore.InitTestDB(t)
|
||||
kvStore := kvstore.ProvideService(store)
|
||||
saStore := database.NewServiceAccountsStore(store, kvStore)
|
||||
saStore := database.ProvideServiceAccountsStore(store, kvStore)
|
||||
svcmock := tests.ServiceAccountMock{}
|
||||
|
||||
var requestResponse = func(server *web.Mux, httpMethod, requestpath string) *httptest.ResponseRecorder {
|
||||
@@ -251,7 +271,11 @@ func setupTestServer(t *testing.T, svc *tests.ServiceAccountMock,
|
||||
routerRegister routing.RouteRegister,
|
||||
acmock *accesscontrolmock.Mock,
|
||||
sqlStore *sqlstore.SQLStore, saStore serviceaccounts.Store) (*web.Mux, *ServiceAccountsAPI) {
|
||||
a := NewServiceAccountsAPI(setting.NewCfg(), svc, acmock, routerRegister, saStore)
|
||||
cfg := setting.NewCfg()
|
||||
saPermissionService, err := ossaccesscontrol.ProvideServiceAccountPermissions(cfg, routing.NewRouteRegister(), sqlStore, acmock, acDatabase.ProvideService(sqlStore), &licensing.OSSLicensingService{}, saStore)
|
||||
require.NoError(t, err)
|
||||
|
||||
a := NewServiceAccountsAPI(cfg, svc, acmock, routerRegister, saStore, saPermissionService)
|
||||
a.RegisterAPIEndpoints()
|
||||
|
||||
a.cfg.ApiKeyMaxSecondsToLive = -1 // disable api key expiration
|
||||
@@ -259,6 +283,7 @@ func setupTestServer(t *testing.T, svc *tests.ServiceAccountMock,
|
||||
m := web.New()
|
||||
signedUser := &models.SignedInUser{
|
||||
OrgId: 1,
|
||||
UserId: 1,
|
||||
OrgRole: models.ROLE_VIEWER,
|
||||
}
|
||||
|
||||
@@ -278,7 +303,7 @@ func setupTestServer(t *testing.T, svc *tests.ServiceAccountMock,
|
||||
func TestServiceAccountsAPI_RetrieveServiceAccount(t *testing.T) {
|
||||
store := sqlstore.InitTestDB(t)
|
||||
kvStore := kvstore.ProvideService(store)
|
||||
saStore := database.NewServiceAccountsStore(store, kvStore)
|
||||
saStore := database.ProvideServiceAccountsStore(store, kvStore)
|
||||
svcmock := tests.ServiceAccountMock{}
|
||||
type testRetrieveSATestCase struct {
|
||||
desc string
|
||||
@@ -369,7 +394,7 @@ func newString(s string) *string {
|
||||
func TestServiceAccountsAPI_UpdateServiceAccount(t *testing.T) {
|
||||
store := sqlstore.InitTestDB(t)
|
||||
kvStore := kvstore.ProvideService(store)
|
||||
saStore := database.NewServiceAccountsStore(store, kvStore)
|
||||
saStore := database.ProvideServiceAccountsStore(store, kvStore)
|
||||
svcmock := tests.ServiceAccountMock{}
|
||||
type testUpdateSATestCase struct {
|
||||
desc string
|
||||
|
||||
@@ -52,7 +52,7 @@ func createTokenforSA(t *testing.T, store serviceaccounts.Store, keyName string,
|
||||
func TestServiceAccountsAPI_CreateToken(t *testing.T) {
|
||||
store := sqlstore.InitTestDB(t)
|
||||
kvStore := kvstore.ProvideService(store)
|
||||
saStore := database.NewServiceAccountsStore(store, kvStore)
|
||||
saStore := database.ProvideServiceAccountsStore(store, kvStore)
|
||||
svcmock := tests.ServiceAccountMock{}
|
||||
sa := tests.SetupUserServiceAccount(t, store, tests.TestUser{Login: "sa", IsServiceAccount: true})
|
||||
|
||||
@@ -169,7 +169,7 @@ func TestServiceAccountsAPI_DeleteToken(t *testing.T) {
|
||||
store := sqlstore.InitTestDB(t)
|
||||
kvStore := kvstore.ProvideService(store)
|
||||
svcMock := &tests.ServiceAccountMock{}
|
||||
saStore := database.NewServiceAccountsStore(store, kvStore)
|
||||
saStore := database.ProvideServiceAccountsStore(store, kvStore)
|
||||
sa := tests.SetupUserServiceAccount(t, store, tests.TestUser{Login: "sa", IsServiceAccount: true})
|
||||
|
||||
type testCreateSAToken struct {
|
||||
|
||||
Reference in New Issue
Block a user