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:
		
							parent
							
								
									0b8f8510dd
								
							
						
					
					
						commit
						d18e1dbcff
					
				| 
						 | 
					@ -15,6 +15,7 @@ const (
 | 
				
			||||||
	RejectedIdentifier
 | 
						RejectedIdentifier
 | 
				
			||||||
	InvalidEmail
 | 
						InvalidEmail
 | 
				
			||||||
	ConnectionFailure
 | 
						ConnectionFailure
 | 
				
			||||||
 | 
						WrongAuthorizationState
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// BoulderError represents internal Boulder errors
 | 
					// BoulderError represents internal Boulder errors
 | 
				
			||||||
| 
						 | 
					@ -75,3 +76,7 @@ func InvalidEmailError(msg string, args ...interface{}) error {
 | 
				
			||||||
func ConnectionFailureError(msg string, args ...interface{}) error {
 | 
					func ConnectionFailureError(msg string, args ...interface{}) error {
 | 
				
			||||||
	return New(ConnectionFailure, msg, args...)
 | 
						return New(ConnectionFailure, msg, args...)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func WrongAuthorizationStateError(msg string, args ...interface{}) error {
 | 
				
			||||||
 | 
						return New(WrongAuthorizationState, msg, args...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								ra/ra.go
								
								
								
								
							
							
						
						
									
										2
									
								
								ra/ra.go
								
								
								
								
							| 
						 | 
					@ -1219,7 +1219,7 @@ func (ra *RegistrationAuthorityImpl) UpdateAuthorization(ctx context.Context, ba
 | 
				
			||||||
	if err = ra.SA.UpdatePendingAuthorization(ctx, authz); err != nil {
 | 
						if err = ra.SA.UpdatePendingAuthorization(ctx, authz); err != nil {
 | 
				
			||||||
		ra.log.Warning(fmt.Sprintf(
 | 
							ra.log.Warning(fmt.Sprintf(
 | 
				
			||||||
			"Error calling ra.SA.UpdatePendingAuthorization: %s\n", err.Error()))
 | 
								"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)
 | 
						ra.stats.Inc("NewPendingAuthorizations", 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -852,6 +852,36 @@ func TestUpdateAuthorizationExpired(t *testing.T) {
 | 
				
			||||||
	test.AssertError(t, err, "Updated expired authorization")
 | 
						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) {
 | 
					func TestUpdateAuthorizationNewRPC(t *testing.T) {
 | 
				
			||||||
	va, sa, ra, _, cleanUp := initAuthorities(t)
 | 
						va, sa, ra, _, cleanUp := initAuthorities(t)
 | 
				
			||||||
	defer cleanUp()
 | 
						defer cleanUp()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										4
									
								
								sa/sa.go
								
								
								
								
							
							
						
						
									
										4
									
								
								sa/sa.go
								
								
								
								
							| 
						 | 
					@ -762,12 +762,12 @@ func (ssa *SQLStorageAuthority) UpdatePendingAuthorization(ctx context.Context,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !statusIsPending(authz.Status) {
 | 
						if !statusIsPending(authz.Status) {
 | 
				
			||||||
		err = berrors.InternalServerError("authorization is not pending")
 | 
							err = berrors.WrongAuthorizationStateError("authorization is not pending")
 | 
				
			||||||
		return Rollback(tx, err)
 | 
							return Rollback(tx, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if existingFinal(tx, authz.ID) {
 | 
						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)
 | 
							return Rollback(tx, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,6 +25,8 @@ func problemDetailsForBoulderError(err *berrors.BoulderError, msg string) *probs
 | 
				
			||||||
		return probs.RejectedIdentifier(fmt.Sprintf("%s :: %s", msg, err))
 | 
							return probs.RejectedIdentifier(fmt.Sprintf("%s :: %s", msg, err))
 | 
				
			||||||
	case berrors.InvalidEmail:
 | 
						case berrors.InvalidEmail:
 | 
				
			||||||
		return probs.InvalidEmail(fmt.Sprintf("%s :: %s", msg, err))
 | 
							return probs.InvalidEmail(fmt.Sprintf("%s :: %s", msg, err))
 | 
				
			||||||
 | 
						case berrors.WrongAuthorizationState:
 | 
				
			||||||
 | 
							return probs.Malformed(fmt.Sprintf("%s :: %s", msg, err))
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		// Internal server error messages may include sensitive data, so we do
 | 
							// Internal server error messages may include sensitive data, so we do
 | 
				
			||||||
		// not include it.
 | 
							// not include it.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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) {
 | 
					func TestBadNonce(t *testing.T) {
 | 
				
			||||||
	wfe, _ := setupWFE(t)
 | 
						wfe, _ := setupWFE(t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,6 +25,8 @@ func problemDetailsForBoulderError(err *berrors.BoulderError, msg string) *probs
 | 
				
			||||||
		return probs.RejectedIdentifier(fmt.Sprintf("%s :: %s", msg, err))
 | 
							return probs.RejectedIdentifier(fmt.Sprintf("%s :: %s", msg, err))
 | 
				
			||||||
	case berrors.InvalidEmail:
 | 
						case berrors.InvalidEmail:
 | 
				
			||||||
		return probs.InvalidEmail(fmt.Sprintf("%s :: %s", msg, err))
 | 
							return probs.InvalidEmail(fmt.Sprintf("%s :: %s", msg, err))
 | 
				
			||||||
 | 
						case berrors.WrongAuthorizationState:
 | 
				
			||||||
 | 
							return probs.Malformed(fmt.Sprintf("%s :: %s", msg, err))
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		// Internal server error messages may include sensitive data, so we do
 | 
							// Internal server error messages may include sensitive data, so we do
 | 
				
			||||||
		// not include it.
 | 
							// not include it.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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) {
 | 
					func TestBadNonce(t *testing.T) {
 | 
				
			||||||
	wfe, _ := setupWFE(t)
 | 
						wfe, _ := setupWFE(t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue