forked from TrueCloudLab/certificates
Add pagination to ACME EAB credentials endpoint
This commit is contained in:
parent
bc5f0e429b
commit
4d726d6b4c
4 changed files with 154 additions and 23 deletions
12
acme/db.go
12
acme/db.go
|
@ -21,7 +21,7 @@ type DB interface {
|
||||||
|
|
||||||
CreateExternalAccountKey(ctx context.Context, provisionerName, name string) (*ExternalAccountKey, error)
|
CreateExternalAccountKey(ctx context.Context, provisionerName, name string) (*ExternalAccountKey, error)
|
||||||
GetExternalAccountKey(ctx context.Context, provisionerName, keyID string) (*ExternalAccountKey, error)
|
GetExternalAccountKey(ctx context.Context, provisionerName, keyID string) (*ExternalAccountKey, error)
|
||||||
GetExternalAccountKeys(ctx context.Context, provisionerName string) ([]*ExternalAccountKey, error)
|
GetExternalAccountKeys(ctx context.Context, provisionerName, cursor string, limit int) ([]*ExternalAccountKey, string, error)
|
||||||
GetExternalAccountKeyByReference(ctx context.Context, provisionerName, reference string) (*ExternalAccountKey, error)
|
GetExternalAccountKeyByReference(ctx context.Context, provisionerName, reference string) (*ExternalAccountKey, error)
|
||||||
DeleteExternalAccountKey(ctx context.Context, provisionerName, keyID string) error
|
DeleteExternalAccountKey(ctx context.Context, provisionerName, keyID string) error
|
||||||
UpdateExternalAccountKey(ctx context.Context, provisionerName string, eak *ExternalAccountKey) error
|
UpdateExternalAccountKey(ctx context.Context, provisionerName string, eak *ExternalAccountKey) error
|
||||||
|
@ -56,7 +56,7 @@ type MockDB struct {
|
||||||
|
|
||||||
MockCreateExternalAccountKey func(ctx context.Context, provisionerName, name string) (*ExternalAccountKey, error)
|
MockCreateExternalAccountKey func(ctx context.Context, provisionerName, name string) (*ExternalAccountKey, error)
|
||||||
MockGetExternalAccountKey func(ctx context.Context, provisionerName, keyID string) (*ExternalAccountKey, error)
|
MockGetExternalAccountKey func(ctx context.Context, provisionerName, keyID string) (*ExternalAccountKey, error)
|
||||||
MockGetExternalAccountKeys func(ctx context.Context, provisionerName string) ([]*ExternalAccountKey, error)
|
MockGetExternalAccountKeys func(ctx context.Context, provisionerName string, cursor string, limit int) ([]*ExternalAccountKey, string, error)
|
||||||
MockGetExternalAccountKeyByReference func(ctx context.Context, provisionerName, reference string) (*ExternalAccountKey, error)
|
MockGetExternalAccountKeyByReference func(ctx context.Context, provisionerName, reference string) (*ExternalAccountKey, error)
|
||||||
MockDeleteExternalAccountKey func(ctx context.Context, provisionerName, keyID string) error
|
MockDeleteExternalAccountKey func(ctx context.Context, provisionerName, keyID string) error
|
||||||
MockUpdateExternalAccountKey func(ctx context.Context, provisionerName string, eak *ExternalAccountKey) error
|
MockUpdateExternalAccountKey func(ctx context.Context, provisionerName string, eak *ExternalAccountKey) error
|
||||||
|
@ -145,13 +145,13 @@ func (m *MockDB) GetExternalAccountKey(ctx context.Context, provisionerName, key
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExternalAccountKeys mock
|
// GetExternalAccountKeys mock
|
||||||
func (m *MockDB) GetExternalAccountKeys(ctx context.Context, provisionerName string) ([]*ExternalAccountKey, error) {
|
func (m *MockDB) GetExternalAccountKeys(ctx context.Context, provisionerName, cursor string, limit int) ([]*ExternalAccountKey, string, error) {
|
||||||
if m.MockGetExternalAccountKeys != nil {
|
if m.MockGetExternalAccountKeys != nil {
|
||||||
return m.MockGetExternalAccountKeys(ctx, provisionerName)
|
return m.MockGetExternalAccountKeys(ctx, provisionerName, cursor, limit)
|
||||||
} else if m.MockError != nil {
|
} else if m.MockError != nil {
|
||||||
return nil, m.MockError
|
return nil, "", m.MockError
|
||||||
}
|
}
|
||||||
return m.MockRet1.([]*ExternalAccountKey), m.MockError
|
return m.MockRet1.([]*ExternalAccountKey), "", m.MockError
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExternalAccountKeyByReference mock
|
// GetExternalAccountKeyByReference mock
|
||||||
|
|
|
@ -259,21 +259,42 @@ func (db *DB) DeleteExternalAccountKey(ctx context.Context, provisionerName, key
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExternalAccountKeys retrieves all External Account Binding keys for a provisioner
|
// GetExternalAccountKeys retrieves all External Account Binding keys for a provisioner
|
||||||
func (db *DB) GetExternalAccountKeys(ctx context.Context, provisionerName string) ([]*acme.ExternalAccountKey, error) {
|
func (db *DB) GetExternalAccountKeys(ctx context.Context, provisionerName, cursor string, limit int) ([]*acme.ExternalAccountKey, string, error) {
|
||||||
entries, err := db.db.List(externalAccountKeyTable)
|
entries, err := db.db.List(externalAccountKeyTable)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set sane limits; based on the Admin API limits
|
||||||
|
switch {
|
||||||
|
case limit <= 0:
|
||||||
|
limit = 20
|
||||||
|
case limit > 100:
|
||||||
|
limit = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
foundCursorKey := false
|
||||||
keys := []*acme.ExternalAccountKey{}
|
keys := []*acme.ExternalAccountKey{}
|
||||||
for _, entry := range entries {
|
for _, entry := range entries { // entries is sorted alphabetically on the key (ID) of the EAK; no need to sort this again.
|
||||||
dbeak := new(dbExternalAccountKey)
|
dbeak := new(dbExternalAccountKey)
|
||||||
if err = json.Unmarshal(entry.Value, dbeak); err != nil {
|
if err = json.Unmarshal(entry.Value, dbeak); err != nil {
|
||||||
return nil, errors.Wrapf(err, "error unmarshaling external account key %s into ExternalAccountKey", string(entry.Key))
|
return nil, "", errors.Wrapf(err, "error unmarshaling external account key %s into ExternalAccountKey", string(entry.Key))
|
||||||
}
|
}
|
||||||
if dbeak.Provisioner != provisionerName {
|
if dbeak.Provisioner != provisionerName {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// skip the IDs not matching the cursor to look for in the sorted list.
|
||||||
|
if cursor != "" && !foundCursorKey && cursor != dbeak.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// look for the entry pointed to by the cursor (the next item to return), to start selecting items
|
||||||
|
if cursor != "" && !foundCursorKey && cursor == dbeak.ID {
|
||||||
|
foundCursorKey = true
|
||||||
|
}
|
||||||
|
// return if the limit of items was found in the previous iteration; the next cursor is set to the next item to return
|
||||||
|
if len(keys) == limit {
|
||||||
|
return keys, dbeak.ID, nil
|
||||||
|
}
|
||||||
keys = append(keys, &acme.ExternalAccountKey{
|
keys = append(keys, &acme.ExternalAccountKey{
|
||||||
ID: dbeak.ID,
|
ID: dbeak.ID,
|
||||||
KeyBytes: dbeak.KeyBytes,
|
KeyBytes: dbeak.KeyBytes,
|
||||||
|
@ -285,7 +306,7 @@ func (db *DB) GetExternalAccountKeys(ctx context.Context, provisionerName string
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return keys, nil
|
return keys, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExternalAccountKeyByReference retrieves an External Account Binding key with unique reference
|
// GetExternalAccountKeyByReference retrieves an External Account Binding key with unique reference
|
||||||
|
|
|
@ -1085,13 +1085,17 @@ func TestDB_GetExternalAccountKeys(t *testing.T) {
|
||||||
keyID1 := "keyID1"
|
keyID1 := "keyID1"
|
||||||
keyID2 := "keyID2"
|
keyID2 := "keyID2"
|
||||||
keyID3 := "keyID3"
|
keyID3 := "keyID3"
|
||||||
|
keyID4 := "keyID4"
|
||||||
prov := "acmeProv"
|
prov := "acmeProv"
|
||||||
ref := "ref"
|
ref := "ref"
|
||||||
type test struct {
|
type test struct {
|
||||||
db nosql.DB
|
db nosql.DB
|
||||||
err error
|
err error
|
||||||
acmeErr *acme.Error
|
cursor string
|
||||||
eaks []*acme.ExternalAccountKey
|
nextCursor string
|
||||||
|
limit int
|
||||||
|
acmeErr *acme.Error
|
||||||
|
eaks []*acme.ExternalAccountKey
|
||||||
}
|
}
|
||||||
var tests = map[string]func(t *testing.T) test{
|
var tests = map[string]func(t *testing.T) test{
|
||||||
"ok": func(t *testing.T) test {
|
"ok": func(t *testing.T) test {
|
||||||
|
@ -1169,6 +1173,103 @@ func TestDB_GetExternalAccountKeys(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ok/paging-single-entry": func(t *testing.T) test {
|
||||||
|
now := clock.Now()
|
||||||
|
dbeak1 := &dbExternalAccountKey{
|
||||||
|
ID: keyID1,
|
||||||
|
Provisioner: prov,
|
||||||
|
Reference: ref,
|
||||||
|
AccountID: "",
|
||||||
|
KeyBytes: []byte{1, 3, 3, 7},
|
||||||
|
CreatedAt: now,
|
||||||
|
}
|
||||||
|
b1, err := json.Marshal(dbeak1)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
dbeak2 := &dbExternalAccountKey{
|
||||||
|
ID: keyID2,
|
||||||
|
Provisioner: prov,
|
||||||
|
Reference: ref,
|
||||||
|
AccountID: "",
|
||||||
|
KeyBytes: []byte{1, 3, 3, 7},
|
||||||
|
CreatedAt: now,
|
||||||
|
}
|
||||||
|
b2, err := json.Marshal(dbeak2)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
dbeak3 := &dbExternalAccountKey{
|
||||||
|
ID: keyID3,
|
||||||
|
Provisioner: "differentProvisioner",
|
||||||
|
Reference: ref,
|
||||||
|
AccountID: "",
|
||||||
|
KeyBytes: []byte{1, 3, 3, 7},
|
||||||
|
CreatedAt: now,
|
||||||
|
}
|
||||||
|
b3, err := json.Marshal(dbeak3)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
dbeak4 := &dbExternalAccountKey{
|
||||||
|
ID: keyID4,
|
||||||
|
Provisioner: prov,
|
||||||
|
Reference: ref,
|
||||||
|
AccountID: "",
|
||||||
|
KeyBytes: []byte{1, 3, 3, 7},
|
||||||
|
CreatedAt: now,
|
||||||
|
}
|
||||||
|
b4, err := json.Marshal(dbeak4)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
return test{
|
||||||
|
db: &db.MockNoSQLDB{
|
||||||
|
MList: func(bucket []byte) ([]*nosqldb.Entry, error) {
|
||||||
|
assert.Equals(t, bucket, externalAccountKeyTable)
|
||||||
|
return []*nosqldb.Entry{
|
||||||
|
{
|
||||||
|
Bucket: bucket,
|
||||||
|
Key: []byte(keyID1),
|
||||||
|
Value: b1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Bucket: bucket,
|
||||||
|
Key: []byte(keyID2),
|
||||||
|
Value: b2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Bucket: bucket,
|
||||||
|
Key: []byte(keyID3),
|
||||||
|
Value: b3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Bucket: bucket,
|
||||||
|
Key: []byte(keyID4),
|
||||||
|
Value: b4,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cursor: keyID2,
|
||||||
|
limit: 1,
|
||||||
|
nextCursor: keyID4,
|
||||||
|
eaks: []*acme.ExternalAccountKey{
|
||||||
|
{
|
||||||
|
ID: keyID2,
|
||||||
|
Provisioner: prov,
|
||||||
|
Reference: ref,
|
||||||
|
AccountID: "",
|
||||||
|
KeyBytes: []byte{1, 3, 3, 7},
|
||||||
|
CreatedAt: now,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ok/paging-max-limit": func(t *testing.T) test {
|
||||||
|
return test{
|
||||||
|
db: &db.MockNoSQLDB{
|
||||||
|
MList: func(bucket []byte) ([]*nosqldb.Entry, error) {
|
||||||
|
assert.Equals(t, bucket, externalAccountKeyTable)
|
||||||
|
return []*nosqldb.Entry{}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
limit: 1337,
|
||||||
|
eaks: []*acme.ExternalAccountKey{},
|
||||||
|
}
|
||||||
|
},
|
||||||
"fail/db.List-error": func(t *testing.T) test {
|
"fail/db.List-error": func(t *testing.T) test {
|
||||||
return test{
|
return test{
|
||||||
db: &db.MockNoSQLDB{
|
db: &db.MockNoSQLDB{
|
||||||
|
@ -1203,7 +1304,7 @@ func TestDB_GetExternalAccountKeys(t *testing.T) {
|
||||||
tc := run(t)
|
tc := run(t)
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
d := DB{db: tc.db}
|
d := DB{db: tc.db}
|
||||||
if eaks, err := d.GetExternalAccountKeys(context.Background(), prov); err != nil {
|
if eaks, nextCursor, err := d.GetExternalAccountKeys(context.Background(), prov, tc.cursor, tc.limit); err != nil {
|
||||||
switch k := err.(type) {
|
switch k := err.(type) {
|
||||||
case *acme.Error:
|
case *acme.Error:
|
||||||
if assert.NotNil(t, tc.acmeErr) {
|
if assert.NotNil(t, tc.acmeErr) {
|
||||||
|
@ -1229,6 +1330,7 @@ func TestDB_GetExternalAccountKeys(t *testing.T) {
|
||||||
assert.Equals(t, eak.AccountID, tc.eaks[i].AccountID)
|
assert.Equals(t, eak.AccountID, tc.eaks[i].AccountID)
|
||||||
assert.Equals(t, eak.BoundAt, tc.eaks[i].BoundAt)
|
assert.Equals(t, eak.BoundAt, tc.eaks[i].BoundAt)
|
||||||
}
|
}
|
||||||
|
assert.Equals(t, nextCursor, tc.nextCursor)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,13 +146,22 @@ func (h *Handler) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request)
|
||||||
prov := chi.URLParam(r, "prov")
|
prov := chi.URLParam(r, "prov")
|
||||||
reference := chi.URLParam(r, "ref")
|
reference := chi.URLParam(r, "ref")
|
||||||
|
|
||||||
// TODO: support paging? It'll probably leak to the DB layer, as we have to loop through all keys
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
key *acme.ExternalAccountKey
|
key *acme.ExternalAccountKey
|
||||||
keys []*acme.ExternalAccountKey
|
keys []*acme.ExternalAccountKey
|
||||||
err error
|
err error
|
||||||
|
cursor string
|
||||||
|
nextCursor string
|
||||||
|
limit int
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cursor, limit, err = api.ParseCursor(r)
|
||||||
|
if err != nil {
|
||||||
|
api.WriteError(w, admin.WrapError(admin.ErrorBadRequestType, err,
|
||||||
|
"error parsing cursor and limit from query params"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if reference != "" {
|
if reference != "" {
|
||||||
key, err = h.acmeDB.GetExternalAccountKeyByReference(r.Context(), prov, reference)
|
key, err = h.acmeDB.GetExternalAccountKeyByReference(r.Context(), prov, reference)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -161,7 +170,7 @@ func (h *Handler) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
keys = []*acme.ExternalAccountKey{key}
|
keys = []*acme.ExternalAccountKey{key}
|
||||||
} else {
|
} else {
|
||||||
keys, err = h.acmeDB.GetExternalAccountKeys(r.Context(), prov)
|
keys, nextCursor, err = h.acmeDB.GetExternalAccountKeys(r.Context(), prov, cursor, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.WriteError(w, admin.WrapErrorISE(err, "error getting external account keys"))
|
api.WriteError(w, admin.WrapErrorISE(err, "error getting external account keys"))
|
||||||
return
|
return
|
||||||
|
@ -181,7 +190,6 @@ func (h *Handler) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextCursor := ""
|
|
||||||
api.JSON(w, &GetExternalAccountKeysResponse{
|
api.JSON(w, &GetExternalAccountKeysResponse{
|
||||||
EAKs: eaks,
|
EAKs: eaks,
|
||||||
NextCursor: nextCursor,
|
NextCursor: nextCursor,
|
||||||
|
|
Loading…
Reference in a new issue