mirror of
https://github.com/smallstep/certificates.git
synced 2024-11-17 15:29:21 +00:00
181 lines
5.0 KiB
Go
181 lines
5.0 KiB
Go
package admin
|
|
|
|
import (
|
|
"crypto/sha1"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/smallstep/certificates/authority/provisioner"
|
|
"github.com/smallstep/certificates/linkedca"
|
|
)
|
|
|
|
// DefaultAdminLimit is the default limit for listing provisioners.
|
|
const DefaultAdminLimit = 20
|
|
|
|
// DefaultAdminMax is the maximum limit for listing provisioners.
|
|
const DefaultAdminMax = 100
|
|
|
|
type uidAdmin struct {
|
|
admin *linkedca.Admin
|
|
uid string
|
|
}
|
|
|
|
type adminSlice []uidAdmin
|
|
|
|
func (p adminSlice) Len() int { return len(p) }
|
|
func (p adminSlice) Less(i, j int) bool { return p[i].uid < p[j].uid }
|
|
func (p adminSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
|
|
// Collection is a memory map of admins.
|
|
type Collection struct {
|
|
byID *sync.Map
|
|
bySubProv *sync.Map
|
|
byProv *sync.Map
|
|
sorted adminSlice
|
|
provisioners *provisioner.Collection
|
|
superCount int
|
|
superCountByProvisioner map[string]int
|
|
}
|
|
|
|
// NewCollection initializes a collection of provisioners. The given list of
|
|
// audiences are the audiences used by the JWT provisioner.
|
|
func NewCollection(provisioners *provisioner.Collection) *Collection {
|
|
return &Collection{
|
|
byID: new(sync.Map),
|
|
byProv: new(sync.Map),
|
|
bySubProv: new(sync.Map),
|
|
superCountByProvisioner: map[string]int{},
|
|
provisioners: provisioners,
|
|
}
|
|
}
|
|
|
|
// LoadByID a admin by the ID.
|
|
func (c *Collection) LoadByID(id string) (*linkedca.Admin, bool) {
|
|
return loadAdmin(c.byID, id)
|
|
}
|
|
|
|
func subProvNameHash(sub, provName string) string {
|
|
subHash := sha1.Sum([]byte(sub))
|
|
provNameHash := sha1.Sum([]byte(provName))
|
|
_res := sha1.Sum(append(subHash[:], provNameHash[:]...))
|
|
return string(_res[:])
|
|
}
|
|
|
|
// LoadBySubProv a admin by the subject and provisioner name.
|
|
func (c *Collection) LoadBySubProv(sub, provName string) (*linkedca.Admin, bool) {
|
|
return loadAdmin(c.bySubProv, subProvNameHash(sub, provName))
|
|
}
|
|
|
|
// LoadByProvisioner a admin by the subject and provisioner name.
|
|
func (c *Collection) LoadByProvisioner(provName string) ([]*linkedca.Admin, bool) {
|
|
a, ok := c.byProv.Load(provName)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
admins, ok := a.([]*linkedca.Admin)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
return admins, true
|
|
}
|
|
|
|
// Store adds an admin to the collection and enforces the uniqueness of
|
|
// admin IDs and amdin subject <-> provisioner name combos.
|
|
func (c *Collection) Store(adm *linkedca.Admin) error {
|
|
p, ok := c.provisioners.Load(adm.ProvisionerId)
|
|
if !ok {
|
|
return fmt.Errorf("provisioner %s not found", adm.ProvisionerId)
|
|
}
|
|
// Store admin always in byID. ID must be unique.
|
|
if _, loaded := c.byID.LoadOrStore(adm.Id, adm); loaded {
|
|
return errors.New("cannot add multiple admins with the same id")
|
|
}
|
|
|
|
provName := p.GetName()
|
|
// Store admin always in bySubProv. Subject <-> ProvisionerName must be unique.
|
|
if _, loaded := c.bySubProv.LoadOrStore(subProvNameHash(adm.Subject, provName), adm); loaded {
|
|
c.byID.Delete(adm.Id)
|
|
return errors.New("cannot add multiple admins with the same subject and provisioner")
|
|
}
|
|
|
|
if admins, ok := c.LoadByProvisioner(provName); ok {
|
|
c.byProv.Store(provName, append(admins, adm))
|
|
c.superCountByProvisioner[provName]++
|
|
} else {
|
|
c.byProv.Store(provName, []*linkedca.Admin{adm})
|
|
c.superCountByProvisioner[provName] = 1
|
|
}
|
|
c.superCount++
|
|
|
|
// Store sorted admins.
|
|
// Use the first 4 bytes (32bit) of the sum to insert the order
|
|
// Using big endian format to get the strings sorted:
|
|
// 0x00000000, 0x00000001, 0x00000002, ...
|
|
bi := make([]byte, 4)
|
|
_sum := sha1.Sum([]byte(adm.Id))
|
|
sum := _sum[:]
|
|
binary.BigEndian.PutUint32(bi, uint32(c.sorted.Len()))
|
|
sum[0], sum[1], sum[2], sum[3] = bi[0], bi[1], bi[2], bi[3]
|
|
c.sorted = append(c.sorted, uidAdmin{
|
|
admin: adm,
|
|
uid: hex.EncodeToString(sum),
|
|
})
|
|
sort.Sort(c.sorted)
|
|
|
|
return nil
|
|
}
|
|
|
|
// SuperCount returns the total number of admins.
|
|
func (c *Collection) SuperCount() int {
|
|
return c.superCount
|
|
}
|
|
|
|
// SuperCountByProvisioner returns the total number of admins.
|
|
func (c *Collection) SuperCountByProvisioner(provName string) int {
|
|
if cnt, ok := c.superCountByProvisioner[provName]; ok {
|
|
return cnt
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Find implements pagination on a list of sorted provisioners.
|
|
func (c *Collection) Find(cursor string, limit int) ([]*linkedca.Admin, string) {
|
|
switch {
|
|
case limit <= 0:
|
|
limit = DefaultAdminLimit
|
|
case limit > DefaultAdminMax:
|
|
limit = DefaultAdminMax
|
|
}
|
|
|
|
n := c.sorted.Len()
|
|
cursor = fmt.Sprintf("%040s", cursor)
|
|
i := sort.Search(n, func(i int) bool { return c.sorted[i].uid >= cursor })
|
|
|
|
slice := []*linkedca.Admin{}
|
|
for ; i < n && len(slice) < limit; i++ {
|
|
slice = append(slice, c.sorted[i].admin)
|
|
}
|
|
|
|
if i < n {
|
|
return slice, strings.TrimLeft(c.sorted[i].uid, "0")
|
|
}
|
|
return slice, ""
|
|
}
|
|
|
|
func loadAdmin(m *sync.Map, key string) (*linkedca.Admin, bool) {
|
|
a, ok := m.Load(key)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
adm, ok := a.(*linkedca.Admin)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
return adm, true
|
|
}
|