2022-08-02 09:55:19 -05:00
|
|
|
package apikeyimpl
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
2022-10-19 08:02:15 -05:00
|
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
2022-08-02 09:55:19 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
2022-08-04 07:19:09 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/apikey"
|
2022-08-10 04:56:48 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/user"
|
2022-10-19 08:02:15 -05:00
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
2022-08-02 09:55:19 -05:00
|
|
|
)
|
|
|
|
|
2022-10-19 08:02:15 -05:00
|
|
|
type getStore func(db.DB, *setting.Cfg) store
|
2022-08-23 10:01:35 -05:00
|
|
|
|
|
|
|
type getApiKeysTestCase struct {
|
|
|
|
desc string
|
|
|
|
user *user.SignedInUser
|
|
|
|
expectedNumKeys int
|
|
|
|
expectedAllNumKeys int
|
|
|
|
}
|
|
|
|
|
2022-08-02 09:55:19 -05:00
|
|
|
func mockTimeNow() {
|
|
|
|
var timeSeed int64
|
|
|
|
timeNow = func() time.Time {
|
|
|
|
loc := time.FixedZone("MockZoneUTC-5", -5*60*60)
|
|
|
|
fakeNow := time.Unix(timeSeed, 0).In(loc)
|
|
|
|
timeSeed++
|
|
|
|
return fakeNow
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func resetTimeNow() {
|
|
|
|
timeNow = time.Now
|
|
|
|
}
|
|
|
|
|
2022-08-23 10:01:35 -05:00
|
|
|
func seedApiKeys(t *testing.T, store store, num int) {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
for i := 0; i < num; i++ {
|
|
|
|
err := store.AddAPIKey(context.Background(), &apikey.AddCommand{
|
|
|
|
Name: fmt.Sprintf("key:%d", i),
|
|
|
|
Key: fmt.Sprintf("key:%d", i),
|
|
|
|
OrgId: 1,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func testIntegrationApiKeyDataAccess(t *testing.T, fn getStore) {
|
2022-11-04 09:14:21 -05:00
|
|
|
t.Helper()
|
|
|
|
|
2022-08-02 09:55:19 -05:00
|
|
|
mockTimeNow()
|
|
|
|
defer resetTimeNow()
|
|
|
|
|
|
|
|
t.Run("Testing API Key data access", func(t *testing.T) {
|
2022-10-19 08:02:15 -05:00
|
|
|
db := db.InitTestDB(t)
|
|
|
|
ss := fn(db, db.Cfg)
|
2022-08-02 09:55:19 -05:00
|
|
|
|
|
|
|
t.Run("Given saved api key", func(t *testing.T) {
|
2022-08-04 07:19:09 -05:00
|
|
|
cmd := apikey.AddCommand{OrgId: 1, Name: "hello", Key: "asd"}
|
2022-08-02 09:55:19 -05:00
|
|
|
err := ss.AddAPIKey(context.Background(), &cmd)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
t.Run("Should be able to get key by name", func(t *testing.T) {
|
2022-08-04 07:19:09 -05:00
|
|
|
query := apikey.GetByNameQuery{KeyName: "hello", OrgId: 1}
|
2022-08-02 09:55:19 -05:00
|
|
|
err = ss.GetApiKeyByName(context.Background(), &query)
|
|
|
|
|
|
|
|
assert.Nil(t, err)
|
|
|
|
assert.NotNil(t, query.Result)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Should be able to get key by hash", func(t *testing.T) {
|
|
|
|
key, err := ss.GetAPIKeyByHash(context.Background(), cmd.Key)
|
|
|
|
|
|
|
|
assert.Nil(t, err)
|
|
|
|
assert.NotNil(t, key)
|
|
|
|
})
|
2022-08-23 10:01:35 -05:00
|
|
|
t.Run("Should be able to delete key by id", func(t *testing.T) {
|
|
|
|
key, err := ss.GetAPIKeyByHash(context.Background(), cmd.Key)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
err = ss.DeleteApiKey(context.Background(), &apikey.DeleteCommand{Id: key.Id, OrgId: key.OrgId})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
})
|
2022-08-02 09:55:19 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Add non expiring key", func(t *testing.T) {
|
2022-08-04 07:19:09 -05:00
|
|
|
cmd := apikey.AddCommand{OrgId: 1, Name: "non-expiring", Key: "asd1", SecondsToLive: 0}
|
2022-08-02 09:55:19 -05:00
|
|
|
err := ss.AddAPIKey(context.Background(), &cmd)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
2022-08-04 07:19:09 -05:00
|
|
|
query := apikey.GetByNameQuery{KeyName: "non-expiring", OrgId: 1}
|
2022-08-02 09:55:19 -05:00
|
|
|
err = ss.GetApiKeyByName(context.Background(), &query)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
assert.Nil(t, query.Result.Expires)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Add an expiring key", func(t *testing.T) {
|
|
|
|
// expires in one hour
|
2022-08-04 07:19:09 -05:00
|
|
|
cmd := apikey.AddCommand{OrgId: 1, Name: "expiring-in-an-hour", Key: "asd2", SecondsToLive: 3600}
|
2022-08-02 09:55:19 -05:00
|
|
|
err := ss.AddAPIKey(context.Background(), &cmd)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
2022-08-04 07:19:09 -05:00
|
|
|
query := apikey.GetByNameQuery{KeyName: "expiring-in-an-hour", OrgId: 1}
|
2022-08-02 09:55:19 -05:00
|
|
|
err = ss.GetApiKeyByName(context.Background(), &query)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
assert.True(t, *query.Result.Expires >= timeNow().Unix())
|
|
|
|
|
|
|
|
// timeNow() has been called twice since creation; once by AddAPIKey and once by GetApiKeyByName
|
|
|
|
// therefore two seconds should be subtracted by next value returned by timeNow()
|
|
|
|
// that equals the number by which timeSeed has been advanced
|
|
|
|
then := timeNow().Add(-2 * time.Second)
|
|
|
|
expected := then.Add(1 * time.Hour).UTC().Unix()
|
|
|
|
assert.Equal(t, *query.Result.Expires, expected)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Last Used At datetime update", func(t *testing.T) {
|
|
|
|
// expires in one hour
|
2022-08-04 07:19:09 -05:00
|
|
|
cmd := apikey.AddCommand{OrgId: 1, Name: "last-update-at", Key: "asd3", SecondsToLive: 3600}
|
2022-08-02 09:55:19 -05:00
|
|
|
err := ss.AddAPIKey(context.Background(), &cmd)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Nil(t, cmd.Result.LastUsedAt)
|
|
|
|
|
|
|
|
err = ss.UpdateAPIKeyLastUsedDate(context.Background(), cmd.Result.Id)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-08-04 07:19:09 -05:00
|
|
|
query := apikey.GetByNameQuery{KeyName: "last-update-at", OrgId: 1}
|
2022-08-02 09:55:19 -05:00
|
|
|
err = ss.GetApiKeyByName(context.Background(), &query)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
assert.NotNil(t, query.Result.LastUsedAt)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Add a key with negative lifespan", func(t *testing.T) {
|
|
|
|
// expires in one day
|
2022-08-04 07:19:09 -05:00
|
|
|
cmd := apikey.AddCommand{OrgId: 1, Name: "key-with-negative-lifespan", Key: "asd3", SecondsToLive: -3600}
|
2022-08-02 09:55:19 -05:00
|
|
|
err := ss.AddAPIKey(context.Background(), &cmd)
|
2022-08-04 07:19:09 -05:00
|
|
|
assert.EqualError(t, err, apikey.ErrInvalidExpiration.Error())
|
2022-08-02 09:55:19 -05:00
|
|
|
|
2022-08-04 07:19:09 -05:00
|
|
|
query := apikey.GetByNameQuery{KeyName: "key-with-negative-lifespan", OrgId: 1}
|
2022-08-02 09:55:19 -05:00
|
|
|
err = ss.GetApiKeyByName(context.Background(), &query)
|
|
|
|
assert.EqualError(t, err, "invalid API key")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Add keys", func(t *testing.T) {
|
|
|
|
// never expires
|
2022-08-04 07:19:09 -05:00
|
|
|
cmd := apikey.AddCommand{OrgId: 1, Name: "key1", Key: "key1", SecondsToLive: 0}
|
2022-08-02 09:55:19 -05:00
|
|
|
err := ss.AddAPIKey(context.Background(), &cmd)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
// expires in 1s
|
2022-08-04 07:19:09 -05:00
|
|
|
cmd = apikey.AddCommand{OrgId: 1, Name: "key2", Key: "key2", SecondsToLive: 1}
|
2022-08-02 09:55:19 -05:00
|
|
|
err = ss.AddAPIKey(context.Background(), &cmd)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
// expires in one hour
|
2022-08-04 07:19:09 -05:00
|
|
|
cmd = apikey.AddCommand{OrgId: 1, Name: "key3", Key: "key3", SecondsToLive: 3600}
|
2022-08-02 09:55:19 -05:00
|
|
|
err = ss.AddAPIKey(context.Background(), &cmd)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
// advance mocked getTime by 1s
|
|
|
|
timeNow()
|
|
|
|
|
2022-08-10 04:56:48 -05:00
|
|
|
testUser := &user.SignedInUser{
|
2022-08-11 06:28:55 -05:00
|
|
|
OrgID: 1,
|
2022-08-02 09:55:19 -05:00
|
|
|
Permissions: map[int64]map[string][]string{
|
|
|
|
1: {accesscontrol.ActionAPIKeyRead: []string{accesscontrol.ScopeAPIKeysAll}},
|
|
|
|
},
|
|
|
|
}
|
2022-08-04 07:19:09 -05:00
|
|
|
query := apikey.GetApiKeysQuery{OrgId: 1, IncludeExpired: false, User: testUser}
|
2022-08-02 09:55:19 -05:00
|
|
|
err = ss.GetAPIKeys(context.Background(), &query)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
for _, k := range query.Result {
|
|
|
|
if k.Name == "key2" {
|
|
|
|
t.Fatalf("key2 should not be there")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-04 07:19:09 -05:00
|
|
|
query = apikey.GetApiKeysQuery{OrgId: 1, IncludeExpired: true, User: testUser}
|
2022-08-02 09:55:19 -05:00
|
|
|
err = ss.GetAPIKeys(context.Background(), &query)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
found := false
|
|
|
|
for _, k := range query.Result {
|
|
|
|
if k.Name == "key2" {
|
|
|
|
found = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.True(t, found)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Testing API Key errors", func(t *testing.T) {
|
2022-10-19 08:02:15 -05:00
|
|
|
db := db.InitTestDB(t)
|
|
|
|
ss := fn(db, db.Cfg)
|
2022-08-02 09:55:19 -05:00
|
|
|
|
|
|
|
t.Run("Delete non-existing key should return error", func(t *testing.T) {
|
2022-08-04 07:19:09 -05:00
|
|
|
cmd := apikey.DeleteCommand{Id: 1}
|
2022-08-02 09:55:19 -05:00
|
|
|
err := ss.DeleteApiKey(context.Background(), &cmd)
|
|
|
|
|
2022-08-04 07:19:09 -05:00
|
|
|
assert.EqualError(t, err, apikey.ErrNotFound.Error())
|
2022-08-02 09:55:19 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Testing API Duplicate Key Errors", func(t *testing.T) {
|
|
|
|
t.Run("Given saved api key", func(t *testing.T) {
|
2022-08-04 07:19:09 -05:00
|
|
|
cmd := apikey.AddCommand{OrgId: 0, Name: "duplicate", Key: "asd"}
|
2022-08-02 09:55:19 -05:00
|
|
|
err := ss.AddAPIKey(context.Background(), &cmd)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
t.Run("Add API Key with existing Org ID and Name", func(t *testing.T) {
|
2022-08-04 07:19:09 -05:00
|
|
|
cmd := apikey.AddCommand{OrgId: 0, Name: "duplicate", Key: "asd"}
|
2022-08-02 09:55:19 -05:00
|
|
|
err = ss.AddAPIKey(context.Background(), &cmd)
|
2022-08-04 07:19:09 -05:00
|
|
|
assert.EqualError(t, err, apikey.ErrDuplicate.Error())
|
2022-08-02 09:55:19 -05:00
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2022-08-23 10:01:35 -05:00
|
|
|
t.Run("Testing Get API keys", func(t *testing.T) {
|
|
|
|
tests := []getApiKeysTestCase{
|
|
|
|
{
|
|
|
|
desc: "expect all keys for wildcard scope",
|
|
|
|
user: &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
|
|
|
|
1: {"apikeys:read": {"apikeys:*"}},
|
|
|
|
}},
|
|
|
|
expectedNumKeys: 10,
|
|
|
|
expectedAllNumKeys: 10,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "expect only api keys that user have scopes for",
|
|
|
|
user: &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
|
|
|
|
1: {"apikeys:read": {"apikeys:id:1", "apikeys:id:3"}},
|
|
|
|
}},
|
|
|
|
expectedNumKeys: 2,
|
|
|
|
expectedAllNumKeys: 10,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "expect no keys when user have no scopes",
|
|
|
|
user: &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
|
|
|
|
1: {"apikeys:read": {}},
|
|
|
|
}},
|
|
|
|
expectedNumKeys: 0,
|
|
|
|
expectedAllNumKeys: 10,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.desc, func(t *testing.T) {
|
2022-10-19 08:02:15 -05:00
|
|
|
db := db.InitTestDB(t, db.InitTestDBOpt{})
|
|
|
|
store := fn(db, db.Cfg)
|
2022-08-23 10:01:35 -05:00
|
|
|
seedApiKeys(t, store, 10)
|
|
|
|
|
|
|
|
query := &apikey.GetApiKeysQuery{OrgId: 1, User: tt.user}
|
|
|
|
err := store.GetAPIKeys(context.Background(), query)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Len(t, query.Result, tt.expectedNumKeys)
|
|
|
|
|
|
|
|
res, err := store.GetAllAPIKeys(context.Background(), 1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, tt.expectedAllNumKeys, len(res))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
2022-08-02 09:55:19 -05:00
|
|
|
}
|