Files
crypto/acme/types_test.go
T
Roland Shoemaker 83a5a9bb28 acme: add support for subproblems
Add support for RFC 8555 subproblems. The type naming is real bike-shed
territory, but I think I've mostly matched the existing style of the
package. In a similar vein the format of how to print subproblems
when stringing an acme.Error is up for debate (it could just be
completely ignored, and require clients to inspect Error.Subproblems
themselves).

Fixes golang/go#38978

Change-Id: Ice803079bab621ae9410de79e7e75e11c1af21b6
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/233165
Trust: Roland Shoemaker <roland@golang.org>
Run-TryBot: Roland Shoemaker <roland@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
2021-04-21 17:06:49 +00:00

220 lines
4.9 KiB
Go

// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package acme
import (
"errors"
"net/http"
"reflect"
"testing"
"time"
)
func TestExternalAccountBindingString(t *testing.T) {
eab := ExternalAccountBinding{
KID: "kid",
Key: []byte("key"),
}
got := eab.String()
want := `&{KID: "kid", Key: redacted}`
if got != want {
t.Errorf("eab.String() = %q, want: %q", got, want)
}
}
func TestRateLimit(t *testing.T) {
now := time.Date(2017, 04, 27, 10, 0, 0, 0, time.UTC)
f := timeNow
defer func() { timeNow = f }()
timeNow = func() time.Time { return now }
h120, hTime := http.Header{}, http.Header{}
h120.Set("Retry-After", "120")
hTime.Set("Retry-After", "Tue Apr 27 11:00:00 2017")
err1 := &Error{
ProblemType: "urn:ietf:params:acme:error:nolimit",
Header: h120,
}
err2 := &Error{
ProblemType: "urn:ietf:params:acme:error:rateLimited",
Header: h120,
}
err3 := &Error{
ProblemType: "urn:ietf:params:acme:error:rateLimited",
Header: nil,
}
err4 := &Error{
ProblemType: "urn:ietf:params:acme:error:rateLimited",
Header: hTime,
}
tt := []struct {
err error
res time.Duration
ok bool
}{
{nil, 0, false},
{errors.New("dummy"), 0, false},
{err1, 0, false},
{err2, 2 * time.Minute, true},
{err3, 0, true},
{err4, time.Hour, true},
}
for i, test := range tt {
res, ok := RateLimit(test.err)
if ok != test.ok {
t.Errorf("%d: RateLimit(%+v): ok = %v; want %v", i, test.err, ok, test.ok)
continue
}
if res != test.res {
t.Errorf("%d: RateLimit(%+v) = %v; want %v", i, test.err, res, test.res)
}
}
}
func TestAuthorizationError(t *testing.T) {
tests := []struct {
desc string
err *AuthorizationError
msg string
}{
{
desc: "when auth error identifier is set",
err: &AuthorizationError{
Identifier: "domain.com",
Errors: []error{
(&wireError{
Status: 403,
Type: "urn:ietf:params:acme:error:caa",
Detail: "CAA record for domain.com prevents issuance",
}).error(nil),
},
},
msg: "acme: authorization error for domain.com: 403 urn:ietf:params:acme:error:caa: CAA record for domain.com prevents issuance",
},
{
desc: "when auth error identifier is unset",
err: &AuthorizationError{
Errors: []error{
(&wireError{
Status: 403,
Type: "urn:ietf:params:acme:error:caa",
Detail: "CAA record for domain.com prevents issuance",
}).error(nil),
},
},
msg: "acme: authorization error: 403 urn:ietf:params:acme:error:caa: CAA record for domain.com prevents issuance",
},
}
for _, tt := range tests {
if tt.err.Error() != tt.msg {
t.Errorf("got: %s\nwant: %s", tt.err, tt.msg)
}
}
}
func TestSubproblems(t *testing.T) {
tests := []struct {
wire wireError
expectedOut Error
}{
{
wire: wireError{
Status: 1,
Type: "urn:error",
Detail: "it's an error",
},
expectedOut: Error{
StatusCode: 1,
ProblemType: "urn:error",
Detail: "it's an error",
},
},
{
wire: wireError{
Status: 1,
Type: "urn:error",
Detail: "it's an error",
Subproblems: []Subproblem{
{
Type: "urn:error:sub",
Detail: "it's a subproblem",
},
},
},
expectedOut: Error{
StatusCode: 1,
ProblemType: "urn:error",
Detail: "it's an error",
Subproblems: []Subproblem{
{
Type: "urn:error:sub",
Detail: "it's a subproblem",
},
},
},
},
{
wire: wireError{
Status: 1,
Type: "urn:error",
Detail: "it's an error",
Subproblems: []Subproblem{
{
Type: "urn:error:sub",
Detail: "it's a subproblem",
Identifier: &AuthzID{Type: "dns", Value: "example"},
},
},
},
expectedOut: Error{
StatusCode: 1,
ProblemType: "urn:error",
Detail: "it's an error",
Subproblems: []Subproblem{
{
Type: "urn:error:sub",
Detail: "it's a subproblem",
Identifier: &AuthzID{Type: "dns", Value: "example"},
},
},
},
},
}
for _, tc := range tests {
out := tc.wire.error(nil)
if !reflect.DeepEqual(*out, tc.expectedOut) {
t.Errorf("Unexpected error: wanted %v, got %v", tc.expectedOut, *out)
}
}
}
func TestErrorStringerWithSubproblems(t *testing.T) {
err := Error{
StatusCode: 1,
ProblemType: "urn:error",
Detail: "it's an error",
Subproblems: []Subproblem{
{
Type: "urn:error:sub",
Detail: "it's a subproblem",
},
{
Type: "urn:error:sub",
Detail: "it's a subproblem",
Identifier: &AuthzID{Type: "dns", Value: "example"},
},
},
}
expectedStr := "1 urn:error: it's an error; subproblems:\n\turn:error:sub: it's a subproblem\n\turn:error:sub: [dns: example] it's a subproblem"
if err.Error() != expectedStr {
t.Errorf("Unexpected error string: wanted %q, got %q", expectedStr, err.Error())
}
}