diff --git a/acme/api/handler_test.go b/acme/api/handler_test.go index 70d2dc14..7fd8e110 100644 --- a/acme/api/handler_test.go +++ b/acme/api/handler_test.go @@ -121,9 +121,9 @@ func TestHandler_GetAuthorization(t *testing.T) { Type: "dns", Value: "example.com", }, - Status: "pending", - Expires: expiry, - Wildcard: false, + Status: "pending", + ExpiresAt: expiry, + Wildcard: false, Challenges: []*acme.Challenge{ { Type: "http-01", @@ -220,7 +220,7 @@ func TestHandler_GetAuthorization(t *testing.T) { return &acme.Authorization{ AccountID: "accID", Status: acme.StatusPending, - Expires: time.Now().Add(-1 * time.Hour), + ExpiresAt: time.Now().Add(-1 * time.Hour), }, nil }, MockUpdateAuthorization: func(ctx context.Context, az *acme.Authorization) error { diff --git a/acme/api/order.go b/acme/api/order.go index 9fe0eb26..379c2287 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -89,14 +89,24 @@ func (h *Handler) NewOrder(w http.ResponseWriter, r *http.Request) { return } + now := clock.Now() + expiry := now.Add(defaultOrderExpiry) // New order. - o := &acme.Order{Identifiers: nor.Identifiers} + o := &acme.Order{ + AccountID: acc.ID, + ProvisionerID: prov.GetID(), + Status: acme.StatusPending, + ExpiresAt: expiry, + Identifiers: nor.Identifiers, + } o.AuthorizationIDs = make([]string, len(o.Identifiers)) for i, identifier := range o.Identifiers { az := &acme.Authorization{ AccountID: acc.ID, Identifier: identifier, + ExpiresAt: expiry, + Status: acme.StatusPending, } if err := h.newAuthorization(ctx, az); err != nil { api.WriteError(w, err) @@ -105,14 +115,12 @@ func (h *Handler) NewOrder(w http.ResponseWriter, r *http.Request) { o.AuthorizationIDs[i] = az.ID } - now := clock.Now() if o.NotBefore.IsZero() { o.NotBefore = now } if o.NotAfter.IsZero() { o.NotAfter = o.NotBefore.Add(prov.DefaultTLSCertDuration()) } - o.Expires = now.Add(defaultOrderExpiry) if err := h.db.CreateOrder(ctx, o); err != nil { api.WriteError(w, acme.WrapErrorISE(err, "error creating order")) @@ -156,6 +164,7 @@ func (h *Handler) newAuthorization(ctx context.Context, az *acme.Authorization) Value: az.Identifier.Value, Type: typ, Token: az.Token, + Status: acme.StatusPending, } if err := h.db.CreateChallenge(ctx, ch); err != nil { return err diff --git a/acme/api/order_test.go b/acme/api/order_test.go index b6783e34..0bc3caab 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -166,9 +166,9 @@ func TestHandler_GetOrder(t *testing.T) { Value: "*.smallstep.com", }, }, - Expires: expiry, - Status: acme.StatusInvalid, - Error: acme.NewError(acme.ErrorMalformedType, "order has expired"), + ExpiresAt: expiry, + Status: acme.StatusInvalid, + Error: acme.NewError(acme.ErrorMalformedType, "order has expired"), AuthorizationURLs: []string{ "https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/foo", "https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/bar", @@ -285,7 +285,7 @@ func TestHandler_GetOrder(t *testing.T) { return &acme.Order{ AccountID: "accountID", ProvisionerID: "acme/test@acme-provisioner.com", - Expires: clock.Now().Add(-time.Hour), + ExpiresAt: clock.Now().Add(-time.Hour), Status: acme.StatusReady, }, nil }, @@ -311,7 +311,7 @@ func TestHandler_GetOrder(t *testing.T) { ID: "orderID", AccountID: "accountID", ProvisionerID: "acme/test@acme-provisioner.com", - Expires: expiry, + ExpiresAt: expiry, Status: acme.StatusReady, AuthorizationIDs: []string{"foo", "bar", "baz"}, NotBefore: nbf, @@ -380,7 +380,7 @@ func TestHandler_NewOrder(t *testing.T) { naf := nbf.Add(17 * time.Hour) o := acme.Order{ ID: "orderID", - Expires: expiry, + ExpiresAt: expiry, NotBefore: nbf, NotAfter: naf, Identifiers: []acme.Identifier{ @@ -607,8 +607,8 @@ func TestHandler_FinalizeOrder(t *testing.T) { Value: "*.smallstep.com", }, }, - Expires: naf, - Status: acme.StatusValid, + ExpiresAt: naf, + Status: acme.StatusValid, AuthorizationURLs: []string{ "https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/foo", "https://test.ca.smallstep.com/acme/test@acme-provisioner.com/authz/bar", @@ -788,7 +788,7 @@ func TestHandler_FinalizeOrder(t *testing.T) { return &acme.Order{ AccountID: "accountID", ProvisionerID: "acme/test@acme-provisioner.com", - Expires: clock.Now().Add(-time.Hour), + ExpiresAt: clock.Now().Add(-time.Hour), Status: acme.StatusReady, }, nil }, @@ -815,7 +815,7 @@ func TestHandler_FinalizeOrder(t *testing.T) { ID: "orderID", AccountID: "accountID", ProvisionerID: "acme/test@acme-provisioner.com", - Expires: naf, + ExpiresAt: naf, Status: acme.StatusValid, AuthorizationIDs: []string{"foo", "bar", "baz"}, NotBefore: nbf, diff --git a/acme/authorization.go b/acme/authorization.go index cf68cba3..62bc4637 100644 --- a/acme/authorization.go +++ b/acme/authorization.go @@ -13,6 +13,7 @@ type Authorization struct { ExpiresAt time.Time `json:"expires"` Challenges []*Challenge `json:"challenges"` Wildcard bool `json:"wildcard"` + Error *Error `json:"error,omitempty"` ID string `json:"-"` AccountID string `json:"-"` Token string `json:"-"` diff --git a/acme/db/nosql/authz.go b/acme/db/nosql/authz.go index a5d422a7..2ea1bb69 100644 --- a/acme/db/nosql/authz.go +++ b/acme/db/nosql/authz.go @@ -23,6 +23,7 @@ type dbAuthz struct { Wildcard bool `json:"wildcard"` CreatedAt time.Time `json:"createdAt"` Error *acme.Error `json:"error"` + Token string `json:"token"` } func (ba *dbAuthz) clone() *dbAuthz { @@ -35,14 +36,14 @@ func (ba *dbAuthz) clone() *dbAuthz { func (db *DB) getDBAuthz(ctx context.Context, id string) (*dbAuthz, error) { data, err := db.db.Get(authzTable, []byte(id)) if nosql.IsErrNotFound(err) { - return nil, errors.Wrapf(err, "authz %s not found", id) + return nil, acme.NewError(acme.ErrorMalformedType, "authz %s not found", id) } else if err != nil { return nil, errors.Wrapf(err, "error loading authz %s", id) } var dbaz dbAuthz if err = json.Unmarshal(data, &dbaz); err != nil { - return nil, errors.Wrap(err, "error unmarshaling authz type into dbAuthz") + return nil, errors.Wrapf(err, "error unmarshaling authz %s into dbAuthz", id) } return &dbaz, nil } @@ -62,12 +63,15 @@ func (db *DB) GetAuthorization(ctx context.Context, id string) (*acme.Authorizat } } return &acme.Authorization{ + ID: dbaz.ID, + AccountID: dbaz.AccountID, Identifier: dbaz.Identifier, Status: dbaz.Status, Challenges: chs, Wildcard: dbaz.Wildcard, ExpiresAt: dbaz.ExpiresAt, - ID: dbaz.ID, + Token: dbaz.Token, + Error: dbaz.Error, }, nil } @@ -89,11 +93,12 @@ func (db *DB) CreateAuthorization(ctx context.Context, az *acme.Authorization) e dbaz := &dbAuthz{ ID: az.ID, AccountID: az.AccountID, - Status: acme.StatusPending, + Status: az.Status, CreatedAt: now, - ExpiresAt: now.Add(defaultExpiryDuration), + ExpiresAt: az.ExpiresAt, Identifier: az.Identifier, Challenges: chIDs, + Token: az.Token, Wildcard: az.Wildcard, } @@ -102,9 +107,6 @@ func (db *DB) CreateAuthorization(ctx context.Context, az *acme.Authorization) e // UpdateAuthorization saves an updated ACME Authorization to the database. func (db *DB) UpdateAuthorization(ctx context.Context, az *acme.Authorization) error { - if len(az.ID) == 0 { - return errors.New("id cannot be empty") - } old, err := db.getDBAuthz(ctx, az.ID) if err != nil { return err @@ -113,5 +115,6 @@ func (db *DB) UpdateAuthorization(ctx context.Context, az *acme.Authorization) e nu := old.clone() nu.Status = az.Status + nu.Error = az.Error return db.save(ctx, old.ID, nu, old, "authz", authzTable) } diff --git a/acme/db/nosql/authz_test.go b/acme/db/nosql/authz_test.go new file mode 100644 index 00000000..825c4648 --- /dev/null +++ b/acme/db/nosql/authz_test.go @@ -0,0 +1,620 @@ +package nosql + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/pkg/errors" + "github.com/smallstep/assert" + "github.com/smallstep/certificates/acme" + "github.com/smallstep/certificates/db" + "github.com/smallstep/nosql" + nosqldb "github.com/smallstep/nosql/database" +) + +func TestDB_getDBAuthz(t *testing.T) { + azID := "azID" + type test struct { + db nosql.DB + err error + acmeErr *acme.Error + dbaz *dbAuthz + } + var tests = map[string]func(t *testing.T) test{ + "fail/not-found": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, authzTable) + assert.Equals(t, string(key), azID) + + return nil, nosqldb.ErrNotFound + }, + }, + acmeErr: acme.NewError(acme.ErrorMalformedType, "authz azID not found"), + } + }, + "fail/db.Get-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, authzTable) + assert.Equals(t, string(key), azID) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading authz azID: force"), + } + }, + "fail/unmarshal-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, authzTable) + assert.Equals(t, string(key), azID) + + return []byte("foo"), nil + }, + }, + err: errors.New("error unmarshaling authz azID into dbAuthz"), + } + }, + "ok": func(t *testing.T) test { + now := clock.Now() + dbaz := &dbAuthz{ + ID: azID, + AccountID: "accountID", + Identifier: acme.Identifier{ + Type: "dns", + Value: "test.ca.smallstep.com", + }, + Status: acme.StatusPending, + Token: "token", + CreatedAt: now, + ExpiresAt: now.Add(5 * time.Minute), + Error: acme.NewErrorISE("force"), + Challenges: []string{"foo", "bar"}, + Wildcard: true, + } + b, err := json.Marshal(dbaz) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, authzTable) + assert.Equals(t, string(key), azID) + + return b, nil + }, + }, + dbaz: dbaz, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db} + if dbaz, err := db.getDBAuthz(context.Background(), azID); err != nil { + switch k := err.(type) { + case *acme.Error: + if assert.NotNil(t, tc.acmeErr) { + assert.Equals(t, k.Type, tc.acmeErr.Type) + assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, k.Status, tc.acmeErr.Status) + assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, k.Detail, tc.acmeErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } else { + if assert.Nil(t, tc.err) { + assert.Equals(t, dbaz.ID, tc.dbaz.ID) + assert.Equals(t, dbaz.AccountID, tc.dbaz.AccountID) + assert.Equals(t, dbaz.Identifier, tc.dbaz.Identifier) + assert.Equals(t, dbaz.Status, tc.dbaz.Status) + assert.Equals(t, dbaz.Token, tc.dbaz.Token) + assert.Equals(t, dbaz.CreatedAt, tc.dbaz.CreatedAt) + assert.Equals(t, dbaz.ExpiresAt, tc.dbaz.ExpiresAt) + assert.Equals(t, dbaz.Error.Error(), tc.dbaz.Error.Error()) + assert.Equals(t, dbaz.Wildcard, tc.dbaz.Wildcard) + } + } + }) + } +} + +func TestDB_GetAuthorization(t *testing.T) { + azID := "azID" + type test struct { + db nosql.DB + err error + acmeErr *acme.Error + dbaz *dbAuthz + } + var tests = map[string]func(t *testing.T) test{ + "fail/db.Get-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, authzTable) + assert.Equals(t, string(key), azID) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading authz azID: force"), + } + }, + "fail/forward-acme-error": func(t *testing.T) test { + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, authzTable) + assert.Equals(t, string(key), azID) + + return nil, nosqldb.ErrNotFound + }, + }, + acmeErr: acme.NewError(acme.ErrorMalformedType, "authz azID not found"), + } + }, + "fail/db.GetChallenge-error": func(t *testing.T) test { + now := clock.Now() + dbaz := &dbAuthz{ + ID: azID, + AccountID: "accountID", + Identifier: acme.Identifier{ + Type: "dns", + Value: "test.ca.smallstep.com", + }, + Status: acme.StatusPending, + Token: "token", + CreatedAt: now, + ExpiresAt: now.Add(5 * time.Minute), + Error: acme.NewErrorISE("force"), + Challenges: []string{"foo", "bar"}, + Wildcard: true, + } + b, err := json.Marshal(dbaz) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + switch string(bucket) { + case string(authzTable): + assert.Equals(t, string(key), azID) + return b, nil + case string(challengeTable): + assert.Equals(t, string(key), "foo") + return nil, errors.New("force") + default: + assert.FatalError(t, errors.Errorf("unexpected bucket '%s'", string(bucket))) + return nil, errors.New("force") + } + }, + }, + err: errors.New("error loading acme challenge foo: force"), + } + }, + "fail/db.GetChallenge-not-found": func(t *testing.T) test { + now := clock.Now() + dbaz := &dbAuthz{ + ID: azID, + AccountID: "accountID", + Identifier: acme.Identifier{ + Type: "dns", + Value: "test.ca.smallstep.com", + }, + Status: acme.StatusPending, + Token: "token", + CreatedAt: now, + ExpiresAt: now.Add(5 * time.Minute), + Error: acme.NewErrorISE("force"), + Challenges: []string{"foo", "bar"}, + Wildcard: true, + } + b, err := json.Marshal(dbaz) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + switch string(bucket) { + case string(authzTable): + assert.Equals(t, string(key), azID) + return b, nil + case string(challengeTable): + assert.Equals(t, string(key), "foo") + return nil, nosqldb.ErrNotFound + default: + assert.FatalError(t, errors.Errorf("unexpected bucket '%s'", string(bucket))) + return nil, errors.New("force") + } + }, + }, + acmeErr: acme.NewError(acme.ErrorMalformedType, "challenge foo not found"), + } + }, + "ok": func(t *testing.T) test { + now := clock.Now() + dbaz := &dbAuthz{ + ID: azID, + AccountID: "accountID", + Identifier: acme.Identifier{ + Type: "dns", + Value: "test.ca.smallstep.com", + }, + Status: acme.StatusPending, + Token: "token", + CreatedAt: now, + ExpiresAt: now.Add(5 * time.Minute), + Error: acme.NewErrorISE("force"), + Challenges: []string{"foo", "bar"}, + Wildcard: true, + } + b, err := json.Marshal(dbaz) + assert.FatalError(t, err) + chCount := 0 + fooChb, err := json.Marshal(&dbChallenge{ID: "foo"}) + assert.FatalError(t, err) + barChb, err := json.Marshal(&dbChallenge{ID: "bar"}) + assert.FatalError(t, err) + return test{ + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + switch string(bucket) { + case string(authzTable): + assert.Equals(t, string(key), azID) + return b, nil + case string(challengeTable): + if chCount == 0 { + chCount++ + assert.Equals(t, string(key), "foo") + return fooChb, nil + } + assert.Equals(t, string(key), "bar") + return barChb, nil + default: + assert.FatalError(t, errors.Errorf("unexpected bucket '%s'", string(bucket))) + return nil, errors.New("force") + } + }, + }, + dbaz: dbaz, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db} + if az, err := db.GetAuthorization(context.Background(), azID); err != nil { + switch k := err.(type) { + case *acme.Error: + if assert.NotNil(t, tc.acmeErr) { + assert.Equals(t, k.Type, tc.acmeErr.Type) + assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, k.Status, tc.acmeErr.Status) + assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, k.Detail, tc.acmeErr.Detail) + } + default: + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } + } else { + if assert.Nil(t, tc.err) { + assert.Equals(t, az.ID, tc.dbaz.ID) + assert.Equals(t, az.AccountID, tc.dbaz.AccountID) + assert.Equals(t, az.Identifier, tc.dbaz.Identifier) + assert.Equals(t, az.Status, tc.dbaz.Status) + assert.Equals(t, az.Token, tc.dbaz.Token) + assert.Equals(t, az.Wildcard, tc.dbaz.Wildcard) + assert.Equals(t, az.ExpiresAt, tc.dbaz.ExpiresAt) + assert.Equals(t, az.Challenges, []*acme.Challenge{ + {ID: "foo"}, + {ID: "bar"}, + }) + assert.Equals(t, az.Error.Error(), tc.dbaz.Error.Error()) + } + } + }) + } +} + +func TestDB_CreateAuthorization(t *testing.T) { + azID := "azID" + type test struct { + db nosql.DB + az *acme.Authorization + err error + _id *string + } + var tests = map[string]func(t *testing.T) test{ + "fail/cmpAndSwap-error": func(t *testing.T) test { + now := clock.Now() + az := &acme.Authorization{ + ID: azID, + AccountID: "accountID", + Identifier: acme.Identifier{ + Type: "dns", + Value: "test.ca.smallstep.com", + }, + Status: acme.StatusPending, + Token: "token", + ExpiresAt: now.Add(5 * time.Minute), + Challenges: []*acme.Challenge{ + {ID: "foo"}, + {ID: "bar"}, + }, + Wildcard: true, + Error: acme.NewErrorISE("force"), + } + return test{ + db: &db.MockNoSQLDB{ + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, authzTable) + assert.Equals(t, string(key), az.ID) + assert.Equals(t, old, nil) + + dbaz := new(dbAuthz) + assert.FatalError(t, json.Unmarshal(nu, dbaz)) + assert.Equals(t, dbaz.ID, string(key)) + assert.Equals(t, dbaz.AccountID, az.AccountID) + assert.Equals(t, dbaz.Identifier, acme.Identifier{ + Type: "dns", + Value: "test.ca.smallstep.com", + }) + assert.Equals(t, dbaz.Status, az.Status) + assert.Equals(t, dbaz.Token, az.Token) + assert.Equals(t, dbaz.Challenges, []string{"foo", "bar"}) + assert.Equals(t, dbaz.Wildcard, az.Wildcard) + assert.Equals(t, dbaz.ExpiresAt, az.ExpiresAt) + assert.Nil(t, dbaz.Error) + assert.True(t, clock.Now().Add(-time.Minute).Before(dbaz.CreatedAt)) + assert.True(t, clock.Now().Add(time.Minute).After(dbaz.CreatedAt)) + return nil, false, errors.New("force") + }, + }, + az: az, + err: errors.New("error saving acme authz: force"), + } + }, + "ok": func(t *testing.T) test { + var ( + id string + idPtr = &id + now = clock.Now() + az = &acme.Authorization{ + ID: azID, + AccountID: "accountID", + Identifier: acme.Identifier{ + Type: "dns", + Value: "test.ca.smallstep.com", + }, + Status: acme.StatusPending, + Token: "token", + ExpiresAt: now.Add(5 * time.Minute), + Challenges: []*acme.Challenge{ + {ID: "foo"}, + {ID: "bar"}, + }, + Wildcard: true, + Error: acme.NewErrorISE("force"), + } + ) + return test{ + db: &db.MockNoSQLDB{ + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + *idPtr = string(key) + assert.Equals(t, bucket, authzTable) + assert.Equals(t, string(key), az.ID) + assert.Equals(t, old, nil) + + dbaz := new(dbAuthz) + assert.FatalError(t, json.Unmarshal(nu, dbaz)) + assert.Equals(t, dbaz.ID, string(key)) + assert.Equals(t, dbaz.AccountID, az.AccountID) + assert.Equals(t, dbaz.Identifier, acme.Identifier{ + Type: "dns", + Value: "test.ca.smallstep.com", + }) + assert.Equals(t, dbaz.Status, az.Status) + assert.Equals(t, dbaz.Token, az.Token) + assert.Equals(t, dbaz.Challenges, []string{"foo", "bar"}) + assert.Equals(t, dbaz.Wildcard, az.Wildcard) + assert.Equals(t, dbaz.ExpiresAt, az.ExpiresAt) + assert.Nil(t, dbaz.Error) + assert.True(t, clock.Now().Add(-time.Minute).Before(dbaz.CreatedAt)) + assert.True(t, clock.Now().Add(time.Minute).After(dbaz.CreatedAt)) + return nu, true, nil + }, + }, + az: az, + _id: idPtr, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db} + if err := db.CreateAuthorization(context.Background(), tc.az); err != nil { + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } else { + if assert.Nil(t, tc.err) { + assert.Equals(t, tc.az.ID, *tc._id) + } + } + }) + } +} + +func TestDB_UpdateAuthorization(t *testing.T) { + azID := "azID" + now := clock.Now() + dbaz := &dbAuthz{ + ID: azID, + AccountID: "accountID", + Identifier: acme.Identifier{ + Type: "dns", + Value: "test.ca.smallstep.com", + }, + Status: acme.StatusPending, + Token: "token", + CreatedAt: now, + ExpiresAt: now.Add(5 * time.Minute), + Challenges: []string{"foo", "bar"}, + Wildcard: true, + } + b, err := json.Marshal(dbaz) + assert.FatalError(t, err) + type test struct { + db nosql.DB + az *acme.Authorization + err error + } + var tests = map[string]func(t *testing.T) test{ + "fail/db.Get-error": func(t *testing.T) test { + return test{ + az: &acme.Authorization{ + ID: azID, + }, + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, authzTable) + assert.Equals(t, string(key), azID) + + return nil, errors.New("force") + }, + }, + err: errors.New("error loading authz azID: force"), + } + }, + "fail/db.CmpAndSwap-error": func(t *testing.T) test { + updAz := &acme.Authorization{ + ID: azID, + Status: acme.StatusValid, + Error: acme.NewError(acme.ErrorMalformedType, "malformed"), + } + return test{ + az: updAz, + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, authzTable) + assert.Equals(t, string(key), azID) + + return b, nil + }, + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, authzTable) + assert.Equals(t, old, b) + + dbOld := new(dbAuthz) + assert.FatalError(t, json.Unmarshal(old, dbOld)) + assert.Equals(t, dbaz, dbOld) + + dbNew := new(dbAuthz) + assert.FatalError(t, json.Unmarshal(nu, dbNew)) + assert.Equals(t, dbNew.ID, dbaz.ID) + assert.Equals(t, dbNew.AccountID, dbaz.AccountID) + assert.Equals(t, dbNew.Identifier, dbaz.Identifier) + assert.Equals(t, dbNew.Status, acme.StatusValid) + assert.Equals(t, dbNew.Token, dbaz.Token) + assert.Equals(t, dbNew.Challenges, dbaz.Challenges) + assert.Equals(t, dbNew.Wildcard, dbaz.Wildcard) + assert.Equals(t, dbNew.CreatedAt, dbaz.CreatedAt) + assert.Equals(t, dbNew.ExpiresAt, dbaz.ExpiresAt) + assert.Equals(t, dbNew.Error.Error(), acme.NewError(acme.ErrorMalformedType, "malformed").Error()) + return nil, false, errors.New("force") + }, + }, + err: errors.New("error saving acme authz: force"), + } + }, + "ok": func(t *testing.T) test { + updAz := &acme.Authorization{ + ID: azID, + AccountID: dbaz.AccountID, + Status: acme.StatusValid, + Identifier: dbaz.Identifier, + Challenges: []*acme.Challenge{ + {ID: "foo"}, + {ID: "bar"}, + }, + Token: dbaz.Token, + Wildcard: dbaz.Wildcard, + ExpiresAt: dbaz.ExpiresAt, + Error: acme.NewError(acme.ErrorMalformedType, "malformed"), + } + return test{ + az: updAz, + db: &db.MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + assert.Equals(t, bucket, authzTable) + assert.Equals(t, string(key), azID) + + return b, nil + }, + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { + assert.Equals(t, bucket, authzTable) + assert.Equals(t, old, b) + + dbOld := new(dbAuthz) + assert.FatalError(t, json.Unmarshal(old, dbOld)) + assert.Equals(t, dbaz, dbOld) + + dbNew := new(dbAuthz) + assert.FatalError(t, json.Unmarshal(nu, dbNew)) + assert.Equals(t, dbNew.ID, dbaz.ID) + assert.Equals(t, dbNew.AccountID, dbaz.AccountID) + assert.Equals(t, dbNew.Identifier, dbaz.Identifier) + assert.Equals(t, dbNew.Status, acme.StatusValid) + assert.Equals(t, dbNew.Token, dbaz.Token) + assert.Equals(t, dbNew.Challenges, dbaz.Challenges) + assert.Equals(t, dbNew.Wildcard, dbaz.Wildcard) + assert.Equals(t, dbNew.CreatedAt, dbaz.CreatedAt) + assert.Equals(t, dbNew.ExpiresAt, dbaz.ExpiresAt) + assert.Equals(t, dbNew.Error.Error(), acme.NewError(acme.ErrorMalformedType, "malformed").Error()) + return nu, true, nil + }, + }, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + db := DB{db: tc.db} + if err := db.UpdateAuthorization(context.Background(), tc.az); err != nil { + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } else { + if assert.Nil(t, tc.err) { + assert.Equals(t, tc.az.ID, dbaz.ID) + assert.Equals(t, tc.az.AccountID, dbaz.AccountID) + assert.Equals(t, tc.az.Identifier, dbaz.Identifier) + assert.Equals(t, tc.az.Status, acme.StatusValid) + assert.Equals(t, tc.az.Wildcard, dbaz.Wildcard) + assert.Equals(t, tc.az.Token, dbaz.Token) + assert.Equals(t, tc.az.ExpiresAt, dbaz.ExpiresAt) + assert.Equals(t, tc.az.Challenges, []*acme.Challenge{ + {ID: "foo"}, + {ID: "bar"}, + }) + assert.Equals(t, tc.az.Error.Error(), acme.NewError(acme.ErrorMalformedType, "malformed").Error()) + } + } + }) + } +} diff --git a/acme/db/nosql/order.go b/acme/db/nosql/order.go index 59afc41c..bc89442a 100644 --- a/acme/db/nosql/order.go +++ b/acme/db/nosql/order.go @@ -58,7 +58,7 @@ func (db *DB) GetOrder(ctx context.Context, id string) (*acme.Order, error) { o := &acme.Order{ Status: dbo.Status, - Expires: dbo.Expires, + ExpiresAt: dbo.Expires, Identifiers: dbo.Identifiers, NotBefore: dbo.NotBefore, NotAfter: dbo.NotAfter, @@ -86,7 +86,7 @@ func (db *DB) CreateOrder(ctx context.Context, o *acme.Order) error { ProvisionerID: o.ProvisionerID, Created: now, Status: acme.StatusPending, - Expires: o.Expires, + Expires: o.ExpiresAt, Identifiers: o.Identifiers, NotBefore: o.NotBefore, NotAfter: o.NotBefore, diff --git a/acme/order.go b/acme/order.go index a2c89fe7..f62e3354 100644 --- a/acme/order.go +++ b/acme/order.go @@ -22,7 +22,7 @@ type Identifier struct { type Order struct { ID string `json:"id"` Status Status `json:"status"` - Expires time.Time `json:"expires,omitempty"` + ExpiresAt time.Time `json:"expires,omitempty"` Identifiers []Identifier `json:"identifiers"` NotBefore time.Time `json:"notBefore,omitempty"` NotAfter time.Time `json:"notAfter,omitempty"` @@ -59,7 +59,7 @@ func (o *Order) UpdateStatus(ctx context.Context, db DB) error { return nil case StatusReady: // Check expiry - if now.After(o.Expires) { + if now.After(o.ExpiresAt) { o.Status = StatusInvalid o.Error = NewError(ErrorMalformedType, "order has expired") break @@ -67,7 +67,7 @@ func (o *Order) UpdateStatus(ctx context.Context, db DB) error { return nil case StatusPending: // Check expiry - if now.After(o.Expires) { + if now.After(o.ExpiresAt) { o.Status = StatusInvalid o.Error = NewError(ErrorMalformedType, "order has expired") break diff --git a/ca/acmeClient_test.go b/ca/acmeClient_test.go index 08d4b734..3fbd42c5 100644 --- a/ca/acmeClient_test.go +++ b/ca/acmeClient_test.go @@ -388,7 +388,7 @@ func TestACMEClient_NewOrder(t *testing.T) { assert.FatalError(t, err) ord := acme.Order{ Status: "valid", - Expires: time.Now(), // "soon" + ExpiresAt: time.Now(), // "soon" FinalizeURL: "finalize-url", } ac := &ACMEClient{ @@ -510,7 +510,7 @@ func TestACMEClient_GetOrder(t *testing.T) { assert.FatalError(t, err) ord := acme.Order{ Status: "valid", - Expires: time.Now(), // "soon" + ExpiresAt: time.Now(), // "soon" FinalizeURL: "finalize-url", } ac := &ACMEClient{ @@ -630,7 +630,7 @@ func TestACMEClient_GetAuthz(t *testing.T) { assert.FatalError(t, err) az := acme.Authorization{ Status: "valid", - Expires: time.Now(), + ExpiresAt: time.Now(), Identifier: acme.Identifier{Type: "dns", Value: "example.com"}, } ac := &ACMEClient{ @@ -988,7 +988,7 @@ func TestACMEClient_FinalizeOrder(t *testing.T) { assert.FatalError(t, err) ord := acme.Order{ Status: "valid", - Expires: time.Now(), // "soon" + ExpiresAt: time.Now(), // "soon" FinalizeURL: "finalize-url", CertificateURL: "cert-url", }