Serviceaccounts: create serviceaccount api (#42150)

* WIP

* wip

* wip

* wip

* refactor: new return of the create service accoutn

* refactor: change to have correct role

* refactor: ability to create service accounts

* make public

* refactor: make ints instead

* refactor: remove location sprintf

* refactor: added back named constants
This commit is contained in:
Eric Leijonmarck 2021-12-14 14:39:25 +01:00 committed by GitHub
parent 19374fce39
commit 4a3961400a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 75 additions and 1 deletions

View File

@ -1,6 +1,7 @@
package api
import (
"errors"
"net/http"
"github.com/grafana/grafana/pkg/api/response"
@ -11,6 +12,7 @@ import (
acmiddleware "github.com/grafana/grafana/pkg/services/accesscontrol/middleware"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
)
type ServiceAccountsAPI struct {
@ -40,9 +42,27 @@ func (api *ServiceAccountsAPI) RegisterAPIEndpoints(
auth := acmiddleware.Middleware(api.accesscontrol)
api.RouterRegister.Group("/api/serviceaccounts", func(serviceAccountsRoute routing.RouteRegister) {
serviceAccountsRoute.Delete("/:serviceAccountId", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionDelete, serviceaccounts.ScopeID)), routing.Wrap(api.DeleteServiceAccount))
serviceAccountsRoute.Post("/", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate, serviceaccounts.ScopeID)), routing.Wrap(api.CreateServiceAccount))
})
}
// POST /api/serviceaccounts
func (api *ServiceAccountsAPI) CreateServiceAccount(c *models.ReqContext) response.Response {
cmd := serviceaccounts.CreateServiceaccountForm{}
if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "Bad request data", err)
}
user, err := api.service.CreateServiceAccount(c.Req.Context(), &cmd)
switch {
case errors.Is(err, serviceaccounts.ErrServiceAccountNotFound):
return response.Error(http.StatusBadRequest, "Failed to create role with the provided name", err)
case err != nil:
return response.Error(http.StatusInternalServerError, "Failed to create service account", err)
}
return response.JSON(http.StatusCreated, user)
}
func (api *ServiceAccountsAPI) DeleteServiceAccount(ctx *models.ReqContext) response.Response {
scopeID := ctx.ParamsInt64(":serviceAccountId")
err := api.service.DeleteServiceAccount(ctx.Req.Context(), ctx.OrgId, scopeID)

View File

@ -3,9 +3,11 @@ package database
import (
"context"
"github.com/google/uuid"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/pkg/errors"
)
type ServiceAccountsStoreImpl struct {
@ -18,6 +20,21 @@ func NewServiceAccountsStore(store *sqlstore.SQLStore) *ServiceAccountsStoreImpl
}
}
func (s *ServiceAccountsStoreImpl) CreateServiceAccount(ctx context.Context, sa *serviceaccounts.CreateServiceaccountForm) (user *models.User, err error) {
// create a new service account - "user" with empty permissions
cmd := models.CreateUserCommand{
Login: "Service-Account-" + uuid.New().String(),
Name: sa.Name + "-Service-Account-" + uuid.New().String(),
OrgId: sa.OrgID,
IsServiceAccount: true,
}
newuser, err := s.sqlStore.CreateUser(ctx, cmd)
if err != nil {
return nil, errors.Errorf("Failed to create user: %v", err)
}
return newuser, nil
}
func (s *ServiceAccountsStoreImpl) DeleteServiceAccount(ctx context.Context, orgID, serviceaccountID int64) error {
return s.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
return deleteServiceAccountInTransaction(sess, orgID, serviceaccountID)

View File

@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/serviceaccounts/api"
@ -42,6 +43,14 @@ func ProvideServiceAccountsService(
return s, nil
}
func (sa *ServiceAccountsService) CreateServiceAccount(ctx context.Context, saForm *serviceaccounts.CreateServiceaccountForm) (*models.User, error) {
if !sa.cfg.FeatureToggles["service-accounts"] {
sa.log.Debug(ServiceAccountFeatureToggleNotFound)
return nil, nil
}
return sa.store.CreateServiceAccount(ctx, saForm)
}
func (sa *ServiceAccountsService) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error {
if !sa.cfg.FeatureToggles["service-accounts"] {
sa.log.Debug(ServiceAccountFeatureToggleNotFound)

View File

@ -13,3 +13,14 @@ const (
ActionCreate = "serviceaccounts:create"
ActionDelete = "serviceaccounts:delete"
)
type ServiceAccount struct {
Id int64
}
type CreateServiceaccountForm struct {
OrgID int64 `json:"-"`
Name string `json:"name" binding:"Required"`
DisplayName string `json:"displayName"`
Description string `json:"description"`
}

View File

@ -1,10 +1,16 @@
package serviceaccounts
import "context"
import (
"context"
"github.com/grafana/grafana/pkg/models"
)
type Service interface {
CreateServiceAccount(ctx context.Context, saForm *CreateServiceaccountForm) (*models.User, error)
DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error
}
type Store interface {
CreateServiceAccount(ctx context.Context, saForm *CreateServiceaccountForm) (*models.User, error)
DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error
}

View File

@ -29,6 +29,10 @@ func SetupUserServiceAccount(t *testing.T, sqlStore *sqlstore.SQLStore, testUser
// create mock for serviceaccountservice
type ServiceAccountMock struct{}
func (s *ServiceAccountMock) CreateServiceAccount(ctx context.Context, saForm *serviceaccounts.CreateServiceaccountForm) (*models.User, error) {
return nil, nil
}
func (s *ServiceAccountMock) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error {
return nil
}
@ -48,6 +52,7 @@ func SetupMockAccesscontrol(t *testing.T, userpermissionsfunc func(c context.Con
var _ serviceaccounts.Store = new(ServiceAccountsStoreMock)
type Calls struct {
CreateServiceAccount []interface{}
DeleteServiceAccount []interface{}
}
@ -55,6 +60,12 @@ type ServiceAccountsStoreMock struct {
Calls Calls
}
func (s *ServiceAccountsStoreMock) CreateServiceAccount(ctx context.Context, cmd *serviceaccounts.CreateServiceaccountForm) (*models.User, error) {
// now we can test that the mock has these calls when we call the function
s.Calls.CreateServiceAccount = append(s.Calls.CreateServiceAccount, []interface{}{ctx, cmd})
return nil, nil
}
func (s *ServiceAccountsStoreMock) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error {
// now we can test that the mock has these calls when we call the function
s.Calls.DeleteServiceAccount = append(s.Calls.DeleteServiceAccount, []interface{}{ctx, orgID, serviceAccountID})