2021-11-11 09:10:24 -06:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
2021-12-14 07:39:25 -06:00
|
|
|
"errors"
|
2021-11-11 09:10:24 -06:00
|
|
|
"net/http"
|
2022-01-14 10:55:57 -06:00
|
|
|
"strconv"
|
2021-11-11 09:10:24 -06:00
|
|
|
|
2022-02-08 13:19:22 -06:00
|
|
|
"github.com/grafana/grafana/pkg/api/dtos"
|
2021-11-11 09:10:24 -06:00
|
|
|
"github.com/grafana/grafana/pkg/api/response"
|
|
|
|
"github.com/grafana/grafana/pkg/api/routing"
|
2022-02-08 13:19:22 -06:00
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
2021-11-11 09:10:24 -06:00
|
|
|
"github.com/grafana/grafana/pkg/middleware"
|
|
|
|
"github.com/grafana/grafana/pkg/models"
|
|
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
|
|
acmiddleware "github.com/grafana/grafana/pkg/services/accesscontrol/middleware"
|
2022-01-26 11:44:20 -06:00
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
2021-11-11 09:10:24 -06:00
|
|
|
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
2022-02-07 07:51:54 -06:00
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
2021-12-14 07:39:25 -06:00
|
|
|
"github.com/grafana/grafana/pkg/web"
|
2021-11-11 09:10:24 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
type ServiceAccountsAPI struct {
|
2022-02-07 07:51:54 -06:00
|
|
|
cfg *setting.Cfg
|
2021-11-11 09:10:24 -06:00
|
|
|
service serviceaccounts.Service
|
|
|
|
accesscontrol accesscontrol.AccessControl
|
|
|
|
RouterRegister routing.RouteRegister
|
2021-12-16 07:28:16 -06:00
|
|
|
store serviceaccounts.Store
|
2022-02-08 13:19:22 -06:00
|
|
|
log log.Logger
|
2021-11-11 09:10:24 -06:00
|
|
|
}
|
|
|
|
|
2022-02-08 07:31:34 -06:00
|
|
|
type serviceAccountIdDTO struct {
|
|
|
|
Id int64 `json:"id"`
|
|
|
|
Message string `json:"message"`
|
|
|
|
}
|
|
|
|
|
2021-11-11 09:10:24 -06:00
|
|
|
func NewServiceAccountsAPI(
|
2022-02-07 07:51:54 -06:00
|
|
|
cfg *setting.Cfg,
|
2021-11-11 09:10:24 -06:00
|
|
|
service serviceaccounts.Service,
|
|
|
|
accesscontrol accesscontrol.AccessControl,
|
|
|
|
routerRegister routing.RouteRegister,
|
2021-12-16 07:28:16 -06:00
|
|
|
store serviceaccounts.Store,
|
2021-11-11 09:10:24 -06:00
|
|
|
) *ServiceAccountsAPI {
|
|
|
|
return &ServiceAccountsAPI{
|
2022-02-07 07:51:54 -06:00
|
|
|
cfg: cfg,
|
2021-11-11 09:10:24 -06:00
|
|
|
service: service,
|
|
|
|
accesscontrol: accesscontrol,
|
|
|
|
RouterRegister: routerRegister,
|
2021-12-16 07:28:16 -06:00
|
|
|
store: store,
|
2022-02-08 13:19:22 -06:00
|
|
|
log: log.New("serviceaccounts.api"),
|
2021-11-11 09:10:24 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (api *ServiceAccountsAPI) RegisterAPIEndpoints(
|
2022-01-26 11:44:20 -06:00
|
|
|
features featuremgmt.FeatureToggles,
|
2021-11-11 09:10:24 -06:00
|
|
|
) {
|
2022-01-26 11:44:20 -06:00
|
|
|
if !features.IsEnabled(featuremgmt.FlagServiceAccounts) {
|
2021-11-11 09:10:24 -06:00
|
|
|
return
|
|
|
|
}
|
2022-01-19 10:03:45 -06:00
|
|
|
|
2021-11-11 09:10:24 -06:00
|
|
|
auth := acmiddleware.Middleware(api.accesscontrol)
|
2022-02-02 09:32:37 -06:00
|
|
|
api.RouterRegister.Group("/api/serviceaccounts", func(serviceAccountsRoute routing.RouteRegister) {
|
2022-01-12 06:23:00 -06:00
|
|
|
serviceAccountsRoute.Get("/", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeAll)), routing.Wrap(api.ListServiceAccounts))
|
2022-02-22 07:58:42 -06:00
|
|
|
serviceAccountsRoute.Post("/", auth(middleware.ReqOrgAdmin,
|
|
|
|
accesscontrol.EvalPermission(serviceaccounts.ActionCreate)), routing.Wrap(api.CreateServiceAccount))
|
|
|
|
serviceAccountsRoute.Get("/:serviceAccountId", auth(middleware.ReqOrgAdmin,
|
|
|
|
accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeID)), routing.Wrap(api.RetrieveServiceAccount))
|
2022-02-17 06:19:58 -06:00
|
|
|
serviceAccountsRoute.Patch("/:serviceAccountId", auth(middleware.ReqOrgAdmin,
|
|
|
|
accesscontrol.EvalPermission(serviceaccounts.ActionWrite, serviceaccounts.ScopeID)), routing.Wrap(api.updateServiceAccount))
|
2021-11-11 09:10:24 -06:00
|
|
|
serviceAccountsRoute.Delete("/:serviceAccountId", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionDelete, serviceaccounts.ScopeID)), routing.Wrap(api.DeleteServiceAccount))
|
2022-02-08 06:44:27 -06:00
|
|
|
serviceAccountsRoute.Post("/upgradeall", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate)), routing.Wrap(api.UpgradeServiceAccounts))
|
2022-01-20 09:51:18 -06:00
|
|
|
serviceAccountsRoute.Post("/convert/:keyId", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate, serviceaccounts.ScopeID)), routing.Wrap(api.ConvertToServiceAccount))
|
2022-02-07 07:51:54 -06:00
|
|
|
serviceAccountsRoute.Get("/:serviceAccountId/tokens", auth(middleware.ReqOrgAdmin,
|
|
|
|
accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeID)), routing.Wrap(api.ListTokens))
|
|
|
|
serviceAccountsRoute.Post("/:serviceAccountId/tokens", auth(middleware.ReqOrgAdmin,
|
|
|
|
accesscontrol.EvalPermission(serviceaccounts.ActionWrite, serviceaccounts.ScopeID)), routing.Wrap(api.CreateToken))
|
|
|
|
serviceAccountsRoute.Delete("/:serviceAccountId/tokens/:tokenId", auth(middleware.ReqOrgAdmin,
|
|
|
|
accesscontrol.EvalPermission(serviceaccounts.ActionWrite, serviceaccounts.ScopeID)), routing.Wrap(api.DeleteToken))
|
2021-11-11 09:10:24 -06:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-12-14 07:39:25 -06:00
|
|
|
// POST /api/serviceaccounts
|
|
|
|
func (api *ServiceAccountsAPI) CreateServiceAccount(c *models.ReqContext) response.Response {
|
2022-02-17 06:19:58 -06:00
|
|
|
cmd := serviceaccounts.CreateServiceAccountForm{}
|
2021-12-14 07:39:25 -06:00
|
|
|
if err := web.Bind(c.Req, &cmd); err != nil {
|
|
|
|
return response.Error(http.StatusBadRequest, "Bad request data", err)
|
|
|
|
}
|
2022-02-22 07:58:42 -06:00
|
|
|
cmd.OrgID = c.OrgId
|
|
|
|
|
2021-12-14 07:39:25 -06:00
|
|
|
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)
|
|
|
|
}
|
2022-02-08 07:31:34 -06:00
|
|
|
sa := &serviceAccountIdDTO{
|
2022-02-07 07:12:39 -06:00
|
|
|
Id: user.Id,
|
2022-02-08 07:31:34 -06:00
|
|
|
Message: "Service account created",
|
2022-02-07 07:12:39 -06:00
|
|
|
}
|
2022-02-08 07:31:34 -06:00
|
|
|
return response.JSON(http.StatusCreated, sa)
|
2021-12-14 07:39:25 -06:00
|
|
|
}
|
|
|
|
|
2021-11-11 09:10:24 -06:00
|
|
|
func (api *ServiceAccountsAPI) DeleteServiceAccount(ctx *models.ReqContext) response.Response {
|
2022-01-14 10:55:57 -06:00
|
|
|
scopeID, err := strconv.ParseInt(web.Params(ctx.Req)[":serviceAccountId"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return response.Error(http.StatusBadRequest, "serviceAccountId is invalid", err)
|
|
|
|
}
|
|
|
|
err = api.service.DeleteServiceAccount(ctx.Req.Context(), ctx.OrgId, scopeID)
|
2021-11-11 09:10:24 -06:00
|
|
|
if err != nil {
|
|
|
|
return response.Error(http.StatusInternalServerError, "Service account deletion error", err)
|
|
|
|
}
|
|
|
|
return response.Success("service account deleted")
|
|
|
|
}
|
2021-12-16 07:28:16 -06:00
|
|
|
|
|
|
|
func (api *ServiceAccountsAPI) UpgradeServiceAccounts(ctx *models.ReqContext) response.Response {
|
|
|
|
if err := api.store.UpgradeServiceAccounts(ctx.Req.Context()); err == nil {
|
|
|
|
return response.Success("service accounts upgraded")
|
|
|
|
} else {
|
2022-02-17 08:00:56 -06:00
|
|
|
return response.Error(http.StatusInternalServerError, "Internal server error", err)
|
2021-12-16 07:28:16 -06:00
|
|
|
}
|
|
|
|
}
|
2022-01-12 06:23:00 -06:00
|
|
|
|
2022-01-20 09:51:18 -06:00
|
|
|
func (api *ServiceAccountsAPI) ConvertToServiceAccount(ctx *models.ReqContext) response.Response {
|
|
|
|
keyId, err := strconv.ParseInt(web.Params(ctx.Req)[":keyId"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return response.Error(http.StatusBadRequest, "keyId is invalid", err)
|
|
|
|
}
|
|
|
|
if err := api.store.ConvertToServiceAccounts(ctx.Req.Context(), []int64{keyId}); err == nil {
|
|
|
|
return response.Success("service accounts converted")
|
|
|
|
} else {
|
|
|
|
return response.Error(500, "Internal server error", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-08 13:19:22 -06:00
|
|
|
func (api *ServiceAccountsAPI) ListServiceAccounts(c *models.ReqContext) response.Response {
|
|
|
|
serviceAccounts, err := api.store.ListServiceAccounts(c.Req.Context(), c.OrgId, -1)
|
2022-01-12 06:23:00 -06:00
|
|
|
if err != nil {
|
2022-01-19 03:23:46 -06:00
|
|
|
return response.Error(http.StatusInternalServerError, "Failed to list service accounts", err)
|
2022-01-12 06:23:00 -06:00
|
|
|
}
|
2022-02-08 13:19:22 -06:00
|
|
|
|
|
|
|
saIDs := map[string]bool{}
|
|
|
|
for i := range serviceAccounts {
|
|
|
|
serviceAccounts[i].AvatarUrl = dtos.GetGravatarUrlWithDefault("", serviceAccounts[i].Name)
|
|
|
|
saIDs[strconv.FormatInt(serviceAccounts[i].Id, 10)] = true
|
|
|
|
}
|
|
|
|
|
2022-02-18 04:27:00 -06:00
|
|
|
metadata := api.getAccessControlMetadata(c, saIDs)
|
|
|
|
if len(metadata) > 0 {
|
2022-02-08 13:19:22 -06:00
|
|
|
for i := range serviceAccounts {
|
|
|
|
serviceAccounts[i].AccessControl = metadata[strconv.FormatInt(serviceAccounts[i].Id, 10)]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-12 06:23:00 -06:00
|
|
|
return response.JSON(http.StatusOK, serviceAccounts)
|
|
|
|
}
|
2022-01-19 03:23:46 -06:00
|
|
|
|
2022-02-18 04:27:00 -06:00
|
|
|
func (api *ServiceAccountsAPI) getAccessControlMetadata(c *models.ReqContext, saIDs map[string]bool) map[string]accesscontrol.Metadata {
|
2022-02-08 13:19:22 -06:00
|
|
|
if api.accesscontrol.IsDisabled() || !c.QueryBool("accesscontrol") {
|
2022-02-18 04:27:00 -06:00
|
|
|
return map[string]accesscontrol.Metadata{}
|
2022-02-08 13:19:22 -06:00
|
|
|
}
|
|
|
|
|
2022-02-18 04:27:00 -06:00
|
|
|
if c.SignedInUser.Permissions == nil {
|
|
|
|
return map[string]accesscontrol.Metadata{}
|
2022-02-08 13:19:22 -06:00
|
|
|
}
|
|
|
|
|
2022-02-18 04:27:00 -06:00
|
|
|
permissions, ok := c.SignedInUser.Permissions[c.OrgId]
|
|
|
|
if !ok {
|
|
|
|
return map[string]accesscontrol.Metadata{}
|
|
|
|
}
|
|
|
|
|
|
|
|
return accesscontrol.GetResourcesMetadata(c.Req.Context(), permissions, "serviceaccounts", saIDs)
|
2022-02-08 13:19:22 -06:00
|
|
|
}
|
|
|
|
|
2022-01-19 03:23:46 -06:00
|
|
|
func (api *ServiceAccountsAPI) RetrieveServiceAccount(ctx *models.ReqContext) response.Response {
|
|
|
|
scopeID, err := strconv.ParseInt(web.Params(ctx.Req)[":serviceAccountId"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return response.Error(http.StatusBadRequest, "serviceAccountId is invalid", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
serviceAccount, err := api.store.RetrieveServiceAccount(ctx.Req.Context(), ctx.OrgId, scopeID)
|
|
|
|
if err != nil {
|
|
|
|
switch {
|
|
|
|
case errors.Is(err, serviceaccounts.ErrServiceAccountNotFound):
|
|
|
|
return response.Error(http.StatusNotFound, "Failed to retrieve service account", err)
|
|
|
|
default:
|
|
|
|
return response.Error(http.StatusInternalServerError, "Failed to retrieve service account", err)
|
|
|
|
}
|
|
|
|
}
|
2022-02-22 07:58:42 -06:00
|
|
|
|
2022-02-25 04:33:34 -06:00
|
|
|
saIDString := strconv.FormatInt(serviceAccount.Id, 10)
|
|
|
|
metadata := api.getAccessControlMetadata(ctx, map[string]bool{saIDString: true})
|
2022-02-22 07:58:42 -06:00
|
|
|
serviceAccount.AvatarUrl = dtos.GetGravatarUrlWithDefault("", serviceAccount.Name)
|
2022-02-25 04:33:34 -06:00
|
|
|
serviceAccount.AccessControl = metadata[saIDString]
|
2022-01-19 03:23:46 -06:00
|
|
|
return response.JSON(http.StatusOK, serviceAccount)
|
|
|
|
}
|
2022-02-17 06:19:58 -06:00
|
|
|
|
|
|
|
func (api *ServiceAccountsAPI) updateServiceAccount(c *models.ReqContext) response.Response {
|
|
|
|
scopeID, err := strconv.ParseInt(web.Params(c.Req)[":serviceAccountId"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return response.Error(http.StatusBadRequest, "serviceAccountId is invalid", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd := &serviceaccounts.UpdateServiceAccountForm{}
|
|
|
|
if err := web.Bind(c.Req, &cmd); err != nil {
|
|
|
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if cmd.Role != nil && !cmd.Role.IsValid() {
|
|
|
|
return response.Error(http.StatusBadRequest, "Invalid role specified", nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := api.store.UpdateServiceAccount(c.Req.Context(), c.OrgId, scopeID, cmd)
|
|
|
|
if err != nil {
|
|
|
|
switch {
|
|
|
|
case errors.Is(err, serviceaccounts.ErrServiceAccountNotFound):
|
|
|
|
return response.Error(http.StatusNotFound, "Failed to retrieve service account", err)
|
|
|
|
default:
|
|
|
|
return response.Error(http.StatusInternalServerError, "Failed update service account", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return response.JSON(http.StatusOK, resp)
|
|
|
|
}
|