Chore: Move api key models into apikey service package (#53241)

* Chore: move api key models into apikey service package

* force table name for api key
pull/53303/head
Serge Zaitsev 3 years ago committed by GitHub
parent af83a09a92
commit 191ab3bb01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      pkg/api/apikey.go
  2. 3
      pkg/middleware/middleware_basic_auth_test.go
  3. 7
      pkg/middleware/middleware_test.go
  4. 64
      pkg/models/apikey.go
  5. 16
      pkg/services/apikey/apikey.go
  6. 15
      pkg/services/apikey/apikeyimpl/apikey.go
  7. 70
      pkg/services/apikey/apikeyimpl/store.go
  8. 47
      pkg/services/apikey/apikeyimpl/store_test.go
  9. 20
      pkg/services/apikey/apikeytest/fake.go
  10. 61
      pkg/services/apikey/model.go
  11. 8
      pkg/services/contexthandler/contexthandler.go
  12. 5
      pkg/services/serviceaccounts/api/token.go
  13. 21
      pkg/services/serviceaccounts/api/token_test.go
  14. 4
      pkg/services/serviceaccounts/database/database.go
  15. 4
      pkg/services/serviceaccounts/database/stats_test.go
  16. 17
      pkg/services/serviceaccounts/database/token_store.go
  17. 8
      pkg/services/serviceaccounts/database/token_store_test.go
  18. 3
      pkg/services/serviceaccounts/models.go
  19. 3
      pkg/services/serviceaccounts/serviceaccounts.go
  20. 9
      pkg/services/serviceaccounts/tests/common.go
  21. 33
      pkg/services/sqlstore/mockstore/mockstore.go

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/components/apikeygen" "github.com/grafana/grafana/pkg/components/apikeygen"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web"
) )
@ -26,7 +27,7 @@ import (
// 404: notFoundError // 404: notFoundError
// 500: internalServerError // 500: internalServerError
func (hs *HTTPServer) GetAPIKeys(c *models.ReqContext) response.Response { func (hs *HTTPServer) GetAPIKeys(c *models.ReqContext) response.Response {
query := models.GetApiKeysQuery{OrgId: c.OrgId, User: c.SignedInUser, IncludeExpired: c.QueryBool("includeExpired")} query := apikey.GetApiKeysQuery{OrgId: c.OrgId, User: c.SignedInUser, IncludeExpired: c.QueryBool("includeExpired")}
if err := hs.apiKeyService.GetAPIKeys(c.Req.Context(), &query); err != nil { if err := hs.apiKeyService.GetAPIKeys(c.Req.Context(), &query); err != nil {
return response.Error(500, "Failed to list api keys", err) return response.Error(500, "Failed to list api keys", err)
@ -75,11 +76,11 @@ func (hs *HTTPServer) DeleteAPIKey(c *models.ReqContext) response.Response {
return response.Error(http.StatusBadRequest, "id is invalid", err) return response.Error(http.StatusBadRequest, "id is invalid", err)
} }
cmd := &models.DeleteApiKeyCommand{Id: id, OrgId: c.OrgId} cmd := &apikey.DeleteCommand{Id: id, OrgId: c.OrgId}
err = hs.apiKeyService.DeleteApiKey(c.Req.Context(), cmd) err = hs.apiKeyService.DeleteApiKey(c.Req.Context(), cmd)
if err != nil { if err != nil {
var status int var status int
if errors.Is(err, models.ErrApiKeyNotFound) { if errors.Is(err, apikey.ErrNotFound) {
status = 404 status = 404
} else { } else {
status = 500 status = 500
@ -104,7 +105,7 @@ func (hs *HTTPServer) DeleteAPIKey(c *models.ReqContext) response.Response {
// 409: conflictError // 409: conflictError
// 500: internalServerError // 500: internalServerError
func (hs *HTTPServer) AddAPIKey(c *models.ReqContext) response.Response { func (hs *HTTPServer) AddAPIKey(c *models.ReqContext) response.Response {
cmd := models.AddApiKeyCommand{} cmd := apikey.AddCommand{}
if err := web.Bind(c.Req, &cmd); err != nil { if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err) return response.Error(http.StatusBadRequest, "bad request data", err)
} }
@ -133,10 +134,10 @@ func (hs *HTTPServer) AddAPIKey(c *models.ReqContext) response.Response {
cmd.Key = newKeyInfo.HashedKey cmd.Key = newKeyInfo.HashedKey
if err := hs.apiKeyService.AddAPIKey(c.Req.Context(), &cmd); err != nil { if err := hs.apiKeyService.AddAPIKey(c.Req.Context(), &cmd); err != nil {
if errors.Is(err, models.ErrInvalidApiKeyExpiration) { if errors.Is(err, apikey.ErrInvalidExpiration) {
return response.Error(400, err.Error(), nil) return response.Error(400, err.Error(), nil)
} }
if errors.Is(err, models.ErrDuplicateApiKey) { if errors.Is(err, apikey.ErrDuplicate) {
return response.Error(409, err.Error(), nil) return response.Error(409, err.Error(), nil)
} }
return response.Error(500, "Failed to add API Key", err) return response.Error(500, "Failed to add API Key", err)
@ -164,7 +165,7 @@ type GetAPIkeysParams struct {
type AddAPIkeyParams struct { type AddAPIkeyParams struct {
// in:body // in:body
// required:true // required:true
Body models.AddApiKeyCommand Body apikey.AddCommand
} }
// swagger:parameters deleteAPIkey // swagger:parameters deleteAPIkey

@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/services/login/logintest" "github.com/grafana/grafana/pkg/services/login/logintest"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
@ -29,7 +30,7 @@ func TestMiddlewareBasicAuth(t *testing.T) {
keyhash, err := util.EncodePassword("v5nAwpMafFP6znaS4urhdWDLS5511M42", "asd") keyhash, err := util.EncodePassword("v5nAwpMafFP6znaS4urhdWDLS5511M42", "asd")
require.NoError(t, err) require.NoError(t, err)
sc.apiKeyService.ExpectedAPIKey = &models.ApiKey{OrgId: orgID, Role: models.ROLE_EDITOR, Key: keyhash} sc.apiKeyService.ExpectedAPIKey = &apikey.APIKey{OrgId: orgID, Role: models.ROLE_EDITOR, Key: keyhash}
authHeader := util.GetBasicAuthHeader("api_key", "eyJrIjoidjVuQXdwTWFmRlA2em5hUzR1cmhkV0RMUzU1MTFNNDIiLCJuIjoiYXNkIiwiaWQiOjF9") authHeader := util.GetBasicAuthHeader("api_key", "eyJrIjoidjVuQXdwTWFmRlA2em5hUzR1cmhkV0RMUzU1MTFNNDIiLCJuIjoiYXNkIiwiaWQiOjF9")
sc.fakeReq("GET", "/").withAuthorizationHeader(authHeader).exec() sc.fakeReq("GET", "/").withAuthorizationHeader(authHeader).exec()

@ -20,6 +20,7 @@ import (
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/apikey/apikeytest" "github.com/grafana/grafana/pkg/services/apikey/apikeytest"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/contexthandler"
@ -151,7 +152,7 @@ func TestMiddlewareContext(t *testing.T) {
keyhash, err := util.EncodePassword("v5nAwpMafFP6znaS4urhdWDLS5511M42", "asd") keyhash, err := util.EncodePassword("v5nAwpMafFP6znaS4urhdWDLS5511M42", "asd")
require.NoError(t, err) require.NoError(t, err)
sc.apiKeyService.ExpectedAPIKey = &models.ApiKey{OrgId: orgID, Role: models.ROLE_EDITOR, Key: keyhash} sc.apiKeyService.ExpectedAPIKey = &apikey.APIKey{OrgId: orgID, Role: models.ROLE_EDITOR, Key: keyhash}
sc.fakeReq("GET", "/").withValidApiKey().exec() sc.fakeReq("GET", "/").withValidApiKey().exec()
@ -164,7 +165,7 @@ func TestMiddlewareContext(t *testing.T) {
middlewareScenario(t, "Valid API key, but does not match DB hash", func(t *testing.T, sc *scenarioContext) { middlewareScenario(t, "Valid API key, but does not match DB hash", func(t *testing.T, sc *scenarioContext) {
const keyhash = "Something_not_matching" const keyhash = "Something_not_matching"
sc.apiKeyService.ExpectedAPIKey = &models.ApiKey{OrgId: 12, Role: models.ROLE_EDITOR, Key: keyhash} sc.apiKeyService.ExpectedAPIKey = &apikey.APIKey{OrgId: 12, Role: models.ROLE_EDITOR, Key: keyhash}
sc.fakeReq("GET", "/").withValidApiKey().exec() sc.fakeReq("GET", "/").withValidApiKey().exec()
@ -179,7 +180,7 @@ func TestMiddlewareContext(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
expires := sc.contextHandler.GetTime().Add(-1 * time.Second).Unix() expires := sc.contextHandler.GetTime().Add(-1 * time.Second).Unix()
sc.apiKeyService.ExpectedAPIKey = &models.ApiKey{OrgId: 12, Role: models.ROLE_EDITOR, Key: keyhash, Expires: &expires} sc.apiKeyService.ExpectedAPIKey = &apikey.APIKey{OrgId: 12, Role: models.ROLE_EDITOR, Key: keyhash, Expires: &expires}
sc.fakeReq("GET", "/").withValidApiKey().exec() sc.fakeReq("GET", "/").withValidApiKey().exec()

@ -1,64 +0,0 @@
package models
import (
"errors"
"time"
)
var (
ErrApiKeyNotFound = errors.New("API key not found")
ErrInvalidApiKey = errors.New("invalid API key")
ErrInvalidApiKeyExpiration = errors.New("negative value for SecondsToLive")
ErrDuplicateApiKey = errors.New("API key, organization ID and name must be unique")
)
type ApiKey struct {
Id int64
OrgId int64
Name string
Key string
Role RoleType
Created time.Time
Updated time.Time
LastUsedAt *time.Time `xorm:"last_used_at"`
Expires *int64
ServiceAccountId *int64
}
// ---------------------
// COMMANDS
// swagger:model
type AddApiKeyCommand struct {
Name string `json:"name" binding:"Required"`
Role RoleType `json:"role" binding:"Required"`
OrgId int64 `json:"-"`
Key string `json:"-"`
SecondsToLive int64 `json:"secondsToLive"`
Result *ApiKey `json:"-"`
}
type DeleteApiKeyCommand struct {
Id int64 `json:"id"`
OrgId int64 `json:"-"`
}
// ----------------------
// QUERIES
type GetApiKeysQuery struct {
OrgId int64
IncludeExpired bool
User *SignedInUser
Result []*ApiKey
}
type GetApiKeyByNameQuery struct {
KeyName string
OrgId int64
Result *ApiKey
}
type GetApiKeyByIdQuery struct {
ApiKeyId int64
Result *ApiKey
}

@ -2,17 +2,15 @@ package apikey
import ( import (
"context" "context"
"github.com/grafana/grafana/pkg/models"
) )
type Service interface { type Service interface {
GetAPIKeys(ctx context.Context, query *models.GetApiKeysQuery) error GetAPIKeys(ctx context.Context, query *GetApiKeysQuery) error
GetAllAPIKeys(ctx context.Context, orgID int64) []*models.ApiKey GetAllAPIKeys(ctx context.Context, orgID int64) []*APIKey
DeleteApiKey(ctx context.Context, cmd *models.DeleteApiKeyCommand) error DeleteApiKey(ctx context.Context, cmd *DeleteCommand) error
AddAPIKey(ctx context.Context, cmd *models.AddApiKeyCommand) error AddAPIKey(ctx context.Context, cmd *AddCommand) error
GetApiKeyById(ctx context.Context, query *models.GetApiKeyByIdQuery) error GetApiKeyById(ctx context.Context, query *GetByIDQuery) error
GetApiKeyByName(ctx context.Context, query *models.GetApiKeyByNameQuery) error GetApiKeyByName(ctx context.Context, query *GetByNameQuery) error
GetAPIKeyByHash(ctx context.Context, hash string) (*models.ApiKey, error) GetAPIKeyByHash(ctx context.Context, hash string) (*APIKey, error)
UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error
} }

@ -3,7 +3,6 @@ package apikeyimpl
import ( import (
"context" "context"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/sqlstore/db" "github.com/grafana/grafana/pkg/services/sqlstore/db"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@ -17,25 +16,25 @@ func ProvideService(db db.DB, cfg *setting.Cfg) apikey.Service {
return &Service{store: &sqlStore{db: db, cfg: cfg}} return &Service{store: &sqlStore{db: db, cfg: cfg}}
} }
func (s *Service) GetAPIKeys(ctx context.Context, query *models.GetApiKeysQuery) error { func (s *Service) GetAPIKeys(ctx context.Context, query *apikey.GetApiKeysQuery) error {
return s.store.GetAPIKeys(ctx, query) return s.store.GetAPIKeys(ctx, query)
} }
func (s *Service) GetAllAPIKeys(ctx context.Context, orgID int64) []*models.ApiKey { func (s *Service) GetAllAPIKeys(ctx context.Context, orgID int64) []*apikey.APIKey {
return s.store.GetAllAPIKeys(ctx, orgID) return s.store.GetAllAPIKeys(ctx, orgID)
} }
func (s *Service) GetApiKeyById(ctx context.Context, query *models.GetApiKeyByIdQuery) error { func (s *Service) GetApiKeyById(ctx context.Context, query *apikey.GetByIDQuery) error {
return s.store.GetApiKeyById(ctx, query) return s.store.GetApiKeyById(ctx, query)
} }
func (s *Service) GetApiKeyByName(ctx context.Context, query *models.GetApiKeyByNameQuery) error { func (s *Service) GetApiKeyByName(ctx context.Context, query *apikey.GetByNameQuery) error {
return s.store.GetApiKeyByName(ctx, query) return s.store.GetApiKeyByName(ctx, query)
} }
func (s *Service) GetAPIKeyByHash(ctx context.Context, hash string) (*models.ApiKey, error) { func (s *Service) GetAPIKeyByHash(ctx context.Context, hash string) (*apikey.APIKey, error) {
return s.store.GetAPIKeyByHash(ctx, hash) return s.store.GetAPIKeyByHash(ctx, hash)
} }
func (s *Service) DeleteApiKey(ctx context.Context, cmd *models.DeleteApiKeyCommand) error { func (s *Service) DeleteApiKey(ctx context.Context, cmd *apikey.DeleteCommand) error {
return s.store.DeleteApiKey(ctx, cmd) return s.store.DeleteApiKey(ctx, cmd)
} }
func (s *Service) AddAPIKey(ctx context.Context, cmd *models.AddApiKeyCommand) error { func (s *Service) AddAPIKey(ctx context.Context, cmd *apikey.AddCommand) error {
return s.store.AddAPIKey(ctx, cmd) return s.store.AddAPIKey(ctx, cmd)
} }
func (s *Service) UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error { func (s *Service) UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error {

@ -5,8 +5,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/db" "github.com/grafana/grafana/pkg/services/sqlstore/db"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@ -14,13 +14,13 @@ import (
) )
type store interface { type store interface {
GetAPIKeys(ctx context.Context, query *models.GetApiKeysQuery) error GetAPIKeys(ctx context.Context, query *apikey.GetApiKeysQuery) error
GetAllAPIKeys(ctx context.Context, orgID int64) []*models.ApiKey GetAllAPIKeys(ctx context.Context, orgID int64) []*apikey.APIKey
DeleteApiKey(ctx context.Context, cmd *models.DeleteApiKeyCommand) error DeleteApiKey(ctx context.Context, cmd *apikey.DeleteCommand) error
AddAPIKey(ctx context.Context, cmd *models.AddApiKeyCommand) error AddAPIKey(ctx context.Context, cmd *apikey.AddCommand) error
GetApiKeyById(ctx context.Context, query *models.GetApiKeyByIdQuery) error GetApiKeyById(ctx context.Context, query *apikey.GetByIDQuery) error
GetApiKeyByName(ctx context.Context, query *models.GetApiKeyByNameQuery) error GetApiKeyByName(ctx context.Context, query *apikey.GetByNameQuery) error
GetAPIKeyByHash(ctx context.Context, hash string) (*models.ApiKey, error) GetAPIKeyByHash(ctx context.Context, hash string) (*apikey.APIKey, error)
UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error
} }
@ -32,7 +32,7 @@ type sqlStore struct {
// timeNow makes it possible to test usage of time // timeNow makes it possible to test usage of time
var timeNow = time.Now var timeNow = time.Now
func (ss *sqlStore) GetAPIKeys(ctx context.Context, query *models.GetApiKeysQuery) error { func (ss *sqlStore) GetAPIKeys(ctx context.Context, query *apikey.GetApiKeysQuery) error {
return ss.db.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error { return ss.db.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
var sess *xorm.Session var sess *xorm.Session
@ -56,13 +56,13 @@ func (ss *sqlStore) GetAPIKeys(ctx context.Context, query *models.GetApiKeysQuer
sess.And(filter.Where, filter.Args...) sess.And(filter.Where, filter.Args...)
} }
query.Result = make([]*models.ApiKey, 0) query.Result = make([]*apikey.APIKey, 0)
return sess.Find(&query.Result) return sess.Find(&query.Result)
}) })
} }
func (ss *sqlStore) GetAllAPIKeys(ctx context.Context, orgID int64) []*models.ApiKey { func (ss *sqlStore) GetAllAPIKeys(ctx context.Context, orgID int64) []*apikey.APIKey {
result := make([]*models.ApiKey, 0) result := make([]*apikey.APIKey, 0)
err := ss.db.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error { err := ss.db.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
sess := dbSession.Where("service_account_id IS NULL").Asc("name") sess := dbSession.Where("service_account_id IS NULL").Asc("name")
if orgID != -1 { if orgID != -1 {
@ -77,7 +77,7 @@ func (ss *sqlStore) GetAllAPIKeys(ctx context.Context, orgID int64) []*models.Ap
return result return result
} }
func (ss *sqlStore) DeleteApiKey(ctx context.Context, cmd *models.DeleteApiKeyCommand) error { func (ss *sqlStore) DeleteApiKey(ctx context.Context, cmd *apikey.DeleteCommand) error {
return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
rawSQL := "DELETE FROM api_key WHERE id=? and org_id=? and service_account_id IS NULL" rawSQL := "DELETE FROM api_key WHERE id=? and org_id=? and service_account_id IS NULL"
result, err := sess.Exec(rawSQL, cmd.Id, cmd.OrgId) result, err := sess.Exec(rawSQL, cmd.Id, cmd.OrgId)
@ -88,18 +88,18 @@ func (ss *sqlStore) DeleteApiKey(ctx context.Context, cmd *models.DeleteApiKeyCo
if err != nil { if err != nil {
return err return err
} else if n == 0 { } else if n == 0 {
return models.ErrApiKeyNotFound return apikey.ErrNotFound
} }
return nil return nil
}) })
} }
func (ss *sqlStore) AddAPIKey(ctx context.Context, cmd *models.AddApiKeyCommand) error { func (ss *sqlStore) AddAPIKey(ctx context.Context, cmd *apikey.AddCommand) error {
return ss.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { return ss.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
key := models.ApiKey{OrgId: cmd.OrgId, Name: cmd.Name} key := apikey.APIKey{OrgId: cmd.OrgId, Name: cmd.Name}
exists, _ := sess.Get(&key) exists, _ := sess.Get(&key)
if exists { if exists {
return models.ErrDuplicateApiKey return apikey.ErrDuplicate
} }
updated := timeNow() updated := timeNow()
@ -108,10 +108,10 @@ func (ss *sqlStore) AddAPIKey(ctx context.Context, cmd *models.AddApiKeyCommand)
v := updated.Add(time.Second * time.Duration(cmd.SecondsToLive)).Unix() v := updated.Add(time.Second * time.Duration(cmd.SecondsToLive)).Unix()
expires = &v expires = &v
} else if cmd.SecondsToLive < 0 { } else if cmd.SecondsToLive < 0 {
return models.ErrInvalidApiKeyExpiration return apikey.ErrInvalidExpiration
} }
t := models.ApiKey{ t := apikey.APIKey{
OrgId: cmd.OrgId, OrgId: cmd.OrgId,
Name: cmd.Name, Name: cmd.Name,
Role: cmd.Role, Role: cmd.Role,
@ -130,56 +130,56 @@ func (ss *sqlStore) AddAPIKey(ctx context.Context, cmd *models.AddApiKeyCommand)
}) })
} }
func (ss *sqlStore) GetApiKeyById(ctx context.Context, query *models.GetApiKeyByIdQuery) error { func (ss *sqlStore) GetApiKeyById(ctx context.Context, query *apikey.GetByIDQuery) error {
return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
var apikey models.ApiKey var key apikey.APIKey
has, err := sess.ID(query.ApiKeyId).Get(&apikey) has, err := sess.ID(query.ApiKeyId).Get(&key)
if err != nil { if err != nil {
return err return err
} else if !has { } else if !has {
return models.ErrInvalidApiKey return apikey.ErrInvalid
} }
query.Result = &apikey query.Result = &key
return nil return nil
}) })
} }
func (ss *sqlStore) GetApiKeyByName(ctx context.Context, query *models.GetApiKeyByNameQuery) error { func (ss *sqlStore) GetApiKeyByName(ctx context.Context, query *apikey.GetByNameQuery) error {
return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
var apikey models.ApiKey var key apikey.APIKey
has, err := sess.Where("org_id=? AND name=?", query.OrgId, query.KeyName).Get(&apikey) has, err := sess.Where("org_id=? AND name=?", query.OrgId, query.KeyName).Get(&key)
if err != nil { if err != nil {
return err return err
} else if !has { } else if !has {
return models.ErrInvalidApiKey return apikey.ErrInvalid
} }
query.Result = &apikey query.Result = &key
return nil return nil
}) })
} }
func (ss *sqlStore) GetAPIKeyByHash(ctx context.Context, hash string) (*models.ApiKey, error) { func (ss *sqlStore) GetAPIKeyByHash(ctx context.Context, hash string) (*apikey.APIKey, error) {
var apikey models.ApiKey var key apikey.APIKey
err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
has, err := sess.Table("api_key").Where(fmt.Sprintf("%s = ?", ss.db.GetDialect().Quote("key")), hash).Get(&apikey) has, err := sess.Table("api_key").Where(fmt.Sprintf("%s = ?", ss.db.GetDialect().Quote("key")), hash).Get(&key)
if err != nil { if err != nil {
return err return err
} else if !has { } else if !has {
return models.ErrInvalidApiKey return apikey.ErrInvalid
} }
return nil return nil
}) })
return &apikey, err return &key, err
} }
func (ss *sqlStore) UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error { func (ss *sqlStore) UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error {
now := timeNow() now := timeNow()
return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
if _, err := sess.Table("api_key").ID(tokenID).Cols("last_used_at").Update(&models.ApiKey{LastUsedAt: &now}); err != nil { if _, err := sess.Table("api_key").ID(tokenID).Cols("last_used_at").Update(&apikey.APIKey{LastUsedAt: &now}); err != nil {
return err return err
} }

@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
) )
@ -40,12 +41,12 @@ func TestIntegrationApiKeyDataAccess(t *testing.T) {
ss := &sqlStore{db: db, cfg: db.Cfg} ss := &sqlStore{db: db, cfg: db.Cfg}
t.Run("Given saved api key", func(t *testing.T) { t.Run("Given saved api key", func(t *testing.T) {
cmd := models.AddApiKeyCommand{OrgId: 1, Name: "hello", Key: "asd"} cmd := apikey.AddCommand{OrgId: 1, Name: "hello", Key: "asd"}
err := ss.AddAPIKey(context.Background(), &cmd) err := ss.AddAPIKey(context.Background(), &cmd)
assert.Nil(t, err) assert.Nil(t, err)
t.Run("Should be able to get key by name", func(t *testing.T) { t.Run("Should be able to get key by name", func(t *testing.T) {
query := models.GetApiKeyByNameQuery{KeyName: "hello", OrgId: 1} query := apikey.GetByNameQuery{KeyName: "hello", OrgId: 1}
err = ss.GetApiKeyByName(context.Background(), &query) err = ss.GetApiKeyByName(context.Background(), &query)
assert.Nil(t, err) assert.Nil(t, err)
@ -61,11 +62,11 @@ func TestIntegrationApiKeyDataAccess(t *testing.T) {
}) })
t.Run("Add non expiring key", func(t *testing.T) { t.Run("Add non expiring key", func(t *testing.T) {
cmd := models.AddApiKeyCommand{OrgId: 1, Name: "non-expiring", Key: "asd1", SecondsToLive: 0} cmd := apikey.AddCommand{OrgId: 1, Name: "non-expiring", Key: "asd1", SecondsToLive: 0}
err := ss.AddAPIKey(context.Background(), &cmd) err := ss.AddAPIKey(context.Background(), &cmd)
assert.Nil(t, err) assert.Nil(t, err)
query := models.GetApiKeyByNameQuery{KeyName: "non-expiring", OrgId: 1} query := apikey.GetByNameQuery{KeyName: "non-expiring", OrgId: 1}
err = ss.GetApiKeyByName(context.Background(), &query) err = ss.GetApiKeyByName(context.Background(), &query)
assert.Nil(t, err) assert.Nil(t, err)
@ -74,11 +75,11 @@ func TestIntegrationApiKeyDataAccess(t *testing.T) {
t.Run("Add an expiring key", func(t *testing.T) { t.Run("Add an expiring key", func(t *testing.T) {
// expires in one hour // expires in one hour
cmd := models.AddApiKeyCommand{OrgId: 1, Name: "expiring-in-an-hour", Key: "asd2", SecondsToLive: 3600} cmd := apikey.AddCommand{OrgId: 1, Name: "expiring-in-an-hour", Key: "asd2", SecondsToLive: 3600}
err := ss.AddAPIKey(context.Background(), &cmd) err := ss.AddAPIKey(context.Background(), &cmd)
assert.Nil(t, err) assert.Nil(t, err)
query := models.GetApiKeyByNameQuery{KeyName: "expiring-in-an-hour", OrgId: 1} query := apikey.GetByNameQuery{KeyName: "expiring-in-an-hour", OrgId: 1}
err = ss.GetApiKeyByName(context.Background(), &query) err = ss.GetApiKeyByName(context.Background(), &query)
assert.Nil(t, err) assert.Nil(t, err)
@ -94,7 +95,7 @@ func TestIntegrationApiKeyDataAccess(t *testing.T) {
t.Run("Last Used At datetime update", func(t *testing.T) { t.Run("Last Used At datetime update", func(t *testing.T) {
// expires in one hour // expires in one hour
cmd := models.AddApiKeyCommand{OrgId: 1, Name: "last-update-at", Key: "asd3", SecondsToLive: 3600} cmd := apikey.AddCommand{OrgId: 1, Name: "last-update-at", Key: "asd3", SecondsToLive: 3600}
err := ss.AddAPIKey(context.Background(), &cmd) err := ss.AddAPIKey(context.Background(), &cmd)
require.NoError(t, err) require.NoError(t, err)
@ -103,7 +104,7 @@ func TestIntegrationApiKeyDataAccess(t *testing.T) {
err = ss.UpdateAPIKeyLastUsedDate(context.Background(), cmd.Result.Id) err = ss.UpdateAPIKeyLastUsedDate(context.Background(), cmd.Result.Id)
require.NoError(t, err) require.NoError(t, err)
query := models.GetApiKeyByNameQuery{KeyName: "last-update-at", OrgId: 1} query := apikey.GetByNameQuery{KeyName: "last-update-at", OrgId: 1}
err = ss.GetApiKeyByName(context.Background(), &query) err = ss.GetApiKeyByName(context.Background(), &query)
assert.Nil(t, err) assert.Nil(t, err)
@ -112,28 +113,28 @@ func TestIntegrationApiKeyDataAccess(t *testing.T) {
t.Run("Add a key with negative lifespan", func(t *testing.T) { t.Run("Add a key with negative lifespan", func(t *testing.T) {
// expires in one day // expires in one day
cmd := models.AddApiKeyCommand{OrgId: 1, Name: "key-with-negative-lifespan", Key: "asd3", SecondsToLive: -3600} cmd := apikey.AddCommand{OrgId: 1, Name: "key-with-negative-lifespan", Key: "asd3", SecondsToLive: -3600}
err := ss.AddAPIKey(context.Background(), &cmd) err := ss.AddAPIKey(context.Background(), &cmd)
assert.EqualError(t, err, models.ErrInvalidApiKeyExpiration.Error()) assert.EqualError(t, err, apikey.ErrInvalidExpiration.Error())
query := models.GetApiKeyByNameQuery{KeyName: "key-with-negative-lifespan", OrgId: 1} query := apikey.GetByNameQuery{KeyName: "key-with-negative-lifespan", OrgId: 1}
err = ss.GetApiKeyByName(context.Background(), &query) err = ss.GetApiKeyByName(context.Background(), &query)
assert.EqualError(t, err, "invalid API key") assert.EqualError(t, err, "invalid API key")
}) })
t.Run("Add keys", func(t *testing.T) { t.Run("Add keys", func(t *testing.T) {
// never expires // never expires
cmd := models.AddApiKeyCommand{OrgId: 1, Name: "key1", Key: "key1", SecondsToLive: 0} cmd := apikey.AddCommand{OrgId: 1, Name: "key1", Key: "key1", SecondsToLive: 0}
err := ss.AddAPIKey(context.Background(), &cmd) err := ss.AddAPIKey(context.Background(), &cmd)
assert.Nil(t, err) assert.Nil(t, err)
// expires in 1s // expires in 1s
cmd = models.AddApiKeyCommand{OrgId: 1, Name: "key2", Key: "key2", SecondsToLive: 1} cmd = apikey.AddCommand{OrgId: 1, Name: "key2", Key: "key2", SecondsToLive: 1}
err = ss.AddAPIKey(context.Background(), &cmd) err = ss.AddAPIKey(context.Background(), &cmd)
assert.Nil(t, err) assert.Nil(t, err)
// expires in one hour // expires in one hour
cmd = models.AddApiKeyCommand{OrgId: 1, Name: "key3", Key: "key3", SecondsToLive: 3600} cmd = apikey.AddCommand{OrgId: 1, Name: "key3", Key: "key3", SecondsToLive: 3600}
err = ss.AddAPIKey(context.Background(), &cmd) err = ss.AddAPIKey(context.Background(), &cmd)
assert.Nil(t, err) assert.Nil(t, err)
@ -146,7 +147,7 @@ func TestIntegrationApiKeyDataAccess(t *testing.T) {
1: {accesscontrol.ActionAPIKeyRead: []string{accesscontrol.ScopeAPIKeysAll}}, 1: {accesscontrol.ActionAPIKeyRead: []string{accesscontrol.ScopeAPIKeysAll}},
}, },
} }
query := models.GetApiKeysQuery{OrgId: 1, IncludeExpired: false, User: testUser} query := apikey.GetApiKeysQuery{OrgId: 1, IncludeExpired: false, User: testUser}
err = ss.GetAPIKeys(context.Background(), &query) err = ss.GetAPIKeys(context.Background(), &query)
assert.Nil(t, err) assert.Nil(t, err)
@ -156,7 +157,7 @@ func TestIntegrationApiKeyDataAccess(t *testing.T) {
} }
} }
query = models.GetApiKeysQuery{OrgId: 1, IncludeExpired: true, User: testUser} query = apikey.GetApiKeysQuery{OrgId: 1, IncludeExpired: true, User: testUser}
err = ss.GetAPIKeys(context.Background(), &query) err = ss.GetAPIKeys(context.Background(), &query)
assert.Nil(t, err) assert.Nil(t, err)
@ -183,22 +184,22 @@ func TestIntegrationApiKeyErrors(t *testing.T) {
ss := &sqlStore{db: db, cfg: db.Cfg} ss := &sqlStore{db: db, cfg: db.Cfg}
t.Run("Delete non-existing key should return error", func(t *testing.T) { t.Run("Delete non-existing key should return error", func(t *testing.T) {
cmd := models.DeleteApiKeyCommand{Id: 1} cmd := apikey.DeleteCommand{Id: 1}
err := ss.DeleteApiKey(context.Background(), &cmd) err := ss.DeleteApiKey(context.Background(), &cmd)
assert.EqualError(t, err, models.ErrApiKeyNotFound.Error()) assert.EqualError(t, err, apikey.ErrNotFound.Error())
}) })
t.Run("Testing API Duplicate Key Errors", func(t *testing.T) { t.Run("Testing API Duplicate Key Errors", func(t *testing.T) {
t.Run("Given saved api key", func(t *testing.T) { t.Run("Given saved api key", func(t *testing.T) {
cmd := models.AddApiKeyCommand{OrgId: 0, Name: "duplicate", Key: "asd"} cmd := apikey.AddCommand{OrgId: 0, Name: "duplicate", Key: "asd"}
err := ss.AddAPIKey(context.Background(), &cmd) err := ss.AddAPIKey(context.Background(), &cmd)
assert.Nil(t, err) assert.Nil(t, err)
t.Run("Add API Key with existing Org ID and Name", func(t *testing.T) { t.Run("Add API Key with existing Org ID and Name", func(t *testing.T) {
cmd := models.AddApiKeyCommand{OrgId: 0, Name: "duplicate", Key: "asd"} cmd := apikey.AddCommand{OrgId: 0, Name: "duplicate", Key: "asd"}
err = ss.AddAPIKey(context.Background(), &cmd) err = ss.AddAPIKey(context.Background(), &cmd)
assert.EqualError(t, err, models.ErrDuplicateApiKey.Error()) assert.EqualError(t, err, apikey.ErrDuplicate.Error())
}) })
}) })
}) })
@ -245,7 +246,7 @@ func TestIntegrationSQLStore_GetAPIKeys(t *testing.T) {
store := &sqlStore{db: db, cfg: db.Cfg} store := &sqlStore{db: db, cfg: db.Cfg}
seedApiKeys(t, store, 10) seedApiKeys(t, store, 10)
query := &models.GetApiKeysQuery{OrgId: 1, User: tt.user} query := &apikey.GetApiKeysQuery{OrgId: 1, User: tt.user}
err := store.GetAPIKeys(context.Background(), query) err := store.GetAPIKeys(context.Background(), query)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, query.Result, tt.expectedNumKeys) assert.Len(t, query.Result, tt.expectedNumKeys)
@ -257,7 +258,7 @@ func seedApiKeys(t *testing.T, store store, num int) {
t.Helper() t.Helper()
for i := 0; i < num; i++ { for i := 0; i < num; i++ {
err := store.AddAPIKey(context.Background(), &models.AddApiKeyCommand{ err := store.AddAPIKey(context.Background(), &apikey.AddCommand{
Name: fmt.Sprintf("key:%d", i), Name: fmt.Sprintf("key:%d", i),
Key: fmt.Sprintf("key:%d", i), Key: fmt.Sprintf("key:%d", i),
OrgId: 1, OrgId: 1,

@ -3,37 +3,37 @@ package apikeytest
import ( import (
"context" "context"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/apikey"
) )
type Service struct { type Service struct {
ExpectedError error ExpectedError error
ExpectedAPIKeys []*models.ApiKey ExpectedAPIKeys []*apikey.APIKey
ExpectedAPIKey *models.ApiKey ExpectedAPIKey *apikey.APIKey
} }
func (s *Service) GetAPIKeys(ctx context.Context, query *models.GetApiKeysQuery) error { func (s *Service) GetAPIKeys(ctx context.Context, query *apikey.GetApiKeysQuery) error {
query.Result = s.ExpectedAPIKeys query.Result = s.ExpectedAPIKeys
return s.ExpectedError return s.ExpectedError
} }
func (s *Service) GetAllAPIKeys(ctx context.Context, orgID int64) []*models.ApiKey { func (s *Service) GetAllAPIKeys(ctx context.Context, orgID int64) []*apikey.APIKey {
return s.ExpectedAPIKeys return s.ExpectedAPIKeys
} }
func (s *Service) GetApiKeyById(ctx context.Context, query *models.GetApiKeyByIdQuery) error { func (s *Service) GetApiKeyById(ctx context.Context, query *apikey.GetByIDQuery) error {
query.Result = s.ExpectedAPIKey query.Result = s.ExpectedAPIKey
return s.ExpectedError return s.ExpectedError
} }
func (s *Service) GetApiKeyByName(ctx context.Context, query *models.GetApiKeyByNameQuery) error { func (s *Service) GetApiKeyByName(ctx context.Context, query *apikey.GetByNameQuery) error {
query.Result = s.ExpectedAPIKey query.Result = s.ExpectedAPIKey
return s.ExpectedError return s.ExpectedError
} }
func (s *Service) GetAPIKeyByHash(ctx context.Context, hash string) (*models.ApiKey, error) { func (s *Service) GetAPIKeyByHash(ctx context.Context, hash string) (*apikey.APIKey, error) {
return s.ExpectedAPIKey, s.ExpectedError return s.ExpectedAPIKey, s.ExpectedError
} }
func (s *Service) DeleteApiKey(ctx context.Context, cmd *models.DeleteApiKeyCommand) error { func (s *Service) DeleteApiKey(ctx context.Context, cmd *apikey.DeleteCommand) error {
return s.ExpectedError return s.ExpectedError
} }
func (s *Service) AddAPIKey(ctx context.Context, cmd *models.AddApiKeyCommand) error { func (s *Service) AddAPIKey(ctx context.Context, cmd *apikey.AddCommand) error {
cmd.Result = s.ExpectedAPIKey cmd.Result = s.ExpectedAPIKey
return s.ExpectedError return s.ExpectedError
} }

@ -1,3 +1,62 @@
package apikey package apikey
// TODO: define all apikey models here import (
"errors"
"time"
"github.com/grafana/grafana/pkg/models"
)
var (
ErrNotFound = errors.New("API key not found")
ErrInvalid = errors.New("invalid API key")
ErrInvalidExpiration = errors.New("negative value for SecondsToLive")
ErrDuplicate = errors.New("API key, organization ID and name must be unique")
)
type APIKey struct {
Id int64
OrgId int64
Name string
Key string
Role models.RoleType
Created time.Time
Updated time.Time
LastUsedAt *time.Time `xorm:"last_used_at"`
Expires *int64
ServiceAccountId *int64
}
func (k APIKey) TableName() string { return "api_key" }
// swagger:model
type AddCommand struct {
Name string `json:"name" binding:"Required"`
Role models.RoleType `json:"role" binding:"Required"`
OrgId int64 `json:"-"`
Key string `json:"-"`
SecondsToLive int64 `json:"secondsToLive"`
Result *APIKey `json:"-"`
}
type DeleteCommand struct {
Id int64 `json:"id"`
OrgId int64 `json:"-"`
}
type GetApiKeysQuery struct {
OrgId int64
IncludeExpired bool
User *models.SignedInUser
Result []*APIKey
}
type GetByNameQuery struct {
KeyName string
OrgId int64
Result *APIKey
}
type GetByIDQuery struct {
ApiKeyId int64
Result *APIKey
}

@ -189,7 +189,7 @@ func (h *ContextHandler) initContextWithAnonymousUser(reqContext *models.ReqCont
return true return true
} }
func (h *ContextHandler) getPrefixedAPIKey(ctx context.Context, keyString string) (*models.ApiKey, error) { func (h *ContextHandler) getPrefixedAPIKey(ctx context.Context, keyString string) (*apikey.APIKey, error) {
// prefixed decode key // prefixed decode key
decoded, err := apikeygenprefix.Decode(keyString) decoded, err := apikeygenprefix.Decode(keyString)
if err != nil { if err != nil {
@ -204,14 +204,14 @@ func (h *ContextHandler) getPrefixedAPIKey(ctx context.Context, keyString string
return h.apiKeyService.GetAPIKeyByHash(ctx, hash) return h.apiKeyService.GetAPIKeyByHash(ctx, hash)
} }
func (h *ContextHandler) getAPIKey(ctx context.Context, keyString string) (*models.ApiKey, error) { func (h *ContextHandler) getAPIKey(ctx context.Context, keyString string) (*apikey.APIKey, error) {
decoded, err := apikeygen.Decode(keyString) decoded, err := apikeygen.Decode(keyString)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// fetch key // fetch key
keyQuery := models.GetApiKeyByNameQuery{KeyName: decoded.Name, OrgId: decoded.OrgId} keyQuery := apikey.GetByNameQuery{KeyName: decoded.Name, OrgId: decoded.OrgId}
if err := h.apiKeyService.GetApiKeyByName(ctx, &keyQuery); err != nil { if err := h.apiKeyService.GetApiKeyByName(ctx, &keyQuery); err != nil {
return nil, err return nil, err
} }
@ -249,7 +249,7 @@ func (h *ContextHandler) initContextWithAPIKey(reqContext *models.ReqContext) bo
defer span.End() defer span.End()
var ( var (
apikey *models.ApiKey apikey *apikey.APIKey
errKey error errKey error
) )
if strings.HasPrefix(keyString, apikeygenprefix.GrafanaPrefix) { if strings.HasPrefix(keyString, apikeygenprefix.GrafanaPrefix) {

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
apikeygenprefix "github.com/grafana/grafana/pkg/components/apikeygenprefixed" apikeygenprefix "github.com/grafana/grafana/pkg/components/apikeygenprefixed"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/serviceaccounts/database" "github.com/grafana/grafana/pkg/services/serviceaccounts/database"
"github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web"
@ -215,10 +216,10 @@ func (api *ServiceAccountsAPI) DeleteToken(c *models.ReqContext) response.Respon
if err = api.store.DeleteServiceAccountToken(c.Req.Context(), c.OrgId, saID, tokenID); err != nil { if err = api.store.DeleteServiceAccountToken(c.Req.Context(), c.OrgId, saID, tokenID); err != nil {
status := http.StatusNotFound status := http.StatusNotFound
if err != nil && !errors.Is(err, models.ErrApiKeyNotFound) { if err != nil && !errors.Is(err, apikey.ErrNotFound) {
status = http.StatusInternalServerError status = http.StatusInternalServerError
} else { } else {
err = models.ErrApiKeyNotFound err = apikey.ErrNotFound
} }
return response.Error(status, failedToDeleteMsg, err) return response.Error(status, failedToDeleteMsg, err)

@ -18,6 +18,7 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/apikey/apikeyimpl" "github.com/grafana/grafana/pkg/services/apikey/apikeyimpl"
"github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/serviceaccounts/database" "github.com/grafana/grafana/pkg/services/serviceaccounts/database"
@ -33,7 +34,7 @@ const (
serviceaccountIDTokensDetailPath = "/api/serviceaccounts/%v/tokens/%v" // #nosec G101 serviceaccountIDTokensDetailPath = "/api/serviceaccounts/%v/tokens/%v" // #nosec G101
) )
func createTokenforSA(t *testing.T, store serviceaccounts.Store, keyName string, orgID int64, saID int64, secondsToLive int64) *models.ApiKey { func createTokenforSA(t *testing.T, store serviceaccounts.Store, keyName string, orgID int64, saID int64, secondsToLive int64) *apikey.APIKey {
key, err := apikeygen.New(orgID, keyName) key, err := apikeygen.New(orgID, keyName)
require.NoError(t, err) require.NoError(t, err)
@ -42,7 +43,7 @@ func createTokenforSA(t *testing.T, store serviceaccounts.Store, keyName string,
OrgId: orgID, OrgId: orgID,
Key: key.HashedKey, Key: key.HashedKey,
SecondsToLive: secondsToLive, SecondsToLive: secondsToLive,
Result: &models.ApiKey{}, Result: &apikey.APIKey{},
} }
err = store.AddServiceAccountToken(context.Background(), saID, &cmd) err = store.AddServiceAccountToken(context.Background(), saID, &cmd)
@ -148,7 +149,7 @@ func TestServiceAccountsAPI_CreateToken(t *testing.T) {
if actualCode == http.StatusOK { if actualCode == http.StatusOK {
assert.Equal(t, tc.body["name"], actualBody["name"]) assert.Equal(t, tc.body["name"], actualBody["name"])
query := models.GetApiKeyByNameQuery{KeyName: tc.body["name"].(string), OrgId: sa.OrgID} query := apikey.GetByNameQuery{KeyName: tc.body["name"].(string), OrgId: sa.OrgID}
err = apiKeyService.GetApiKeyByName(context.Background(), &query) err = apiKeyService.GetApiKeyByName(context.Background(), &query)
require.NoError(t, err) require.NoError(t, err)
@ -245,7 +246,7 @@ func TestServiceAccountsAPI_DeleteToken(t *testing.T) {
_ = json.Unmarshal(actual.Body.Bytes(), &actualBody) _ = json.Unmarshal(actual.Body.Bytes(), &actualBody)
require.Equal(t, tc.expectedCode, actualCode, endpoint, actualBody) require.Equal(t, tc.expectedCode, actualCode, endpoint, actualBody)
query := models.GetApiKeyByNameQuery{KeyName: tc.keyName, OrgId: sa.OrgID} query := apikey.GetByNameQuery{KeyName: tc.keyName, OrgId: sa.OrgID}
err := apiKeyService.GetApiKeyByName(context.Background(), &query) err := apiKeyService.GetApiKeyByName(context.Background(), &query)
if actualCode == http.StatusOK { if actualCode == http.StatusOK {
require.Error(t, err) require.Error(t, err)
@ -258,10 +259,10 @@ func TestServiceAccountsAPI_DeleteToken(t *testing.T) {
type saStoreMockTokens struct { type saStoreMockTokens struct {
serviceaccounts.Store serviceaccounts.Store
saAPIKeys []*models.ApiKey saAPIKeys []*apikey.APIKey
} }
func (s *saStoreMockTokens) ListTokens(ctx context.Context, orgID, saID int64) ([]*models.ApiKey, error) { func (s *saStoreMockTokens) ListTokens(ctx context.Context, orgID, saID int64) ([]*apikey.APIKey, error) {
return s.saAPIKeys, nil return s.saAPIKeys, nil
} }
@ -272,7 +273,7 @@ func TestServiceAccountsAPI_ListTokens(t *testing.T) {
type testCreateSAToken struct { type testCreateSAToken struct {
desc string desc string
tokens []*models.ApiKey tokens []*apikey.APIKey
expectedHasExpired bool expectedHasExpired bool
expectedResponseBodyField string expectedResponseBodyField string
expectedCode int expectedCode int
@ -286,7 +287,7 @@ func TestServiceAccountsAPI_ListTokens(t *testing.T) {
testCases := []testCreateSAToken{ testCases := []testCreateSAToken{
{ {
desc: "should be able to list serviceaccount with no expiration date", desc: "should be able to list serviceaccount with no expiration date",
tokens: []*models.ApiKey{{ tokens: []*apikey.APIKey{{
Id: 1, Id: 1,
OrgId: 1, OrgId: 1,
ServiceAccountId: &saId, ServiceAccountId: &saId,
@ -306,7 +307,7 @@ func TestServiceAccountsAPI_ListTokens(t *testing.T) {
}, },
{ {
desc: "should be able to list serviceaccount with secondsUntilExpiration", desc: "should be able to list serviceaccount with secondsUntilExpiration",
tokens: []*models.ApiKey{{ tokens: []*apikey.APIKey{{
Id: 1, Id: 1,
OrgId: 1, OrgId: 1,
ServiceAccountId: &saId, ServiceAccountId: &saId,
@ -326,7 +327,7 @@ func TestServiceAccountsAPI_ListTokens(t *testing.T) {
}, },
{ {
desc: "should be able to list serviceaccount with expired token", desc: "should be able to list serviceaccount with expired token",
tokens: []*models.ApiKey{{ tokens: []*apikey.APIKey{{
Id: 1, Id: 1,
OrgId: 1, OrgId: 1,
ServiceAccountId: &saId, ServiceAccountId: &saId,

@ -438,7 +438,7 @@ func (s *ServiceAccountsStoreImpl) MigrateApiKey(ctx context.Context, orgId int6
return nil return nil
} }
func (s *ServiceAccountsStoreImpl) CreateServiceAccountFromApikey(ctx context.Context, key *models.ApiKey) error { func (s *ServiceAccountsStoreImpl) CreateServiceAccountFromApikey(ctx context.Context, key *apikey.APIKey) error {
prefix := "sa-autogen" prefix := "sa-autogen"
cmd := user.CreateUserCommand{ cmd := user.CreateUserCommand{
Login: fmt.Sprintf("%v-%v-%v", prefix, key.OrgId, key.Name), Login: fmt.Sprintf("%v-%v-%v", prefix, key.OrgId, key.Name),
@ -467,7 +467,7 @@ func (s *ServiceAccountsStoreImpl) CreateServiceAccountFromApikey(ctx context.Co
// RevertApiKey converts service account token to old API key // RevertApiKey converts service account token to old API key
func (s *ServiceAccountsStoreImpl) RevertApiKey(ctx context.Context, saId int64, keyId int64) error { func (s *ServiceAccountsStoreImpl) RevertApiKey(ctx context.Context, saId int64, keyId int64) error {
query := models.GetApiKeyByIdQuery{ApiKeyId: keyId} query := apikey.GetByIDQuery{ApiKeyId: keyId}
if err := s.apiKeyService.GetApiKeyById(ctx, &query); err != nil { if err := s.apiKeyService.GetApiKeyById(ctx, &query); err != nil {
return err return err
} }

@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/grafana/grafana/pkg/components/apikeygen" "github.com/grafana/grafana/pkg/components/apikeygen"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/serviceaccounts/tests" "github.com/grafana/grafana/pkg/services/serviceaccounts/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -27,7 +27,7 @@ func TestStore_UsageStats(t *testing.T) {
OrgId: sa.OrgID, OrgId: sa.OrgID,
Key: key.HashedKey, Key: key.HashedKey,
SecondsToLive: 0, SecondsToLive: 0,
Result: &models.ApiKey{}, Result: &apikey.APIKey{},
} }
err = store.AddServiceAccountToken(context.Background(), sa.ID, &cmd) err = store.AddServiceAccountToken(context.Background(), sa.ID, &cmd)

@ -5,13 +5,14 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"xorm.io/xorm" "xorm.io/xorm"
) )
func (s *ServiceAccountsStoreImpl) ListTokens(ctx context.Context, orgId int64, serviceAccountId int64) ([]*models.ApiKey, error) { func (s *ServiceAccountsStoreImpl) ListTokens(ctx context.Context, orgId int64, serviceAccountId int64) ([]*apikey.APIKey, error) {
result := make([]*models.ApiKey, 0) result := make([]*apikey.APIKey, 0)
err := s.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error { err := s.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
var sess *xorm.Session var sess *xorm.Session
@ -32,7 +33,7 @@ func (s *ServiceAccountsStoreImpl) AddServiceAccountToken(ctx context.Context, s
return err return err
} }
key := models.ApiKey{OrgId: cmd.OrgId, Name: cmd.Name} key := apikey.APIKey{OrgId: cmd.OrgId, Name: cmd.Name}
exists, _ := sess.Get(&key) exists, _ := sess.Get(&key)
if exists { if exists {
return ErrDuplicateToken return ErrDuplicateToken
@ -47,7 +48,7 @@ func (s *ServiceAccountsStoreImpl) AddServiceAccountToken(ctx context.Context, s
return ErrInvalidTokenExpiration return ErrInvalidTokenExpiration
} }
token := models.ApiKey{ token := apikey.APIKey{
OrgId: cmd.OrgId, OrgId: cmd.OrgId,
Name: cmd.Name, Name: cmd.Name,
Role: models.ROLE_VIEWER, Role: models.ROLE_VIEWER,
@ -86,7 +87,7 @@ func (s *ServiceAccountsStoreImpl) DeleteServiceAccountToken(ctx context.Context
// assignApiKeyToServiceAccount sets the API key service account ID // assignApiKeyToServiceAccount sets the API key service account ID
func (s *ServiceAccountsStoreImpl) assignApiKeyToServiceAccount(sess *sqlstore.DBSession, apiKeyId int64, serviceAccountId int64) error { func (s *ServiceAccountsStoreImpl) assignApiKeyToServiceAccount(sess *sqlstore.DBSession, apiKeyId int64, serviceAccountId int64) error {
key := models.ApiKey{Id: apiKeyId} key := apikey.APIKey{Id: apiKeyId}
exists, err := sess.Get(&key) exists, err := sess.Get(&key)
if err != nil { if err != nil {
s.log.Warn("API key not loaded", "err", err) s.log.Warn("API key not loaded", "err", err)
@ -94,7 +95,7 @@ func (s *ServiceAccountsStoreImpl) assignApiKeyToServiceAccount(sess *sqlstore.D
} }
if !exists { if !exists {
s.log.Warn("API key not found", "err", err) s.log.Warn("API key not found", "err", err)
return models.ErrApiKeyNotFound return apikey.ErrNotFound
} }
key.ServiceAccountId = &serviceAccountId key.ServiceAccountId = &serviceAccountId
@ -108,7 +109,7 @@ func (s *ServiceAccountsStoreImpl) assignApiKeyToServiceAccount(sess *sqlstore.D
// detachApiKeyFromServiceAccount converts service account token to old API key // detachApiKeyFromServiceAccount converts service account token to old API key
func (s *ServiceAccountsStoreImpl) detachApiKeyFromServiceAccount(sess *sqlstore.DBSession, apiKeyId int64) error { func (s *ServiceAccountsStoreImpl) detachApiKeyFromServiceAccount(sess *sqlstore.DBSession, apiKeyId int64) error {
key := models.ApiKey{Id: apiKeyId} key := apikey.APIKey{Id: apiKeyId}
exists, err := sess.Get(&key) exists, err := sess.Get(&key)
if err != nil { if err != nil {
s.log.Warn("Cannot get API key", "err", err) s.log.Warn("Cannot get API key", "err", err)
@ -116,7 +117,7 @@ func (s *ServiceAccountsStoreImpl) detachApiKeyFromServiceAccount(sess *sqlstore
} }
if !exists { if !exists {
s.log.Warn("API key not found", "err", err) s.log.Warn("API key not found", "err", err)
return models.ErrApiKeyNotFound return apikey.ErrNotFound
} }
key.ServiceAccountId = nil key.ServiceAccountId = nil

@ -5,7 +5,7 @@ import (
"testing" "testing"
"github.com/grafana/grafana/pkg/components/apikeygen" "github.com/grafana/grafana/pkg/components/apikeygen"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/serviceaccounts/tests" "github.com/grafana/grafana/pkg/services/serviceaccounts/tests"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -34,7 +34,7 @@ func TestStore_AddServiceAccountToken(t *testing.T) {
OrgId: user.OrgID, OrgId: user.OrgID,
Key: key.HashedKey, Key: key.HashedKey,
SecondsToLive: tc.secondsToLive, SecondsToLive: tc.secondsToLive,
Result: &models.ApiKey{}, Result: &apikey.APIKey{},
} }
err = store.AddServiceAccountToken(context.Background(), user.ID, &cmd) err = store.AddServiceAccountToken(context.Background(), user.ID, &cmd)
@ -84,7 +84,7 @@ func TestStore_AddServiceAccountToken_WrongServiceAccount(t *testing.T) {
OrgId: sa.OrgID, OrgId: sa.OrgID,
Key: key.HashedKey, Key: key.HashedKey,
SecondsToLive: 0, SecondsToLive: 0,
Result: &models.ApiKey{}, Result: &apikey.APIKey{},
} }
err = store.AddServiceAccountToken(context.Background(), sa.ID+1, &cmd) err = store.AddServiceAccountToken(context.Background(), sa.ID+1, &cmd)
@ -105,7 +105,7 @@ func TestStore_DeleteServiceAccountToken(t *testing.T) {
OrgId: sa.OrgID, OrgId: sa.OrgID,
Key: key.HashedKey, Key: key.HashedKey,
SecondsToLive: 0, SecondsToLive: 0,
Result: &models.ApiKey{}, Result: &apikey.APIKey{},
} }
err = store.AddServiceAccountToken(context.Background(), sa.ID, &cmd) err = store.AddServiceAccountToken(context.Background(), sa.ID, &cmd)

@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apikey"
) )
var ( var (
@ -68,7 +69,7 @@ type AddServiceAccountTokenCommand struct {
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
Key string `json:"-"` Key string `json:"-"`
SecondsToLive int64 `json:"secondsToLive"` SecondsToLive int64 `json:"secondsToLive"`
Result *models.ApiKey `json:"-"` Result *apikey.APIKey `json:"-"`
} }
// swagger: model // swagger: model

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/apikey"
) )
// this should reflect the api // this should reflect the api
@ -27,7 +28,7 @@ type Store interface {
MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) error MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) error
MigrateApiKey(ctx context.Context, orgID int64, keyId int64) error MigrateApiKey(ctx context.Context, orgID int64, keyId int64) error
RevertApiKey(ctx context.Context, saId int64, keyId int64) error RevertApiKey(ctx context.Context, saId int64, keyId int64) error
ListTokens(ctx context.Context, orgID int64, serviceAccount int64) ([]*models.ApiKey, error) ListTokens(ctx context.Context, orgID int64, serviceAccount int64) ([]*apikey.APIKey, error)
DeleteServiceAccountToken(ctx context.Context, orgID, serviceAccountID, tokenID int64) error DeleteServiceAccountToken(ctx context.Context, orgID, serviceAccountID, tokenID int64) error
AddServiceAccountToken(ctx context.Context, serviceAccountID int64, cmd *AddServiceAccountTokenCommand) error AddServiceAccountToken(ctx context.Context, serviceAccountID int64, cmd *AddServiceAccountTokenCommand) error
GetUsageMetrics(ctx context.Context) (map[string]interface{}, error) GetUsageMetrics(ctx context.Context) (map[string]interface{}, error)

@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/apikey/apikeyimpl" "github.com/grafana/grafana/pkg/services/apikey/apikeyimpl"
"github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
@ -45,13 +46,13 @@ func SetupUserServiceAccount(t *testing.T, sqlStore *sqlstore.SQLStore, testUser
return u1 return u1
} }
func SetupApiKey(t *testing.T, sqlStore *sqlstore.SQLStore, testKey TestApiKey) *models.ApiKey { func SetupApiKey(t *testing.T, sqlStore *sqlstore.SQLStore, testKey TestApiKey) *apikey.APIKey {
role := models.ROLE_VIEWER role := models.ROLE_VIEWER
if testKey.Role != "" { if testKey.Role != "" {
role = testKey.Role role = testKey.Role
} }
addKeyCmd := &models.AddApiKeyCommand{ addKeyCmd := &apikey.AddCommand{
Name: testKey.Name, Name: testKey.Name,
Role: role, Role: role,
OrgId: testKey.OrgId, OrgId: testKey.OrgId,
@ -71,7 +72,7 @@ func SetupApiKey(t *testing.T, sqlStore *sqlstore.SQLStore, testKey TestApiKey)
err := sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error { err := sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
// Force setting expires to time before now to make key expired // Force setting expires to time before now to make key expired
var expires int64 = 1 var expires int64 = 1
key := models.ApiKey{Expires: &expires} key := apikey.APIKey{Expires: &expires}
rowsAffected, err := sess.ID(addKeyCmd.Result.Id).Update(&key) rowsAffected, err := sess.ID(addKeyCmd.Result.Id).Update(&key)
require.Equal(t, int64(1), rowsAffected) require.Equal(t, int64(1), rowsAffected)
return err return err
@ -182,7 +183,7 @@ func (s *ServiceAccountsStoreMock) RevertApiKey(ctx context.Context, saId int64,
return nil return nil
} }
func (s *ServiceAccountsStoreMock) ListTokens(ctx context.Context, orgID int64, serviceAccount int64) ([]*models.ApiKey, error) { func (s *ServiceAccountsStoreMock) ListTokens(ctx context.Context, orgID int64, serviceAccount int64) ([]*apikey.APIKey, error) {
s.Calls.ListTokens = append(s.Calls.ListTokens, []interface{}{ctx, orgID, serviceAccount}) s.Calls.ListTokens = append(s.Calls.ListTokens, []interface{}{ctx, orgID, serviceAccount})
return nil, nil return nil, nil
} }

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
@ -39,7 +40,6 @@ type SQLStoreMock struct {
ExpectedNotifierUsageStats []*models.NotifierUsageStats ExpectedNotifierUsageStats []*models.NotifierUsageStats
ExpectedPersistedDashboards models.HitList ExpectedPersistedDashboards models.HitList
ExpectedSignedInUser *models.SignedInUser ExpectedSignedInUser *models.SignedInUser
ExpectedAPIKey *models.ApiKey
ExpectedUserStars map[int64]bool ExpectedUserStars map[int64]bool
ExpectedLoginAttempts int64 ExpectedLoginAttempts int64
@ -505,35 +505,6 @@ func (m *SQLStoreMock) GetOrCreateAlertNotificationState(ctx context.Context, cm
return m.ExpectedError return m.ExpectedError
} }
func (m *SQLStoreMock) GetAPIKeys(ctx context.Context, query *models.GetApiKeysQuery) error {
return m.ExpectedError
}
func (m *SQLStoreMock) GetAllAPIKeys(ctx context.Context, orgID int64) []*models.ApiKey {
return nil
}
func (m *SQLStoreMock) DeleteApiKey(ctx context.Context, cmd *models.DeleteApiKeyCommand) error {
return m.ExpectedError
}
func (m *SQLStoreMock) AddAPIKey(ctx context.Context, cmd *models.AddApiKeyCommand) error {
return m.ExpectedError
}
func (m *SQLStoreMock) GetApiKeyById(ctx context.Context, query *models.GetApiKeyByIdQuery) error {
return m.ExpectedError
}
func (m *SQLStoreMock) GetApiKeyByName(ctx context.Context, query *models.GetApiKeyByNameQuery) error {
query.Result = m.ExpectedAPIKey
return m.ExpectedError
}
func (m *SQLStoreMock) UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error {
return nil
}
func (m *SQLStoreMock) UpdateTempUserStatus(ctx context.Context, cmd *models.UpdateTempUserStatusCommand) error { func (m *SQLStoreMock) UpdateTempUserStatus(ctx context.Context, cmd *models.UpdateTempUserStatusCommand) error {
return m.ExpectedError return m.ExpectedError
} }
@ -575,6 +546,6 @@ func (m *SQLStoreMock) IsAdminOfTeams(ctx context.Context, query *models.IsAdmin
return m.ExpectedError return m.ExpectedError
} }
func (m *SQLStoreMock) GetAPIKeyByHash(ctx context.Context, hash string) (*models.ApiKey, error) { func (m *SQLStoreMock) GetAPIKeyByHash(ctx context.Context, hash string) (*apikey.APIKey, error) {
return nil, m.ExpectedError return nil, m.ExpectedError
} }

Loading…
Cancel
Save