Add WrongAuthorizationState error code for UpdateAuthorization (#3053)

This commit adds a new boulder error type WrongAuthorizationState.
This error type is returned by the SA when UpdateAuthorization is
provided an authz that isn't pending. The RA and WFE are updated
accordingly such that this error percolates back through the API to the
user as a Malformed problem with a sensible description. Previously this
behaviour resulted in a ServerInternal error.

Resolves #3032
This commit is contained in:
Daniel McCarney 2017-09-07 14:22:02 -04:00 committed by Roland Bracewell Shoemaker
parent 0b8f8510dd
commit d18e1dbcff
8 changed files with 112 additions and 3 deletions

View File

@ -15,6 +15,7 @@ const (
RejectedIdentifier
InvalidEmail
ConnectionFailure
WrongAuthorizationState
)
// BoulderError represents internal Boulder errors
@ -75,3 +76,7 @@ func InvalidEmailError(msg string, args ...interface{}) error {
func ConnectionFailureError(msg string, args ...interface{}) error {
return New(ConnectionFailure, msg, args...)
}
func WrongAuthorizationStateError(msg string, args ...interface{}) error {
return New(WrongAuthorizationState, msg, args...)
}

View File

@ -1219,7 +1219,7 @@ func (ra *RegistrationAuthorityImpl) UpdateAuthorization(ctx context.Context, ba
if err = ra.SA.UpdatePendingAuthorization(ctx, authz); err != nil {
ra.log.Warning(fmt.Sprintf(
"Error calling ra.SA.UpdatePendingAuthorization: %s\n", err.Error()))
return core.Authorization{}, berrors.InternalServerError("could not update pending authorization")
return core.Authorization{}, err
}
ra.stats.Inc("NewPendingAuthorizations", 1)

View File

@ -852,6 +852,36 @@ func TestUpdateAuthorizationExpired(t *testing.T) {
test.AssertError(t, err, "Updated expired authorization")
}
func TestUpdateAuthorizationAlreadyValid(t *testing.T) {
va, sa, ra, _, cleanUp := initAuthorities(t)
defer cleanUp()
// Create a finalized authorization
finalAuthz := AuthzInitial
finalAuthz.Status = "valid"
exp := ra.clk.Now().Add(365 * 24 * time.Hour)
finalAuthz.Expires = &exp
finalAuthz.Challenges[0].Status = "valid"
finalAuthz.RegistrationID = Registration.ID
finalAuthz, err := sa.NewPendingAuthorization(ctx, finalAuthz)
test.AssertNotError(t, err, "Could not create pending authorization")
err = sa.FinalizeAuthorization(ctx, finalAuthz)
test.AssertNotError(t, err, "Could not finalize pending authorization")
response, err := makeResponse(finalAuthz.Challenges[ResponseIndex])
test.AssertNotError(t, err, "Unable to construct response to challenge")
finalAuthz.Challenges[ResponseIndex].Type = core.ChallengeTypeDNS01
finalAuthz.Challenges[ResponseIndex].Status = core.StatusPending
va.RecordsReturn = []core.ValidationRecord{
{Hostname: "example.com"}}
va.ProblemReturn = nil
// A subsequent call to update the authorization should return the expected error
_, err = ra.UpdateAuthorization(ctx, finalAuthz, ResponseIndex, response)
test.Assert(t, berrors.Is(err, berrors.WrongAuthorizationState),
"FinalizeAuthorization of valid authz didn't return a berrors.WrongAuthorizationState")
}
func TestUpdateAuthorizationNewRPC(t *testing.T) {
va, sa, ra, _, cleanUp := initAuthorities(t)
defer cleanUp()

View File

@ -762,12 +762,12 @@ func (ssa *SQLStorageAuthority) UpdatePendingAuthorization(ctx context.Context,
}
if !statusIsPending(authz.Status) {
err = berrors.InternalServerError("authorization is not pending")
err = berrors.WrongAuthorizationStateError("authorization is not pending")
return Rollback(tx, err)
}
if existingFinal(tx, authz.ID) {
err = berrors.InternalServerError("cannot update a finalized authorization")
err = berrors.WrongAuthorizationStateError("cannot update a finalized authorization")
return Rollback(tx, err)
}

View File

@ -25,6 +25,8 @@ func problemDetailsForBoulderError(err *berrors.BoulderError, msg string) *probs
return probs.RejectedIdentifier(fmt.Sprintf("%s :: %s", msg, err))
case berrors.InvalidEmail:
return probs.InvalidEmail(fmt.Sprintf("%s :: %s", msg, err))
case berrors.WrongAuthorizationState:
return probs.Malformed(fmt.Sprintf("%s :: %s", msg, err))
default:
// Internal server error messages may include sensitive data, so we do
// not include it.

View File

@ -1123,6 +1123,41 @@ func TestChallenge(t *testing.T) {
}
// MockRAStrictUpdateAuthz is a mock RA that enforces authz status in `UpdateAuthorization`
type MockRAStrictUpdateAuthz struct {
MockRegistrationAuthority
}
// UpdateAuthorization for a MockRAStrictUpdateAuthz returns a
// berrors.WrongAuthorizationStateError when told to update a non-pending authz.
// It returns the authz unchanged for all other cases.
func (ra *MockRAStrictUpdateAuthz) UpdateAuthorization(_ context.Context, authz core.Authorization, _ int, _ core.Challenge) (core.Authorization, error) {
if authz.Status != core.StatusPending {
return core.Authorization{}, berrors.WrongAuthorizationStateError("authorization is not pending")
}
return authz, nil
}
// TestUpdateChallengeFinalizedAuthz tests that POSTing a challenge associated
// with an already valid authorization returns the expected Malformed problem.
func TestUpdateChallengeFinalizedAuthz(t *testing.T) {
wfe, _ := setupWFE(t)
wfe.RA = &MockRAStrictUpdateAuthz{}
responseWriter := httptest.NewRecorder()
path := "valid/23"
wfe.Challenge(ctx, newRequestEvent(), responseWriter,
makePostRequestWithPath(path,
signRequest(t, `{"resource":"challenge"}`, wfe.nonceService)))
body := responseWriter.Body.String()
test.AssertUnmarshaledEquals(t, body, `{
"type": "`+probs.V1ErrorNS+`malformed",
"detail": "Unable to update challenge :: authorization is not pending",
"status": 400
}`)
}
func TestBadNonce(t *testing.T) {
wfe, _ := setupWFE(t)

View File

@ -25,6 +25,8 @@ func problemDetailsForBoulderError(err *berrors.BoulderError, msg string) *probs
return probs.RejectedIdentifier(fmt.Sprintf("%s :: %s", msg, err))
case berrors.InvalidEmail:
return probs.InvalidEmail(fmt.Sprintf("%s :: %s", msg, err))
case berrors.WrongAuthorizationState:
return probs.Malformed(fmt.Sprintf("%s :: %s", msg, err))
default:
// Internal server error messages may include sensitive data, so we do
// not include it.

View File

@ -979,6 +979,41 @@ func TestChallenge(t *testing.T) {
}
}
// MockRAStrictUpdateAuthz is a mock RA that enforces authz status in `UpdateAuthorization`
type MockRAStrictUpdateAuthz struct {
MockRegistrationAuthority
}
// UpdateAuthorization for a MockRAStrictUpdateAuthz returns a
// berrors.WrongAuthorizationStateError when told to update a non-pending authz. It
// returns the authz unchanged for all other cases.
func (ra *MockRAStrictUpdateAuthz) UpdateAuthorization(_ context.Context, authz core.Authorization, _ int, _ core.Challenge) (core.Authorization, error) {
if authz.Status != core.StatusPending {
return core.Authorization{}, berrors.WrongAuthorizationStateError("authorization is not pending")
}
return authz, nil
}
// TestUpdateChallengeFinalizedAuthz tests that POSTing a challenge associated
// with an already valid authorization returns the expected Malformed problem.
func TestUpdateChallengeFinalizedAuthz(t *testing.T) {
wfe, _ := setupWFE(t)
wfe.RA = &MockRAStrictUpdateAuthz{}
responseWriter := httptest.NewRecorder()
signedURL := "http://localhost/valid/23"
_, _, jwsBody := signRequestKeyID(t, 1, nil, signedURL, `{}`, wfe.nonceService)
request := makePostRequestWithPath("valid/23", jwsBody)
wfe.Challenge(ctx, newRequestEvent(), responseWriter, request)
body := responseWriter.Body.String()
test.AssertUnmarshaledEquals(t, body, `{
"type": "`+probs.V2ErrorNS+`malformed",
"detail": "Unable to update challenge :: authorization is not pending",
"status": 400
}`)
}
func TestBadNonce(t *testing.T) {
wfe, _ := setupWFE(t)