diff --git a/pkg/services/serviceaccounts/api/api.go b/pkg/services/serviceaccounts/api/api.go index e8f91700384..4d9dd2bc593 100644 --- a/pkg/services/serviceaccounts/api/api.go +++ b/pkg/services/serviceaccounts/api/api.go @@ -5,6 +5,8 @@ import ( "net/http" "strconv" + "time" + "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/middleware" @@ -49,6 +51,7 @@ func (api *ServiceAccountsAPI) RegisterAPIEndpoints( serviceAccountsRoute.Delete("/:serviceAccountId", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionDelete, serviceaccounts.ScopeID)), routing.Wrap(api.DeleteServiceAccount)) serviceAccountsRoute.Get("/upgrade", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate, serviceaccounts.ScopeID)), routing.Wrap(api.UpgradeServiceAccounts)) serviceAccountsRoute.Post("/", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate, serviceaccounts.ScopeID)), routing.Wrap(api.CreateServiceAccount)) + serviceAccountsRoute.Get("/:serviceAccountId/tokens", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeID)), routing.Wrap(api.ListTokens)) }) } @@ -89,6 +92,33 @@ func (api *ServiceAccountsAPI) UpgradeServiceAccounts(ctx *models.ReqContext) re } } +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) + } +} + func (api *ServiceAccountsAPI) ListServiceAccounts(ctx *models.ReqContext) response.Response { serviceAccounts, err := api.store.ListServiceAccounts(ctx.Req.Context(), ctx.OrgId) if err != nil { diff --git a/pkg/services/serviceaccounts/database/database.go b/pkg/services/serviceaccounts/database/database.go index bbd362e47d4..0528b57a4ce 100644 --- a/pkg/services/serviceaccounts/database/database.go +++ b/pkg/services/serviceaccounts/database/database.go @@ -1,14 +1,17 @@ package database +//nolint:goimports import ( "context" "fmt" + "time" "github.com/google/uuid" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/sqlstore" + "xorm.io/xorm" ) type ServiceAccountsStoreImpl struct { @@ -85,6 +88,21 @@ func (s *ServiceAccountsStoreImpl) UpgradeServiceAccounts(ctx context.Context) e return nil } +//nolint:gosimple +func (s *ServiceAccountsStoreImpl) ListTokens(ctx context.Context, orgID int64, serviceAccount int64) ([]*models.ApiKey, error) { + result := make([]*models.ApiKey, 0) + err := s.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error { + var sess *xorm.Session + + sess = dbSession.Limit(100, 0). + Join("inner", "user", "user.id = api_key.service_account_id"). + Where("user.org_id=? AND user.id=? AND ( expires IS NULL or expires >= ?)", orgID, serviceAccount, time.Now().Unix()). + Asc("name") + + return sess.Find(&result) + }) + return result, err +} func (s *ServiceAccountsStoreImpl) ListServiceAccounts(ctx context.Context, orgID int64) ([]*models.OrgUserDTO, error) { query := models.GetOrgUsersQuery{OrgId: orgID, IsServiceAccount: true} err := s.sqlStore.GetOrgUsers(ctx, &query) diff --git a/pkg/services/serviceaccounts/serviceaccounts.go b/pkg/services/serviceaccounts/serviceaccounts.go index 7ad36dc7a4a..2d7b44e7715 100644 --- a/pkg/services/serviceaccounts/serviceaccounts.go +++ b/pkg/services/serviceaccounts/serviceaccounts.go @@ -17,4 +17,5 @@ type Store interface { ListServiceAccounts(ctx context.Context, orgID int64) ([]*models.OrgUserDTO, error) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error UpgradeServiceAccounts(ctx context.Context) error + ListTokens(ctx context.Context, orgID int64, serviceAccount int64) ([]*models.ApiKey, error) } diff --git a/pkg/services/serviceaccounts/tests/common.go b/pkg/services/serviceaccounts/tests/common.go index c8fc4f56dd3..f933d7be73b 100644 --- a/pkg/services/serviceaccounts/tests/common.go +++ b/pkg/services/serviceaccounts/tests/common.go @@ -60,6 +60,7 @@ type Calls struct { ListServiceAccounts []interface{} DeleteServiceAccount []interface{} UpgradeServiceAccounts []interface{} + ListTokens []interface{} } type ServiceAccountsStoreMock struct { @@ -79,10 +80,14 @@ func (s *ServiceAccountsStoreMock) DeleteServiceAccount(ctx context.Context, org } func (s *ServiceAccountsStoreMock) UpgradeServiceAccounts(ctx context.Context) error { - s.Calls.DeleteServiceAccount = append(s.Calls.UpgradeServiceAccounts, []interface{}{ctx}) + s.Calls.UpgradeServiceAccounts = append(s.Calls.UpgradeServiceAccounts, []interface{}{ctx}) return nil } +func (s *ServiceAccountsStoreMock) ListTokens(ctx context.Context, orgID int64, serviceAccount int64) ([]*models.ApiKey, error) { + s.Calls.ListTokens = append(s.Calls.ListTokens, []interface{}{ctx, orgID, serviceAccount}) + return nil, nil +} func (s *ServiceAccountsStoreMock) ListServiceAccounts(ctx context.Context, orgID int64) ([]*models.OrgUserDTO, error) { s.Calls.ListServiceAccounts = append(s.Calls.ListServiceAccounts, []interface{}{ctx, orgID}) return nil, nil diff --git a/pkg/services/sqlstore/dashboard_test.go b/pkg/services/sqlstore/dashboard_test.go index 5f24b14b8a9..616af63c0d4 100644 --- a/pkg/services/sqlstore/dashboard_test.go +++ b/pkg/services/sqlstore/dashboard_test.go @@ -8,14 +8,15 @@ import ( "encoding/json" "errors" "fmt" + "testing" + "time" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" - "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require"