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-01-19 02:55:38 -06:00
|
|
|
"time"
|
|
|
|
|
2021-11-11 09:10:24 -06:00
|
|
|
"github.com/grafana/grafana/pkg/api/response"
|
|
|
|
"github.com/grafana/grafana/pkg/api/routing"
|
|
|
|
"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"
|
|
|
|
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
2022-01-24 09:08:05 -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 {
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
func NewServiceAccountsAPI(
|
|
|
|
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{
|
|
|
|
service: service,
|
|
|
|
accesscontrol: accesscontrol,
|
|
|
|
RouterRegister: routerRegister,
|
2021-12-16 07:28:16 -06:00
|
|
|
store: store,
|
2021-11-11 09:10:24 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (api *ServiceAccountsAPI) RegisterAPIEndpoints(
|
2022-01-24 09:08:05 -06:00
|
|
|
cfg *setting.Cfg,
|
2021-11-11 09:10:24 -06:00
|
|
|
) {
|
2022-01-24 09:08:05 -06:00
|
|
|
if !cfg.FeatureToggles["service-accounts"] {
|
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-01-13 07:15:43 -06:00
|
|
|
api.RouterRegister.Group("/api/org/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-01-19 03:23:46 -06:00
|
|
|
serviceAccountsRoute.Get("/:serviceAccountId", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeID)), routing.Wrap(api.RetrieveServiceAccount))
|
2021-11-11 09:10:24 -06:00
|
|
|
serviceAccountsRoute.Delete("/:serviceAccountId", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionDelete, serviceaccounts.ScopeID)), routing.Wrap(api.DeleteServiceAccount))
|
2021-12-16 07:28:16 -06:00
|
|
|
serviceAccountsRoute.Get("/upgrade", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate, serviceaccounts.ScopeID)), 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))
|
2021-12-14 07:39:25 -06:00
|
|
|
serviceAccountsRoute.Post("/", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate, serviceaccounts.ScopeID)), routing.Wrap(api.CreateServiceAccount))
|
2022-01-19 02:55:38 -06:00
|
|
|
serviceAccountsRoute.Get("/:serviceAccountId/tokens", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeID)), routing.Wrap(api.ListTokens))
|
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 {
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
return response.Error(500, "Internal server error", err)
|
|
|
|
}
|
|
|
|
}
|
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-01-19 02:55:38 -06:00
|
|
|
func (api *ServiceAccountsAPI) ListTokens(ctx *models.ReqContext) response.Response {
|
|
|
|
saID, err := strconv.ParseInt(web.Params(ctx.Req)[":serviceAccountId"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return response.Error(http.StatusBadRequest, "serviceAccountId is invalid", err)
|
|
|
|
}
|
|
|
|
if saTokens, err := api.store.ListTokens(ctx.Req.Context(), ctx.OrgId, saID); err == nil {
|
|
|
|
result := make([]*models.ApiKeyDTO, len(saTokens))
|
|
|
|
for i, t := range saTokens {
|
|
|
|
var expiration *time.Time = nil
|
|
|
|
if t.Expires != nil {
|
|
|
|
v := time.Unix(*t.Expires, 0)
|
|
|
|
expiration = &v
|
|
|
|
}
|
|
|
|
result[i] = &models.ApiKeyDTO{
|
|
|
|
Id: t.Id,
|
|
|
|
Name: t.Name,
|
|
|
|
Role: t.Role,
|
|
|
|
Expiration: expiration,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return response.JSON(200, result)
|
|
|
|
} else {
|
|
|
|
return response.Error(500, "Internal server error", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-12 06:23:00 -06:00
|
|
|
func (api *ServiceAccountsAPI) ListServiceAccounts(ctx *models.ReqContext) response.Response {
|
|
|
|
serviceAccounts, err := api.store.ListServiceAccounts(ctx.Req.Context(), ctx.OrgId)
|
|
|
|
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
|
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, serviceAccounts)
|
|
|
|
}
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, serviceAccount)
|
|
|
|
}
|