You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
smallstep-certificates/acme/authority_test.go

1740 lines
48 KiB
Go

package acme
import (
"context"
"crypto"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
"github.com/pkg/errors"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/db"
"github.com/smallstep/nosql/database"
"go.step.sm/crypto/jose"
)
func TestAuthorityGetLink(t *testing.T) {
auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
prov := newProv()
provName := url.PathEscape(prov.GetName())
baseURL := &url.URL{Scheme: "https", Host: "test.ca.smallstep.com"}
ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
ctx = context.WithValue(ctx, BaseURLContextKey, baseURL)
type test struct {
auth *Authority
typ Link
abs bool
inputs []string
res string
}
tests := map[string]func(t *testing.T) test{
"ok/new-account/abs": func(t *testing.T) test {
return test{
auth: auth,
typ: NewAccountLink,
abs: true,
res: fmt.Sprintf("%s/acme/%s/new-account", baseURL.String(), provName),
}
},
"ok/new-account/no-abs": func(t *testing.T) test {
return test{
auth: auth,
typ: NewAccountLink,
abs: false,
res: fmt.Sprintf("/%s/new-account", provName),
}
},
"ok/order/abs": func(t *testing.T) test {
return test{
auth: auth,
typ: OrderLink,
abs: true,
inputs: []string{"foo"},
res: fmt.Sprintf("%s/acme/%s/order/foo", baseURL.String(), provName),
}
},
"ok/order/no-abs": func(t *testing.T) test {
return test{
auth: auth,
typ: OrderLink,
abs: false,
inputs: []string{"foo"},
res: fmt.Sprintf("/%s/order/foo", provName),
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
link := tc.auth.GetLink(ctx, tc.typ, tc.abs, tc.inputs...)
assert.Equals(t, tc.res, link)
})
}
}
func TestAuthorityGetDirectory(t *testing.T) {
auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
prov := newProv()
baseURL := &url.URL{Scheme: "https", Host: "test.ca.smallstep.com"}
ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
ctx = context.WithValue(ctx, BaseURLContextKey, baseURL)
type test struct {
ctx context.Context
err *Error
}
tests := map[string]func(t *testing.T) test{
"ok/empty-provisioner": func(t *testing.T) test {
return test{
ctx: context.Background(),
}
},
"ok/no-baseURL": func(t *testing.T) test {
return test{
ctx: context.WithValue(context.Background(), ProvisionerContextKey, prov),
}
},
"ok/baseURL": func(t *testing.T) test {
return test{
ctx: ctx,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
if dir, err := auth.GetDirectory(tc.ctx); err != nil {
if assert.NotNil(t, tc.err) {
ae, ok := err.(*Error)
assert.True(t, ok)
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
} else {
if assert.Nil(t, tc.err) {
bu := BaseURLFromContext(tc.ctx)
if bu == nil {
bu = &url.URL{Scheme: "https", Host: "ca.smallstep.com"}
}
var provName string
prov, err := ProvisionerFromContext(tc.ctx)
if err != nil {
provName = ""
} else {
provName = url.PathEscape(prov.GetName())
}
assert.Equals(t, dir.NewNonce, fmt.Sprintf("%s/acme/%s/new-nonce", bu.String(), provName))
assert.Equals(t, dir.NewAccount, fmt.Sprintf("%s/acme/%s/new-account", bu.String(), provName))
assert.Equals(t, dir.NewOrder, fmt.Sprintf("%s/acme/%s/new-order", bu.String(), provName))
assert.Equals(t, dir.RevokeCert, fmt.Sprintf("%s/acme/%s/revoke-cert", bu.String(), provName))
assert.Equals(t, dir.KeyChange, fmt.Sprintf("%s/acme/%s/key-change", bu.String(), provName))
}
}
})
}
}
func TestAuthorityNewNonce(t *testing.T) {
type test struct {
auth *Authority
res *string
err *Error
}
tests := map[string]func(t *testing.T) test{
"fail/newNonce-error": func(t *testing.T) test {
auth, err := NewAuthority(&db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
return nil, false, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
res: nil,
err: ServerInternalErr(errors.New("error storing nonce: force")),
}
},
"ok": func(t *testing.T) test {
var _res string
res := &_res
auth, err := NewAuthority(&db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
*res = string(key)
return nil, true, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
res: res,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
if nonce, err := tc.auth.NewNonce(); err != nil {
if assert.NotNil(t, tc.err) {
ae, ok := err.(*Error)
assert.True(t, ok)
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
} else {
if assert.Nil(t, tc.err) {
assert.Equals(t, nonce, *tc.res)
}
}
})
}
}
func TestAuthorityUseNonce(t *testing.T) {
type test struct {
auth *Authority
err *Error
}
tests := map[string]func(t *testing.T) test{
"fail/newNonce-error": func(t *testing.T) test {
auth, err := NewAuthority(&db.MockNoSQLDB{
MUpdate: func(tx *database.Tx) error {
return errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
err: ServerInternalErr(errors.New("error deleting nonce foo: force")),
}
},
"ok": func(t *testing.T) test {
auth, err := NewAuthority(&db.MockNoSQLDB{
MUpdate: func(tx *database.Tx) error {
return nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
if err := tc.auth.UseNonce("foo"); err != nil {
if assert.NotNil(t, tc.err) {
ae, ok := err.(*Error)
assert.True(t, ok)
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
} else {
assert.Nil(t, tc.err)
}
})
}
}
func TestAuthorityNewAccount(t *testing.T) {
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
ops := AccountOptions{
Key: jwk, Contact: []string{"foo", "bar"},
}
prov := newProv()
ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
type test struct {
auth *Authority
ops AccountOptions
err *Error
acc **Account
}
tests := map[string]func(t *testing.T) test{
"fail/newAccount-error": func(t *testing.T) test {
auth, err := NewAuthority(&db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
return nil, false, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
ops: ops,
err: ServerInternalErr(errors.New("error setting key-id to account-id index: force")),
}
},
"ok": func(t *testing.T) test {
var (
_acmeacc = &Account{}
acmeacc = &_acmeacc
count = 0
dir = newDirectory("ca.smallstep.com", "acme")
)
auth, err := NewAuthority(&db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
if count == 1 {
var acc *account
assert.FatalError(t, json.Unmarshal(newval, &acc))
*acmeacc, err = acc.toACME(ctx, nil, dir)
return nil, true, nil
}
count++
return nil, true, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
ops: ops,
acc: acmeacc,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
if acmeAcc, err := tc.auth.NewAccount(ctx, tc.ops); err != nil {
if assert.NotNil(t, tc.err) {
ae, ok := err.(*Error)
assert.True(t, ok)
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
} else {
if assert.Nil(t, tc.err) {
gotb, err := json.Marshal(acmeAcc)
assert.FatalError(t, err)
expb, err := json.Marshal(*tc.acc)
assert.FatalError(t, err)
assert.Equals(t, expb, gotb)
}
}
})
}
}
func TestAuthorityGetAccount(t *testing.T) {
prov := newProv()
ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
type test struct {
auth *Authority
id string
err *Error
acc *account
}
tests := map[string]func(t *testing.T) test{
"fail/getAccount-error": func(t *testing.T) test {
id := "foo"
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, accountTable)
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: id,
err: ServerInternalErr(errors.Errorf("error loading account %s: force", id)),
}
},
"ok": func(t *testing.T) test {
acc, err := newAcc()
assert.FatalError(t, err)
b, err := json.Marshal(acc)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: acc.ID,
acc: acc,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
if acmeAcc, err := tc.auth.GetAccount(ctx, tc.id); err != nil {
if assert.NotNil(t, tc.err) {
ae, ok := err.(*Error)
assert.True(t, ok)
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
} else {
if assert.Nil(t, tc.err) {
gotb, err := json.Marshal(acmeAcc)
assert.FatalError(t, err)
acmeExp, err := tc.acc.toACME(ctx, nil, tc.auth.dir)
assert.FatalError(t, err)
expb, err := json.Marshal(acmeExp)
assert.FatalError(t, err)
assert.Equals(t, expb, gotb)
}
}
})
}
}
func TestAuthorityGetAccountByKey(t *testing.T) {
prov := newProv()
ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
type test struct {
auth *Authority
jwk *jose.JSONWebKey
err *Error
acc *account
}
tests := map[string]func(t *testing.T) test{
"fail/generate-thumbprint-error": func(t *testing.T) test {
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
jwk.Key = "foo"
auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
jwk: jwk,
err: ServerInternalErr(errors.New("error generating jwk thumbprint: square/go-jose: unknown key type 'string'")),
}
},
"fail/getAccount-error": func(t *testing.T) test {
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
kid, err := keyToID(jwk)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, accountByKeyIDTable)
assert.Equals(t, key, []byte(kid))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
jwk: jwk,
err: ServerInternalErr(errors.New("error loading key-account index: force")),
}
},
"ok": func(t *testing.T) test {
acc, err := newAcc()
assert.FatalError(t, err)
b, err := json.Marshal(acc)
assert.FatalError(t, err)
count := 0
kid, err := keyToID(acc.Key)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
var ret []byte
switch {
case count == 0:
assert.Equals(t, bucket, accountByKeyIDTable)
assert.Equals(t, key, []byte(kid))
ret = []byte(acc.ID)
case count == 1:
assert.Equals(t, bucket, accountTable)
assert.Equals(t, key, []byte(acc.ID))
ret = b
}
count++
return ret, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
jwk: acc.Key,
acc: acc,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
if acmeAcc, err := tc.auth.GetAccountByKey(ctx, tc.jwk); err != nil {
if assert.NotNil(t, tc.err) {
ae, ok := err.(*Error)
assert.True(t, ok)
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
} else {
if assert.Nil(t, tc.err) {
gotb, err := json.Marshal(acmeAcc)
assert.FatalError(t, err)
acmeExp, err := tc.acc.toACME(ctx, nil, tc.auth.dir)
assert.FatalError(t, err)
expb, err := json.Marshal(acmeExp)
assert.FatalError(t, err)
assert.Equals(t, expb, gotb)
}
}
})
}
}
func TestAuthorityGetOrder(t *testing.T) {
prov := newProv()
ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
type test struct {
auth *Authority
id, accID string
err *Error
o *order
}
tests := map[string]func(t *testing.T) test{
"fail/getOrder-error": func(t *testing.T) test {
id := "foo"
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, orderTable)
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: id,
err: ServerInternalErr(errors.New("error loading order foo: force")),
}
},
"fail/order-not-owned-by-account": func(t *testing.T) test {
o, err := newO()
assert.FatalError(t, err)
b, err := json.Marshal(o)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, orderTable)
assert.Equals(t, key, []byte(o.ID))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: o.ID,
accID: "foo",
err: UnauthorizedErr(errors.New("account does not own order")),
}
},
"fail/updateStatus-error": func(t *testing.T) test {
o, err := newO()
assert.FatalError(t, err)
b, err := json.Marshal(o)
assert.FatalError(t, err)
i := 0
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
switch {
case i == 0:
i++
assert.Equals(t, bucket, orderTable)
assert.Equals(t, key, []byte(o.ID))
return b, nil
default:
assert.Equals(t, bucket, authzTable)
assert.Equals(t, key, []byte(o.Authorizations[0]))
return nil, ServerInternalErr(errors.New("force"))
}
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: o.ID,
accID: o.AccountID,
err: ServerInternalErr(errors.Errorf("error loading authz %s: force", o.Authorizations[0])),
}
},
"ok": func(t *testing.T) test {
o, err := newO()
assert.FatalError(t, err)
o.Status = "valid"
b, err := json.Marshal(o)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, orderTable)
assert.Equals(t, key, []byte(o.ID))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: o.ID,
accID: o.AccountID,
o: o,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
if acmeO, err := tc.auth.GetOrder(ctx, tc.accID, tc.id); err != nil {
if assert.NotNil(t, tc.err) {
ae, ok := err.(*Error)
assert.True(t, ok)
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
} else {
if assert.Nil(t, tc.err) {
gotb, err := json.Marshal(acmeO)
assert.FatalError(t, err)
acmeExp, err := tc.o.toACME(ctx, nil, tc.auth.dir)
assert.FatalError(t, err)
expb, err := json.Marshal(acmeExp)
assert.FatalError(t, err)
assert.Equals(t, expb, gotb)
}
}
})
}
}
func TestAuthorityGetCertificate(t *testing.T) {
type test struct {
auth *Authority
id, accID string
err *Error
cert *certificate
}
tests := map[string]func(t *testing.T) test{
"fail/getCertificate-error": func(t *testing.T) test {
id := "foo"
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, certTable)
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: id,
err: ServerInternalErr(errors.New("error loading certificate: force")),
}
},
"fail/certificate-not-owned-by-account": func(t *testing.T) test {
cert, err := newcert()
assert.FatalError(t, err)
b, err := json.Marshal(cert)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, certTable)
assert.Equals(t, key, []byte(cert.ID))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: cert.ID,
accID: "foo",
err: UnauthorizedErr(errors.New("account does not own certificate")),
}
},
"ok": func(t *testing.T) test {
cert, err := newcert()
assert.FatalError(t, err)
b, err := json.Marshal(cert)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, certTable)
assert.Equals(t, key, []byte(cert.ID))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: cert.ID,
accID: cert.AccountID,
cert: cert,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
if acmeCert, err := tc.auth.GetCertificate(tc.accID, tc.id); err != nil {
if assert.NotNil(t, tc.err) {
ae, ok := err.(*Error)
assert.True(t, ok)
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
} else {
if assert.Nil(t, tc.err) {
gotb, err := json.Marshal(acmeCert)
assert.FatalError(t, err)
acmeExp, err := tc.cert.toACME(nil, tc.auth.dir)
assert.FatalError(t, err)
expb, err := json.Marshal(acmeExp)
assert.FatalError(t, err)
assert.Equals(t, expb, gotb)
}
}
})
}
}
func TestAuthorityGetAuthz(t *testing.T) {
prov := newProv()
ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
type test struct {
auth *Authority
id, accID string
err *Error
acmeAz *Authz
}
tests := map[string]func(t *testing.T) test{
"fail/getAuthz-error": func(t *testing.T) test {
id := "foo"
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, authzTable)
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: id,
err: ServerInternalErr(errors.Errorf("error loading authz %s: force", id)),
}
},
"fail/authz-not-owned-by-account": func(t *testing.T) test {
az, err := newAz()
assert.FatalError(t, err)
b, err := json.Marshal(az)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, authzTable)
assert.Equals(t, key, []byte(az.getID()))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: az.getID(),
accID: "foo",
err: UnauthorizedErr(errors.New("account does not own authz")),
}
},
"fail/update-status-error": func(t *testing.T) test {
az, err := newAz()
assert.FatalError(t, err)
b, err := json.Marshal(az)
assert.FatalError(t, err)
count := 0
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
var ret []byte
switch count {
case 0:
assert.Equals(t, bucket, authzTable)
assert.Equals(t, key, []byte(az.getID()))
ret = b
case 1:
assert.Equals(t, bucket, challengeTable)
assert.Equals(t, key, []byte(az.getChallenges()[0]))
return nil, errors.New("force")
}
count++
return ret, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: az.getID(),
accID: az.getAccountID(),
err: ServerInternalErr(errors.New("error updating authz status: error loading challenge")),
}
},
"ok": func(t *testing.T) test {
var ch1B, ch2B, ch3B = &[]byte{}, &[]byte{}, &[]byte{}
count := 0
mockdb := &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
switch count {
case 0:
*ch1B = newval
case 1:
*ch2B = newval
case 2:
*ch3B = newval
}
count++
return nil, true, nil
},
}
az, err := newAuthz(mockdb, "1234", Identifier{
Type: "dns", Value: "acme.example.com",
})
assert.FatalError(t, err)
_az, ok := az.(*dnsAuthz)
assert.Fatal(t, ok)
_az.baseAuthz.Status = StatusValid
b, err := json.Marshal(az)
assert.FatalError(t, err)
ch1, err := unmarshalChallenge(*ch1B)
assert.FatalError(t, err)
ch2, err := unmarshalChallenge(*ch2B)
assert.FatalError(t, err)
ch3, err := unmarshalChallenge(*ch3B)
assert.FatalError(t, err)
count = 0
mockdb = &db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
var ret []byte
switch count {
case 0:
assert.Equals(t, bucket, challengeTable)
assert.Equals(t, key, []byte(ch1.getID()))
ret = *ch1B
case 1:
assert.Equals(t, bucket, challengeTable)
assert.Equals(t, key, []byte(ch2.getID()))
ret = *ch2B
case 2:
assert.Equals(t, bucket, challengeTable)
assert.Equals(t, key, []byte(ch3.getID()))
ret = *ch3B
}
count++
return ret, nil
},
}
acmeAz, err := az.toACME(ctx, mockdb, newDirectory("ca.smallstep.com", "acme"))
assert.FatalError(t, err)
count = 0
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
var ret []byte
switch count {
case 0:
assert.Equals(t, bucket, authzTable)
assert.Equals(t, key, []byte(az.getID()))
ret = b
case 1:
assert.Equals(t, bucket, challengeTable)
assert.Equals(t, key, []byte(ch1.getID()))
ret = *ch1B
case 2:
assert.Equals(t, bucket, challengeTable)
assert.Equals(t, key, []byte(ch2.getID()))
ret = *ch2B
case 3:
assert.Equals(t, bucket, challengeTable)
assert.Equals(t, key, []byte(ch3.getID()))
ret = *ch3B
}
count++
return ret, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: az.getID(),
accID: az.getAccountID(),
acmeAz: acmeAz,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
if acmeAz, err := tc.auth.GetAuthz(ctx, tc.accID, tc.id); err != nil {
if assert.NotNil(t, tc.err) {
ae, ok := err.(*Error)
assert.True(t, ok)
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
} else {
if assert.Nil(t, tc.err) {
gotb, err := json.Marshal(acmeAz)
assert.FatalError(t, err)
expb, err := json.Marshal(tc.acmeAz)
assert.FatalError(t, err)
assert.Equals(t, expb, gotb)
}
}
})
}
}
func TestAuthorityNewOrder(t *testing.T) {
prov := newProv()
ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
type test struct {
auth *Authority
ops OrderOptions
ctx context.Context
err *Error
o **Order
}
tests := map[string]func(t *testing.T) test{
"fail/no-provisioner": func(t *testing.T) test {
auth, err := NewAuthority(&db.MockNoSQLDB{}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
ops: defaultOrderOps(),
ctx: context.Background(),
err: ServerInternalErr(errors.New("provisioner expected in request context")),
}
},
"fail/newOrder-error": func(t *testing.T) test {
auth, err := NewAuthority(&db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
return nil, false, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
ops: defaultOrderOps(),
ctx: ctx,
err: ServerInternalErr(errors.New("error creating order: error creating http challenge: error saving acme challenge: force")),
}
},
"ok": func(t *testing.T) test {
var (
_acmeO = &Order{}
acmeO = &_acmeO
count = 0
dir = newDirectory("ca.smallstep.com", "acme")
err error
_accID string
accID = &_accID
)
auth, err := NewAuthority(&db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
switch count {
case 0:
assert.Equals(t, bucket, challengeTable)
case 1:
assert.Equals(t, bucket, challengeTable)
case 2:
assert.Equals(t, bucket, challengeTable)
case 3:
assert.Equals(t, bucket, authzTable)
case 4:
assert.Equals(t, bucket, challengeTable)
case 5:
assert.Equals(t, bucket, challengeTable)
case 6:
assert.Equals(t, bucket, challengeTable)
case 7:
assert.Equals(t, bucket, authzTable)
case 8:
assert.Equals(t, bucket, orderTable)
var o order
assert.FatalError(t, json.Unmarshal(newval, &o))
*acmeO, err = o.toACME(ctx, nil, dir)
assert.FatalError(t, err)
*accID = o.AccountID
case 9:
assert.Equals(t, bucket, ordersByAccountIDTable)
assert.Equals(t, string(key), *accID)
}
count++
return nil, true, nil
},
MGet: func(bucket, key []byte) ([]byte, error) {
return nil, database.ErrNotFound
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
ops: defaultOrderOps(),
ctx: ctx,
o: acmeO,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
if acmeO, err := tc.auth.NewOrder(tc.ctx, tc.ops); err != nil {
if assert.NotNil(t, tc.err) {
ae, ok := err.(*Error)
assert.True(t, ok)
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
} else {
if assert.Nil(t, tc.err) {
gotb, err := json.Marshal(acmeO)
assert.FatalError(t, err)
expb, err := json.Marshal(*tc.o)
assert.FatalError(t, err)
assert.Equals(t, expb, gotb)
}
}
})
}
}
func TestAuthorityGetOrdersByAccount(t *testing.T) {
prov := newProv()
provName := url.PathEscape(prov.GetName())
baseURL := &url.URL{Scheme: "https", Host: "test.ca.smallstep.com"}
ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
ctx = context.WithValue(ctx, BaseURLContextKey, baseURL)
type test struct {
auth *Authority
id string
err *Error
res []string
}
tests := map[string]func(t *testing.T) test{
"fail/getOrderIDsByAccount-error": func(t *testing.T) test {
id := "foo"
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, ordersByAccountIDTable)
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: id,
err: ServerInternalErr(errors.New("error loading orderIDs for account foo: force")),
}
},
"fail/getOrder-error": func(t *testing.T) test {
var (
id = "zap"
oids = []string{"foo", "bar"}
count = 0
err error
)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
var ret []byte
switch count {
case 0:
assert.Equals(t, bucket, ordersByAccountIDTable)
assert.Equals(t, key, []byte(id))
ret, err = json.Marshal(oids)
assert.FatalError(t, err)
case 1:
assert.Equals(t, bucket, orderTable)
assert.Equals(t, key, []byte(oids[0]))
return nil, errors.New("force")
}
count++
return ret, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: id,
err: ServerInternalErr(errors.New("error loading order foo for account zap: error loading order foo: force")),
}
},
"ok": func(t *testing.T) test {
accID := "zap"
foo, err := newO()
assert.FatalError(t, err)
bfoo, err := json.Marshal(foo)
assert.FatalError(t, err)
bar, err := newO()
assert.FatalError(t, err)
bar.Status = StatusInvalid
bbar, err := json.Marshal(bar)
assert.FatalError(t, err)
zap, err := newO()
assert.FatalError(t, err)
bzap, err := json.Marshal(zap)
assert.FatalError(t, err)
az, err := newAz()
assert.FatalError(t, err)
baz, err := json.Marshal(az)
assert.FatalError(t, err)
ch, err := newDNSCh()
assert.FatalError(t, err)
bch, err := json.Marshal(ch)
assert.FatalError(t, err)
dbGetOrder := 0
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
switch string(bucket) {
case string(orderTable):
dbGetOrder++
switch dbGetOrder {
case 1:
return bfoo, nil
case 2:
return bbar, nil
case 3:
return bzap, nil
}
case string(ordersByAccountIDTable):
assert.Equals(t, bucket, ordersByAccountIDTable)
assert.Equals(t, key, []byte(accID))
ret, err := json.Marshal([]string{foo.ID, bar.ID, zap.ID})
assert.FatalError(t, err)
return ret, nil
case string(challengeTable):
return bch, nil
case string(authzTable):
return baz, nil
}
return nil, errors.Errorf("should not be query db table %s", bucket)
},
MCmpAndSwap: func(bucket, key, old, newVal []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, ordersByAccountIDTable)
assert.Equals(t, string(key), accID)
return nil, true, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: accID,
res: []string{
fmt.Sprintf("%s/acme/%s/order/%s", baseURL.String(), provName, foo.ID),
fmt.Sprintf("%s/acme/%s/order/%s", baseURL.String(), provName, zap.ID),
},
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
if orderLinks, err := tc.auth.GetOrdersByAccount(ctx, tc.id); err != nil {
if assert.NotNil(t, tc.err) {
ae, ok := err.(*Error)
assert.True(t, ok)
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
} else {
if assert.Nil(t, tc.err) {
assert.Equals(t, tc.res, orderLinks)
}
}
})
}
}
func TestAuthorityFinalizeOrder(t *testing.T) {
prov := newProv()
ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
type test struct {
auth *Authority
id, accID string
ctx context.Context
err *Error
o *order
}
tests := map[string]func(t *testing.T) test{
"fail/no-provisioner": func(t *testing.T) test {
auth, err := NewAuthority(&db.MockNoSQLDB{}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: "foo",
ctx: context.Background(),
err: ServerInternalErr(errors.New("provisioner expected in request context")),
}
},
"fail/getOrder-error": func(t *testing.T) test {
id := "foo"
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, orderTable)
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: id,
ctx: ctx,
err: ServerInternalErr(errors.New("error loading order foo: force")),
}
},
"fail/order-not-owned-by-account": func(t *testing.T) test {
o, err := newO()
assert.FatalError(t, err)
b, err := json.Marshal(o)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, orderTable)
assert.Equals(t, key, []byte(o.ID))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: o.ID,
accID: "foo",
ctx: ctx,
err: UnauthorizedErr(errors.New("account does not own order")),
}
},
"fail/finalize-error": func(t *testing.T) test {
o, err := newO()
assert.FatalError(t, err)
o.Expires = time.Now().Add(-time.Minute)
b, err := json.Marshal(o)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, orderTable)
assert.Equals(t, key, []byte(o.ID))
return b, nil
},
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, orderTable)
assert.Equals(t, key, []byte(o.ID))
return nil, false, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: o.ID,
accID: o.AccountID,
ctx: ctx,
err: ServerInternalErr(errors.New("error finalizing order: error storing order: force")),
}
},
"ok": func(t *testing.T) test {
o, err := newO()
assert.FatalError(t, err)
o.Status = StatusValid
o.Certificate = "certID"
b, err := json.Marshal(o)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, orderTable)
assert.Equals(t, key, []byte(o.ID))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: o.ID,
accID: o.AccountID,
ctx: ctx,
o: o,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
if acmeO, err := tc.auth.FinalizeOrder(tc.ctx, tc.accID, tc.id, nil); err != nil {
if assert.NotNil(t, tc.err) {
ae, ok := err.(*Error)
assert.True(t, ok)
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
} else {
if assert.Nil(t, tc.err) {
gotb, err := json.Marshal(acmeO)
assert.FatalError(t, err)
acmeExp, err := tc.o.toACME(ctx, nil, tc.auth.dir)
assert.FatalError(t, err)
expb, err := json.Marshal(acmeExp)
assert.FatalError(t, err)
assert.Equals(t, expb, gotb)
}
}
})
}
}
func TestAuthorityValidateChallenge(t *testing.T) {
prov := newProv()
ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
type test struct {
auth *Authority
id, accID string
err *Error
ch challenge
jwk *jose.JSONWebKey
server *httptest.Server
}
tests := map[string]func(t *testing.T) test{
"fail/getChallenge-error": func(t *testing.T) test {
id := "foo"
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, challengeTable)
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: id,
err: ServerInternalErr(errors.Errorf("error loading challenge %s: force", id)),
}
},
"fail/challenge-not-owned-by-account": func(t *testing.T) test {
ch, err := newHTTPCh()
assert.FatalError(t, err)
b, err := json.Marshal(ch)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, challengeTable)
assert.Equals(t, key, []byte(ch.getID()))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: ch.getID(),
accID: "foo",
err: UnauthorizedErr(errors.New("account does not own challenge")),
}
},
"fail/validate-error": func(t *testing.T) test {
keyauth := "temp"
keyauthp := &keyauth
// Create test server that returns challenge auth
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s\r\n", *keyauthp)
}))
t.Cleanup(func() { ts.Close() })
ch, err := newHTTPChWithServer(strings.TrimPrefix(ts.URL, "http://"))
assert.FatalError(t, err)
jwk, _, err := jose.GenerateDefaultKeyPair([]byte("pass"))
assert.FatalError(t, err)
thumbprint, err := jwk.Thumbprint(crypto.SHA256)
assert.FatalError(t, err)
encPrint := base64.RawURLEncoding.EncodeToString(thumbprint)
*keyauthp = fmt.Sprintf("%s.%s", ch.getToken(), encPrint)
b, err := json.Marshal(ch)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, challengeTable)
assert.Equals(t, key, []byte(ch.getID()))
return b, nil
},
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, challengeTable)
assert.Equals(t, key, []byte(ch.getID()))
return nil, false, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: ch.getID(),
accID: ch.getAccountID(),
jwk: jwk,
server: ts,
err: ServerInternalErr(errors.New("error attempting challenge validation: error saving acme challenge: force")),
}
},
"ok/already-valid": func(t *testing.T) test {
ch, err := newHTTPCh()
assert.FatalError(t, err)
_ch, ok := ch.(*http01Challenge)
assert.Fatal(t, ok)
_ch.baseChallenge.Status = StatusValid
_ch.baseChallenge.Validated = clock.Now()
b, err := json.Marshal(ch)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, challengeTable)
assert.Equals(t, key, []byte(ch.getID()))
return b, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: ch.getID(),
accID: ch.getAccountID(),
ch: ch,
}
},
"ok": func(t *testing.T) test {
keyauth := "temp"
keyauthp := &keyauth
// Create test server that returns challenge auth
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s\r\n", *keyauthp)
}))
t.Cleanup(func() { ts.Close() })
ch, err := newHTTPChWithServer(strings.TrimPrefix(ts.URL, "http://"))
assert.FatalError(t, err)
jwk, _, err := jose.GenerateDefaultKeyPair([]byte("pass"))
assert.FatalError(t, err)
thumbprint, err := jwk.Thumbprint(crypto.SHA256)
assert.FatalError(t, err)
encPrint := base64.RawURLEncoding.EncodeToString(thumbprint)
*keyauthp = fmt.Sprintf("%s.%s", ch.getToken(), encPrint)
b, err := json.Marshal(ch)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, challengeTable)
assert.Equals(t, key, []byte(ch.getID()))
return b, nil
},
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, challengeTable)
assert.Equals(t, key, []byte(ch.getID()))
return nil, true, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: ch.getID(),
accID: ch.getAccountID(),
jwk: jwk,
server: ts,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
if acmeCh, err := tc.auth.ValidateChallenge(ctx, tc.accID, tc.id, tc.jwk); err != nil {
if assert.NotNil(t, tc.err) {
ae, ok := err.(*Error)
assert.True(t, ok)
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
} else {
if assert.Nil(t, tc.err) {
gotb, err := json.Marshal(acmeCh)
assert.FatalError(t, err)
if tc.ch != nil {
acmeExp, err := tc.ch.toACME(ctx, nil, tc.auth.dir)
assert.FatalError(t, err)
expb, err := json.Marshal(acmeExp)
assert.FatalError(t, err)
assert.Equals(t, expb, gotb)
}
}
}
})
}
}
func TestAuthorityUpdateAccount(t *testing.T) {
contact := []string{"baz", "zap"}
prov := newProv()
ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
type test struct {
auth *Authority
id string
contact []string
acc *account
err *Error
}
tests := map[string]func(t *testing.T) test{
"fail/getAccount-error": func(t *testing.T) test {
id := "foo"
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, accountTable)
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: id,
contact: contact,
err: ServerInternalErr(errors.Errorf("error loading account %s: force", id)),
}
},
"fail/update-error": func(t *testing.T) test {
acc, err := newAcc()
assert.FatalError(t, err)
b, err := json.Marshal(acc)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
return b, nil
},
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
return nil, false, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: acc.ID,
contact: contact,
err: ServerInternalErr(errors.New("error storing account: force")),
}
},
"ok": func(t *testing.T) test {
acc, err := newAcc()
assert.FatalError(t, err)
b, err := json.Marshal(acc)
assert.FatalError(t, err)
_acc := *acc
clone := &_acc
clone.Contact = contact
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
return b, nil
},
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, accountTable)
assert.Equals(t, key, []byte(acc.ID))
return nil, true, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: acc.ID,
contact: contact,
acc: clone,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
if acmeAcc, err := tc.auth.UpdateAccount(ctx, tc.id, tc.contact); err != nil {
if assert.NotNil(t, tc.err) {
ae, ok := err.(*Error)
assert.True(t, ok)
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
} else {
if assert.Nil(t, tc.err) {
gotb, err := json.Marshal(acmeAcc)
assert.FatalError(t, err)
acmeExp, err := tc.acc.toACME(ctx, nil, tc.auth.dir)
assert.FatalError(t, err)
expb, err := json.Marshal(acmeExp)
assert.FatalError(t, err)
assert.Equals(t, expb, gotb)
}
}
})
}
}
func TestAuthorityDeactivateAccount(t *testing.T) {
prov := newProv()
ctx := context.WithValue(context.Background(), ProvisionerContextKey, prov)
ctx = context.WithValue(ctx, BaseURLContextKey, "https://test.ca.smallstep.com:8080")
type test struct {
auth *Authority
id string
acc *account
err *Error
}
tests := map[string]func(t *testing.T) test{
"fail/getAccount-error": func(t *testing.T) test {
id := "foo"
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equals(t, bucket, accountTable)
assert.Equals(t, key, []byte(id))
return nil, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: id,
err: ServerInternalErr(errors.Errorf("error loading account %s: force", id)),
}
},
"fail/deactivate-error": func(t *testing.T) test {
acc, err := newAcc()
assert.FatalError(t, err)
b, err := json.Marshal(acc)
assert.FatalError(t, err)
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
return b, nil
},
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
return nil, false, errors.New("force")
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: acc.ID,
err: ServerInternalErr(errors.New("error storing account: force")),
}
},
"ok": func(t *testing.T) test {
acc, err := newAcc()
assert.FatalError(t, err)
b, err := json.Marshal(acc)
assert.FatalError(t, err)
_acc := *acc
clone := &_acc
clone.Status = StatusDeactivated
clone.Deactivated = clock.Now()
auth, err := NewAuthority(&db.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
return b, nil
},
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, accountTable)
assert.Equals(t, key, []byte(acc.ID))
return nil, true, nil
},
}, "ca.smallstep.com", "acme", nil)
assert.FatalError(t, err)
return test{
auth: auth,
id: acc.ID,
acc: clone,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
if acmeAcc, err := tc.auth.DeactivateAccount(ctx, tc.id); err != nil {
if assert.NotNil(t, tc.err) {
ae, ok := err.(*Error)
assert.True(t, ok)
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
} else {
if assert.Nil(t, tc.err) {
gotb, err := json.Marshal(acmeAcc)
assert.FatalError(t, err)
acmeExp, err := tc.acc.toACME(ctx, nil, tc.auth.dir)
assert.FatalError(t, err)
expb, err := json.Marshal(acmeExp)
assert.FatalError(t, err)
assert.Equals(t, expb, gotb)
}
}
})
}
}