1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
|
package nosql
import (
"context"
"encoding/json"
"time"
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority/admin"
"github.com/smallstep/nosql"
"go.step.sm/linkedca"
"google.golang.org/protobuf/types/known/timestamppb"
)
// dbAdmin is the database representation of the Admin type.
type dbAdmin struct {
ID string `json:"id"`
AuthorityID string `json:"authorityID"`
ProvisionerID string `json:"provisionerID"`
Subject string `json:"subject"`
Type linkedca.Admin_Type `json:"type"`
CreatedAt time.Time `json:"createdAt"`
DeletedAt time.Time `json:"deletedAt"`
}
func (dba *dbAdmin) convert() *linkedca.Admin {
return &linkedca.Admin{
Id: dba.ID,
AuthorityId: dba.AuthorityID,
ProvisionerId: dba.ProvisionerID,
Subject: dba.Subject,
Type: dba.Type,
CreatedAt: timestamppb.New(dba.CreatedAt),
DeletedAt: timestamppb.New(dba.DeletedAt),
}
}
func (dba *dbAdmin) clone() *dbAdmin {
u := *dba
return &u
}
func (db *DB) getDBAdminBytes(ctx context.Context, id string) ([]byte, error) {
data, err := db.db.Get(adminsTable, []byte(id))
if nosql.IsErrNotFound(err) {
return nil, admin.NewError(admin.ErrorNotFoundType, "admin %s not found", id)
} else if err != nil {
return nil, errors.Wrapf(err, "error loading admin %s", id)
}
return data, nil
}
func (db *DB) unmarshalDBAdmin(data []byte, id string) (*dbAdmin, error) {
var dba = new(dbAdmin)
if err := json.Unmarshal(data, dba); err != nil {
return nil, errors.Wrapf(err, "error unmarshaling admin %s into dbAdmin", id)
}
if !dba.DeletedAt.IsZero() {
return nil, admin.NewError(admin.ErrorDeletedType, "admin %s is deleted", id)
}
if dba.AuthorityID != db.authorityID {
return nil, admin.NewError(admin.ErrorAuthorityMismatchType,
"admin %s is not owned by authority %s", dba.ID, db.authorityID)
}
return dba, nil
}
func (db *DB) getDBAdmin(ctx context.Context, id string) (*dbAdmin, error) {
data, err := db.getDBAdminBytes(ctx, id)
if err != nil {
return nil, err
}
dba, err := db.unmarshalDBAdmin(data, id)
if err != nil {
return nil, err
}
return dba, nil
}
func (db *DB) unmarshalAdmin(data []byte, id string) (*linkedca.Admin, error) {
dba, err := db.unmarshalDBAdmin(data, id)
if err != nil {
return nil, err
}
return dba.convert(), nil
}
// GetAdmin retrieves and unmarshals a admin from the database.
func (db *DB) GetAdmin(ctx context.Context, id string) (*linkedca.Admin, error) {
data, err := db.getDBAdminBytes(ctx, id)
if err != nil {
return nil, err
}
adm, err := db.unmarshalAdmin(data, id)
if err != nil {
return nil, err
}
return adm, nil
}
// GetAdmins retrieves and unmarshals all active (not deleted) admins
// from the database.
// TODO should we be paginating?
func (db *DB) GetAdmins(ctx context.Context) ([]*linkedca.Admin, error) {
dbEntries, err := db.db.List(adminsTable)
if err != nil {
return nil, errors.Wrap(err, "error loading admins")
}
var admins = []*linkedca.Admin{}
for _, entry := range dbEntries {
adm, err := db.unmarshalAdmin(entry.Value, string(entry.Key))
if err != nil {
switch k := err.(type) {
case *admin.Error:
if k.IsType(admin.ErrorDeletedType) || k.IsType(admin.ErrorAuthorityMismatchType) {
continue
} else {
return nil, err
}
default:
return nil, err
}
}
if adm.AuthorityId != db.authorityID {
continue
}
admins = append(admins, adm)
}
return admins, nil
}
// CreateAdmin stores a new admin to the database.
func (db *DB) CreateAdmin(ctx context.Context, adm *linkedca.Admin) error {
var err error
adm.Id, err = randID()
if err != nil {
return admin.WrapErrorISE(err, "error generating random id for admin")
}
adm.AuthorityId = db.authorityID
dba := &dbAdmin{
ID: adm.Id,
AuthorityID: db.authorityID,
ProvisionerID: adm.ProvisionerId,
Subject: adm.Subject,
Type: adm.Type,
CreatedAt: clock.Now(),
}
return db.save(ctx, dba.ID, dba, nil, "admin", adminsTable)
}
// UpdateAdmin saves an updated admin to the database.
func (db *DB) UpdateAdmin(ctx context.Context, adm *linkedca.Admin) error {
old, err := db.getDBAdmin(ctx, adm.Id)
if err != nil {
return err
}
nu := old.clone()
nu.Type = adm.Type
return db.save(ctx, old.ID, nu, old, "admin", adminsTable)
}
// DeleteAdmin saves an updated admin to the database.
func (db *DB) DeleteAdmin(ctx context.Context, id string) error {
old, err := db.getDBAdmin(ctx, id)
if err != nil {
return err
}
nu := old.clone()
nu.DeletedAt = clock.Now()
return db.save(ctx, old.ID, nu, old, "admin", adminsTable)
}
|