Allow account IDs in authz and challenge URLs (#7768)
This adds new handlers under `/acme/authz/` and `/acme/chall/` that
expect to be followed by `{regID}/{authzID}` and
`{regID}/{authzID}/{challengeID}`, respectively. For deployability, the
old handlers continue to work, and the URLs returned for newly created
objects will still point to the paths used by the old handlers
(`/acme/authz-v3/` and `/acme/chall-v3/`).
There are some self-referential URLs in authz and challenge responses,
like the Location header, and the URL of challenges embedded in an
authorization object. This PR updates `prepAuthorizationForDisplay` and
`prepChallengeForDisplay` so those URLs can be generated consistently
with the path that was requested.
For the WFE tests, in most cases I duplicated an entire test and then
updated it to test the `WithAccount` handler. The idea is that once
we're fully switched over to the new format we can delete the tests for
the non-`WithAccount` variants.
Part of #7683
			
			
This commit is contained in:
		
							parent
							
								
									2603aa45a8
								
							
						
					
					
						commit
						2058d985cc
					
				
							
								
								
									
										147
									
								
								wfe2/wfe.go
								
								
								
								
							
							
						
						
									
										147
									
								
								wfe2/wfe.go
								
								
								
								
							|  | @ -57,16 +57,18 @@ const ( | |||
| 	acctPath      = "/acme/acct/" | ||||
| 	// When we moved to authzv2, we used a "-v3" suffix to avoid confusion
 | ||||
| 	// regarding ACMEv2.
 | ||||
| 	authzPath         = "/acme/authz-v3/" | ||||
| 	challengePath     = "/acme/chall-v3/" | ||||
| 	certPath          = "/acme/cert/" | ||||
| 	revokeCertPath    = "/acme/revoke-cert" | ||||
| 	buildIDPath       = "/build" | ||||
| 	rolloverPath      = "/acme/key-change" | ||||
| 	newNoncePath      = "/acme/new-nonce" | ||||
| 	newOrderPath      = "/acme/new-order" | ||||
| 	orderPath         = "/acme/order/" | ||||
| 	finalizeOrderPath = "/acme/finalize/" | ||||
| 	authzPath             = "/acme/authz-v3/" | ||||
| 	authzPathWithAcct     = "/acme/authz/" | ||||
| 	challengePath         = "/acme/chall-v3/" | ||||
| 	challengePathWithAcct = "/acme/chall/" | ||||
| 	certPath              = "/acme/cert/" | ||||
| 	revokeCertPath        = "/acme/revoke-cert" | ||||
| 	buildIDPath           = "/build" | ||||
| 	rolloverPath          = "/acme/key-change" | ||||
| 	newNoncePath          = "/acme/new-nonce" | ||||
| 	newOrderPath          = "/acme/new-order" | ||||
| 	orderPath             = "/acme/order/" | ||||
| 	finalizeOrderPath     = "/acme/finalize/" | ||||
| 
 | ||||
| 	getAPIPrefix     = "/get/" | ||||
| 	getOrderPath     = getAPIPrefix + "order/" | ||||
|  | @ -432,13 +434,15 @@ func (wfe *WebFrontEndImpl) Handler(stats prometheus.Registerer, oTelHTTPOptions | |||
| 	// TODO(@cpu): After November 1st, 2020 support for "GET" to the following
 | ||||
| 	// endpoints will be removed, leaving only POST-as-GET support.
 | ||||
| 	wfe.HandleFunc(m, orderPath, wfe.GetOrder, "GET", "POST") | ||||
| 	wfe.HandleFunc(m, authzPath, wfe.Authorization, "GET", "POST") | ||||
| 	wfe.HandleFunc(m, challengePath, wfe.Challenge, "GET", "POST") | ||||
| 	wfe.HandleFunc(m, authzPath, wfe.AuthorizationHandler, "GET", "POST") | ||||
| 	wfe.HandleFunc(m, authzPathWithAcct, wfe.AuthorizationHandlerWithAccount, "GET", "POST") | ||||
| 	wfe.HandleFunc(m, challengePath, wfe.ChallengeHandler, "GET", "POST") | ||||
| 	wfe.HandleFunc(m, challengePathWithAcct, wfe.ChallengeHandlerWithAccount, "GET", "POST") | ||||
| 	wfe.HandleFunc(m, certPath, wfe.Certificate, "GET", "POST") | ||||
| 	// Boulder-specific GET-able resource endpoints
 | ||||
| 	wfe.HandleFunc(m, getOrderPath, wfe.GetOrder, "GET") | ||||
| 	wfe.HandleFunc(m, getAuthzPath, wfe.Authorization, "GET") | ||||
| 	wfe.HandleFunc(m, getChallengePath, wfe.Challenge, "GET") | ||||
| 	wfe.HandleFunc(m, getAuthzPath, wfe.AuthorizationHandler, "GET") | ||||
| 	wfe.HandleFunc(m, getChallengePath, wfe.ChallengeHandler, "GET") | ||||
| 	wfe.HandleFunc(m, getCertPath, wfe.Certificate, "GET") | ||||
| 
 | ||||
| 	// Endpoint for draft-ietf-acme-ari
 | ||||
|  | @ -1088,31 +1092,55 @@ func (wfe *WebFrontEndImpl) RevokeCertificate( | |||
| 	response.WriteHeader(http.StatusOK) | ||||
| } | ||||
| 
 | ||||
| // Challenge handles POST requests to challenge URLs.
 | ||||
| // ChallengeHandler handles POST requests to challenge URLs of the form /acme/chall-v3/<authorizationID>/<challengeID>.
 | ||||
| // Such requests are clients' responses to the server's challenges.
 | ||||
| func (wfe *WebFrontEndImpl) Challenge( | ||||
| func (wfe *WebFrontEndImpl) ChallengeHandler( | ||||
| 	ctx context.Context, | ||||
| 	logEvent *web.RequestEvent, | ||||
| 	response http.ResponseWriter, | ||||
| 	request *http.Request) { | ||||
| 	notFound := func() { | ||||
| 		wfe.sendError(response, logEvent, probs.NotFound("No such challenge"), nil) | ||||
| 	} | ||||
| 	slug := strings.Split(request.URL.Path, "/") | ||||
| 	if len(slug) != 2 { | ||||
| 		notFound() | ||||
| 		wfe.sendError(response, logEvent, probs.NotFound("No such challenge"), nil) | ||||
| 		return | ||||
| 	} | ||||
| 	authorizationID, err := strconv.ParseInt(slug[0], 10, 64) | ||||
| 
 | ||||
| 	wfe.Challenge(ctx, logEvent, challengePath, response, request, slug[0], slug[1]) | ||||
| } | ||||
| 
 | ||||
| // ChallengeHandlerWithAccount handles POST requests to challenge URLs of the form /acme/chall/{regID}/{authzID}/{challID}.
 | ||||
| func (wfe *WebFrontEndImpl) ChallengeHandlerWithAccount( | ||||
| 	ctx context.Context, | ||||
| 	logEvent *web.RequestEvent, | ||||
| 	response http.ResponseWriter, | ||||
| 	request *http.Request) { | ||||
| 	slug := strings.Split(request.URL.Path, "/") | ||||
| 	if len(slug) != 3 { | ||||
| 		wfe.sendError(response, logEvent, probs.NotFound("No such challenge"), nil) | ||||
| 		return | ||||
| 	} | ||||
| 	// TODO(#7683): the regID is currently ignored.
 | ||||
| 	wfe.Challenge(ctx, logEvent, challengePathWithAcct, response, request, slug[1], slug[2]) | ||||
| } | ||||
| 
 | ||||
| // Challenge handles POSTS to both formats of challenge URLs.
 | ||||
| func (wfe *WebFrontEndImpl) Challenge( | ||||
| 	ctx context.Context, | ||||
| 	logEvent *web.RequestEvent, | ||||
| 	handlerPath string, | ||||
| 	response http.ResponseWriter, | ||||
| 	request *http.Request, | ||||
| 	authorizationIDStr string, | ||||
| 	challengeID string) { | ||||
| 	authorizationID, err := strconv.ParseInt(authorizationIDStr, 10, 64) | ||||
| 	if err != nil { | ||||
| 		wfe.sendError(response, logEvent, probs.Malformed("Invalid authorization ID"), nil) | ||||
| 		return | ||||
| 	} | ||||
| 	challengeID := slug[1] | ||||
| 	authzPB, err := wfe.ra.GetAuthorization(ctx, &rapb.GetAuthorizationRequest{Id: authorizationID}) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, berrors.NotFound) { | ||||
| 			notFound() | ||||
| 			wfe.sendError(response, logEvent, probs.NotFound("No such challenge"), nil) | ||||
| 		} else { | ||||
| 			wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Problem getting authorization"), err) | ||||
| 		} | ||||
|  | @ -1133,7 +1161,7 @@ func (wfe *WebFrontEndImpl) Challenge( | |||
| 	} | ||||
| 	challengeIndex := authz.FindChallengeByStringID(challengeID) | ||||
| 	if challengeIndex == -1 { | ||||
| 		notFound() | ||||
| 		wfe.sendError(response, logEvent, probs.NotFound("No such challenge"), nil) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
|  | @ -1157,11 +1185,11 @@ func (wfe *WebFrontEndImpl) Challenge( | |||
| 	challenge := authz.Challenges[challengeIndex] | ||||
| 	switch request.Method { | ||||
| 	case "GET", "HEAD": | ||||
| 		wfe.getChallenge(response, request, authz, &challenge, logEvent) | ||||
| 		wfe.getChallenge(handlerPath, response, request, authz, &challenge, logEvent) | ||||
| 
 | ||||
| 	case "POST": | ||||
| 		logEvent.ChallengeType = string(challenge.Type) | ||||
| 		wfe.postChallenge(ctx, response, request, authz, challengeIndex, logEvent) | ||||
| 		wfe.postChallenge(ctx, handlerPath, response, request, authz, challengeIndex, logEvent) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | @ -1186,9 +1214,17 @@ func prepAccountForDisplay(acct *core.Registration) { | |||
| // prepChallengeForDisplay takes a core.Challenge and prepares it for display to
 | ||||
| // the client by filling in its URL field and clearing several unnecessary
 | ||||
| // fields.
 | ||||
| func (wfe *WebFrontEndImpl) prepChallengeForDisplay(request *http.Request, authz core.Authorization, challenge *core.Challenge) { | ||||
| func (wfe *WebFrontEndImpl) prepChallengeForDisplay( | ||||
| 	handlerPath string, | ||||
| 	request *http.Request, | ||||
| 	authz core.Authorization, | ||||
| 	challenge *core.Challenge, | ||||
| ) { | ||||
| 	// Update the challenge URL to be relative to the HTTP request Host
 | ||||
| 	challenge.URL = web.RelativeEndpoint(request, fmt.Sprintf("%s%s/%s", challengePath, authz.ID, challenge.StringID())) | ||||
| 	if handlerPath == challengePathWithAcct || handlerPath == authzPathWithAcct { | ||||
| 		challenge.URL = web.RelativeEndpoint(request, fmt.Sprintf("%s%d/%s/%s", challengePathWithAcct, authz.RegistrationID, authz.ID, challenge.StringID())) | ||||
| 	} | ||||
| 
 | ||||
| 	// Internally, we store challenge error problems with just the short form
 | ||||
| 	// (e.g. "CAA") of the problem type. But for external display, we need to
 | ||||
|  | @ -1211,9 +1247,9 @@ func (wfe *WebFrontEndImpl) prepChallengeForDisplay(request *http.Request, authz | |||
| 
 | ||||
| // prepAuthorizationForDisplay takes a core.Authorization and prepares it for
 | ||||
| // display to the client by preparing all its challenges.
 | ||||
| func (wfe *WebFrontEndImpl) prepAuthorizationForDisplay(request *http.Request, authz *core.Authorization) { | ||||
| func (wfe *WebFrontEndImpl) prepAuthorizationForDisplay(handlerPath string, request *http.Request, authz *core.Authorization) { | ||||
| 	for i := range authz.Challenges { | ||||
| 		wfe.prepChallengeForDisplay(request, *authz, &authz.Challenges[i]) | ||||
| 		wfe.prepChallengeForDisplay(handlerPath, request, *authz, &authz.Challenges[i]) | ||||
| 	} | ||||
| 
 | ||||
| 	// Shuffle the challenges so no one relies on their order.
 | ||||
|  | @ -1235,15 +1271,15 @@ func (wfe *WebFrontEndImpl) prepAuthorizationForDisplay(request *http.Request, a | |||
| } | ||||
| 
 | ||||
| func (wfe *WebFrontEndImpl) getChallenge( | ||||
| 	handlerPath string, | ||||
| 	response http.ResponseWriter, | ||||
| 	request *http.Request, | ||||
| 	authz core.Authorization, | ||||
| 	challenge *core.Challenge, | ||||
| 	logEvent *web.RequestEvent) { | ||||
| 	wfe.prepChallengeForDisplay(handlerPath, request, authz, challenge) | ||||
| 
 | ||||
| 	wfe.prepChallengeForDisplay(request, authz, challenge) | ||||
| 
 | ||||
| 	authzURL := urlForAuthz(authz, request) | ||||
| 	authzURL := urlForAuthz(handlerPath, authz, request) | ||||
| 	response.Header().Add("Location", challenge.URL) | ||||
| 	response.Header().Add("Link", link(authzURL, "up")) | ||||
| 
 | ||||
|  | @ -1258,6 +1294,7 @@ func (wfe *WebFrontEndImpl) getChallenge( | |||
| 
 | ||||
| func (wfe *WebFrontEndImpl) postChallenge( | ||||
| 	ctx context.Context, | ||||
| 	handlerPath string, | ||||
| 	response http.ResponseWriter, | ||||
| 	request *http.Request, | ||||
| 	authz core.Authorization, | ||||
|  | @ -1286,7 +1323,7 @@ func (wfe *WebFrontEndImpl) postChallenge( | |||
| 	// challenge details, not a POST to initiate a challenge
 | ||||
| 	if string(body) == "" { | ||||
| 		challenge := authz.Challenges[challengeIndex] | ||||
| 		wfe.getChallenge(response, request, authz, &challenge, logEvent) | ||||
| 		wfe.getChallenge(handlerPath, response, request, authz, &challenge, logEvent) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
|  | @ -1336,9 +1373,9 @@ func (wfe *WebFrontEndImpl) postChallenge( | |||
| 
 | ||||
| 	// assumption: PerformValidation does not modify order of challenges
 | ||||
| 	challenge := returnAuthz.Challenges[challengeIndex] | ||||
| 	wfe.prepChallengeForDisplay(request, authz, &challenge) | ||||
| 	wfe.prepChallengeForDisplay(handlerPath, request, authz, &challenge) | ||||
| 
 | ||||
| 	authzURL := urlForAuthz(authz, request) | ||||
| 	authzURL := urlForAuthz(handlerPath, authz, request) | ||||
| 	response.Header().Add("Location", challenge.URL) | ||||
| 	response.Header().Add("Link", link(authzURL, "up")) | ||||
| 
 | ||||
|  | @ -1524,11 +1561,39 @@ func (wfe *WebFrontEndImpl) deactivateAuthorization( | |||
| 	return true | ||||
| } | ||||
| 
 | ||||
| func (wfe *WebFrontEndImpl) Authorization( | ||||
| // AuthorizationHandler handles requests to authorization URLs of the form /acme/authz/{authzID}.
 | ||||
| func (wfe *WebFrontEndImpl) AuthorizationHandler( | ||||
| 	ctx context.Context, | ||||
| 	logEvent *web.RequestEvent, | ||||
| 	response http.ResponseWriter, | ||||
| 	request *http.Request) { | ||||
| 	wfe.Authorization(ctx, authzPath, logEvent, response, request, request.URL.Path) | ||||
| } | ||||
| 
 | ||||
| // AuthorizationHandlerWithAccount handles requests to authorization URLs of the form /acme/authz/{regID}/{authzID}.
 | ||||
| func (wfe *WebFrontEndImpl) AuthorizationHandlerWithAccount( | ||||
| 	ctx context.Context, | ||||
| 	logEvent *web.RequestEvent, | ||||
| 	response http.ResponseWriter, | ||||
| 	request *http.Request) { | ||||
| 	slug := strings.Split(request.URL.Path, "/") | ||||
| 	if len(slug) != 2 { | ||||
| 		wfe.sendError(response, logEvent, probs.NotFound("No such authorization"), nil) | ||||
| 		return | ||||
| 	} | ||||
| 	// TODO(#7683): The regID is currently ignored.
 | ||||
| 	wfe.Authorization(ctx, authzPathWithAcct, logEvent, response, request, slug[1]) | ||||
| } | ||||
| 
 | ||||
| // Authorization handles both `/acme/authz/{authzID}` and `/acme/authz/{regID}/{authzID}` requests,
 | ||||
| // after the calling function has parsed out the authzID.
 | ||||
| func (wfe *WebFrontEndImpl) Authorization( | ||||
| 	ctx context.Context, | ||||
| 	handlerPath string, | ||||
| 	logEvent *web.RequestEvent, | ||||
| 	response http.ResponseWriter, | ||||
| 	request *http.Request, | ||||
| 	authzIDStr string) { | ||||
| 	var requestAccount *core.Registration | ||||
| 	var requestBody []byte | ||||
| 	// If the request is a POST it is either:
 | ||||
|  | @ -1546,7 +1611,7 @@ func (wfe *WebFrontEndImpl) Authorization( | |||
| 		requestBody = body | ||||
| 	} | ||||
| 
 | ||||
| 	authzID, err := strconv.ParseInt(request.URL.Path, 10, 64) | ||||
| 	authzID, err := strconv.ParseInt(authzIDStr, 10, 64) | ||||
| 	if err != nil { | ||||
| 		wfe.sendError(response, logEvent, probs.Malformed("Invalid authorization ID"), nil) | ||||
| 		return | ||||
|  | @ -1615,7 +1680,7 @@ func (wfe *WebFrontEndImpl) Authorization( | |||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	wfe.prepAuthorizationForDisplay(request, &authz) | ||||
| 	wfe.prepAuthorizationForDisplay(handlerPath, request, &authz) | ||||
| 
 | ||||
| 	err = wfe.writeJsonResponse(response, logEvent, http.StatusOK, authz) | ||||
| 	if err != nil { | ||||
|  | @ -2731,6 +2796,10 @@ func extractRequesterIP(req *http.Request) (net.IP, error) { | |||
| 	return net.ParseIP(host), nil | ||||
| } | ||||
| 
 | ||||
| func urlForAuthz(authz core.Authorization, request *http.Request) string { | ||||
| func urlForAuthz(handlerPath string, authz core.Authorization, request *http.Request) string { | ||||
| 	if handlerPath == challengePathWithAcct || handlerPath == authzPathWithAcct { | ||||
| 		return web.RelativeEndpoint(request, fmt.Sprintf("%s%d/%s", authzPathWithAcct, authz.RegistrationID, authz.ID)) | ||||
| 	} | ||||
| 
 | ||||
| 	return web.RelativeEndpoint(request, authzPath+authz.ID) | ||||
| } | ||||
|  |  | |||
							
								
								
									
										463
									
								
								wfe2/wfe_test.go
								
								
								
								
							
							
						
						
									
										463
									
								
								wfe2/wfe_test.go
								
								
								
								
							|  | @ -1164,7 +1164,7 @@ func TestHTTPMethods(t *testing.T) { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestGetChallenge(t *testing.T) { | ||||
| func TestGetChallengeHandler(t *testing.T) { | ||||
| 	wfe, _, _ := setupWFE(t) | ||||
| 
 | ||||
| 	// The slug "7TyhFQ" is the StringID of a challenge with type "http-01" and
 | ||||
|  | @ -1181,7 +1181,7 @@ func TestGetChallenge(t *testing.T) { | |||
| 		test.AssertNotError(t, err, "Could not make NewRequest") | ||||
| 		req.URL.Path = fmt.Sprintf("1/%s", challSlug) | ||||
| 
 | ||||
| 		wfe.Challenge(ctx, newRequestEvent(), resp, req) | ||||
| 		wfe.ChallengeHandler(ctx, newRequestEvent(), resp, req) | ||||
| 		test.AssertEquals(t, resp.Code, http.StatusOK) | ||||
| 		test.AssertEquals(t, resp.Header().Get("Location"), challengeURL) | ||||
| 		test.AssertEquals(t, resp.Header().Get("Content-Type"), "application/json") | ||||
|  | @ -1198,7 +1198,41 @@ func TestGetChallenge(t *testing.T) { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestChallenge(t *testing.T) { | ||||
| func TestGetChallengeHandlerWithAccount(t *testing.T) { | ||||
| 	wfe, _, _ := setupWFE(t) | ||||
| 
 | ||||
| 	// The slug "7TyhFQ" is the StringID of a challenge with type "http-01" and
 | ||||
| 	// token "token".
 | ||||
| 	challSlug := "7TyhFQ" | ||||
| 
 | ||||
| 	for _, method := range []string{"GET", "HEAD"} { | ||||
| 		resp := httptest.NewRecorder() | ||||
| 
 | ||||
| 		// We set req.URL.Path separately to emulate the path-stripping that
 | ||||
| 		// Boulder's request handler does.
 | ||||
| 		challengeURL := fmt.Sprintf("http://localhost/acme/chall/1/1/%s", challSlug) | ||||
| 		req, err := http.NewRequest(method, challengeURL, nil) | ||||
| 		test.AssertNotError(t, err, "Could not make NewRequest") | ||||
| 		req.URL.Path = fmt.Sprintf("1/1/%s", challSlug) | ||||
| 
 | ||||
| 		wfe.ChallengeHandlerWithAccount(ctx, newRequestEvent(), resp, req) | ||||
| 		test.AssertEquals(t, resp.Code, http.StatusOK) | ||||
| 		test.AssertEquals(t, resp.Header().Get("Location"), challengeURL) | ||||
| 		test.AssertEquals(t, resp.Header().Get("Content-Type"), "application/json") | ||||
| 		test.AssertEquals(t, resp.Header().Get("Link"), `<http://localhost/acme/authz/1/1>;rel="up"`) | ||||
| 
 | ||||
| 		// Body is only relevant for GET. For HEAD, body will
 | ||||
| 		// be discarded by HandleFunc() anyway, so it doesn't
 | ||||
| 		// matter what Challenge() writes to it.
 | ||||
| 		if method == "GET" { | ||||
| 			test.AssertUnmarshaledEquals( | ||||
| 				t, resp.Body.String(), | ||||
| 				`{"status": "valid", "type":"http-01","token":"token","url":"http://localhost/acme/chall/1/1/7TyhFQ"}`) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestChallengeHandler(t *testing.T) { | ||||
| 	wfe, _, signer := setupWFE(t) | ||||
| 
 | ||||
| 	post := func(path string) *http.Request { | ||||
|  | @ -1264,7 +1298,86 @@ func TestChallenge(t *testing.T) { | |||
| 	for _, tc := range testCases { | ||||
| 		t.Run(tc.Name, func(t *testing.T) { | ||||
| 			responseWriter := httptest.NewRecorder() | ||||
| 			wfe.Challenge(ctx, newRequestEvent(), responseWriter, tc.Request) | ||||
| 			wfe.ChallengeHandler(ctx, newRequestEvent(), responseWriter, tc.Request) | ||||
| 			// Check the response code, headers and body match expected
 | ||||
| 			headers := responseWriter.Header() | ||||
| 			body := responseWriter.Body.String() | ||||
| 			test.AssertEquals(t, responseWriter.Code, tc.ExpectedStatus) | ||||
| 			for h, v := range tc.ExpectedHeaders { | ||||
| 				test.AssertEquals(t, headers.Get(h), v) | ||||
| 			} | ||||
| 			test.AssertUnmarshaledEquals(t, body, tc.ExpectedBody) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestChallengeHandlerWithAccount(t *testing.T) { | ||||
| 	wfe, _, signer := setupWFE(t) | ||||
| 
 | ||||
| 	post := func(path string) *http.Request { | ||||
| 		signedURL := fmt.Sprintf("http://localhost/%s", path) | ||||
| 		_, _, jwsBody := signer.byKeyID(1, nil, signedURL, `{}`) | ||||
| 		return makePostRequestWithPath(path, jwsBody) | ||||
| 	} | ||||
| 	postAsGet := func(keyID int64, path, body string) *http.Request { | ||||
| 		_, _, jwsBody := signer.byKeyID(keyID, nil, fmt.Sprintf("http://localhost/%s", path), body) | ||||
| 		return makePostRequestWithPath(path, jwsBody) | ||||
| 	} | ||||
| 
 | ||||
| 	testCases := []struct { | ||||
| 		Name            string | ||||
| 		Request         *http.Request | ||||
| 		ExpectedStatus  int | ||||
| 		ExpectedHeaders map[string]string | ||||
| 		ExpectedBody    string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			Name:           "Valid challenge", | ||||
| 			Request:        post("1/1/7TyhFQ"), | ||||
| 			ExpectedStatus: http.StatusOK, | ||||
| 			ExpectedHeaders: map[string]string{ | ||||
| 				"Content-Type": "application/json", | ||||
| 				"Location":     "http://localhost/acme/chall/1/1/7TyhFQ", | ||||
| 				"Link":         `<http://localhost/acme/authz/1/1>;rel="up"`, | ||||
| 			}, | ||||
| 			ExpectedBody: `{"status": "valid", "type":"http-01","token":"token","url":"http://localhost/acme/chall/1/1/7TyhFQ"}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:           "Expired challenge", | ||||
| 			Request:        post("1/3/7TyhFQ"), | ||||
| 			ExpectedStatus: http.StatusNotFound, | ||||
| 			ExpectedBody:   `{"type":"` + probs.ErrorNS + `malformed","detail":"Expired authorization","status":404}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:           "Missing challenge", | ||||
| 			Request:        post("1/1/"), | ||||
| 			ExpectedStatus: http.StatusNotFound, | ||||
| 			ExpectedBody:   `{"type":"` + probs.ErrorNS + `malformed","detail":"No such challenge","status":404}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:           "Unspecified database error", | ||||
| 			Request:        post("1/4/7TyhFQ"), | ||||
| 			ExpectedStatus: http.StatusInternalServerError, | ||||
| 			ExpectedBody:   `{"type":"` + probs.ErrorNS + `serverInternal","detail":"Problem getting authorization","status":500}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:           "POST-as-GET, wrong owner", | ||||
| 			Request:        postAsGet(1, "1/5/7TyhFQ", ""), | ||||
| 			ExpectedStatus: http.StatusForbidden, | ||||
| 			ExpectedBody:   `{"type":"` + probs.ErrorNS + `unauthorized","detail":"User account ID doesn't match account ID in authorization","status":403}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:           "Valid POST-as-GET", | ||||
| 			Request:        postAsGet(1, "1/1/7TyhFQ", ""), | ||||
| 			ExpectedStatus: http.StatusOK, | ||||
| 			ExpectedBody:   `{"status": "valid", "type":"http-01", "token":"token", "url": "http://localhost/acme/chall/1/1/7TyhFQ"}`, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, tc := range testCases { | ||||
| 		t.Run(tc.Name, func(t *testing.T) { | ||||
| 			responseWriter := httptest.NewRecorder() | ||||
| 			wfe.ChallengeHandlerWithAccount(ctx, newRequestEvent(), responseWriter, tc.Request) | ||||
| 			// Check the response code, headers and body match expected
 | ||||
| 			headers := responseWriter.Header() | ||||
| 			body := responseWriter.Body.String() | ||||
|  | @ -1287,10 +1400,10 @@ func (ra *MockRAPerformValidationError) PerformValidation(context.Context, *rapb | |||
| 	return nil, errors.New("broken on purpose") | ||||
| } | ||||
| 
 | ||||
| // TestUpdateChallengeFinalizedAuthz tests that POSTing a challenge associated
 | ||||
| // TestUpdateChallengeHandlerFinalizedAuthz tests that POSTing a challenge associated
 | ||||
| // with an already valid authorization just returns the challenge without calling
 | ||||
| // the RA.
 | ||||
| func TestUpdateChallengeFinalizedAuthz(t *testing.T) { | ||||
| func TestUpdateChallengeHandlerFinalizedAuthz(t *testing.T) { | ||||
| 	wfe, fc, signer := setupWFE(t) | ||||
| 	wfe.ra = &MockRAPerformValidationError{MockRegistrationAuthority{clk: fc}} | ||||
| 	responseWriter := httptest.NewRecorder() | ||||
|  | @ -1298,7 +1411,7 @@ func TestUpdateChallengeFinalizedAuthz(t *testing.T) { | |||
| 	signedURL := "http://localhost/1/7TyhFQ" | ||||
| 	_, _, jwsBody := signer.byKeyID(1, nil, signedURL, `{}`) | ||||
| 	request := makePostRequestWithPath("1/7TyhFQ", jwsBody) | ||||
| 	wfe.Challenge(ctx, newRequestEvent(), responseWriter, request) | ||||
| 	wfe.ChallengeHandler(ctx, newRequestEvent(), responseWriter, request) | ||||
| 
 | ||||
| 	body := responseWriter.Body.String() | ||||
| 	test.AssertUnmarshaledEquals(t, body, `{ | ||||
|  | @ -1309,10 +1422,32 @@ func TestUpdateChallengeFinalizedAuthz(t *testing.T) { | |||
| 	  }`) | ||||
| } | ||||
| 
 | ||||
| // TestUpdateChallengeRAError tests that when the RA returns an error from
 | ||||
| // TestUpdateChallengeHandlerWithAccountFinalizedAuthz tests that POSTing a challenge associated
 | ||||
| // with an already valid authorization just returns the challenge without calling
 | ||||
| // the RA.
 | ||||
| func TestUpdateChallengeHandlerWithAccountFinalizedAuthz(t *testing.T) { | ||||
| 	wfe, fc, signer := setupWFE(t) | ||||
| 	wfe.ra = &MockRAPerformValidationError{MockRegistrationAuthority{clk: fc}} | ||||
| 	responseWriter := httptest.NewRecorder() | ||||
| 
 | ||||
| 	signedURL := "http://localhost/1/1/7TyhFQ" | ||||
| 	_, _, jwsBody := signer.byKeyID(1, nil, signedURL, `{}`) | ||||
| 	request := makePostRequestWithPath("1/1/7TyhFQ", jwsBody) | ||||
| 	wfe.ChallengeHandlerWithAccount(ctx, newRequestEvent(), responseWriter, request) | ||||
| 
 | ||||
| 	body := responseWriter.Body.String() | ||||
| 	test.AssertUnmarshaledEquals(t, body, `{ | ||||
| 	  "status": "valid", | ||||
| 		"type": "http-01", | ||||
| 		"token": "token", | ||||
| 		"url": "http://localhost/acme/chall/1/1/7TyhFQ" | ||||
| 	  }`) | ||||
| } | ||||
| 
 | ||||
| // TestUpdateChallengeHandlerRAError tests that when the RA returns an error from
 | ||||
| // PerformValidation that the WFE returns an internal server error as expected
 | ||||
| // and does not panic or otherwise bug out.
 | ||||
| func TestUpdateChallengeRAError(t *testing.T) { | ||||
| func TestUpdateChallengeHandlerRAError(t *testing.T) { | ||||
| 	wfe, fc, signer := setupWFE(t) | ||||
| 	// Mock the RA to always fail PerformValidation
 | ||||
| 	wfe.ra = &MockRAPerformValidationError{MockRegistrationAuthority{clk: fc}} | ||||
|  | @ -1323,7 +1458,32 @@ func TestUpdateChallengeRAError(t *testing.T) { | |||
| 	responseWriter := httptest.NewRecorder() | ||||
| 	request := makePostRequestWithPath("2/7TyhFQ", jwsBody) | ||||
| 
 | ||||
| 	wfe.Challenge(ctx, newRequestEvent(), responseWriter, request) | ||||
| 	wfe.ChallengeHandler(ctx, newRequestEvent(), responseWriter, request) | ||||
| 
 | ||||
| 	// The result should be an internal server error problem.
 | ||||
| 	body := responseWriter.Body.String() | ||||
| 	test.AssertUnmarshaledEquals(t, body, `{ | ||||
| 		"type": "urn:ietf:params:acme:error:serverInternal", | ||||
| 	  "detail": "Unable to update challenge", | ||||
| 		"status": 500 | ||||
| 	}`) | ||||
| } | ||||
| 
 | ||||
| // TestUpdateChallengeHandlerWithAccountRAError tests that when the RA returns an error from
 | ||||
| // PerformValidation that the WFE returns an internal server error as expected
 | ||||
| // and does not panic or otherwise bug out.
 | ||||
| func TestUpdateChallengeHandlerWithAccountRAError(t *testing.T) { | ||||
| 	wfe, fc, signer := setupWFE(t) | ||||
| 	// Mock the RA to always fail PerformValidation
 | ||||
| 	wfe.ra = &MockRAPerformValidationError{MockRegistrationAuthority{clk: fc}} | ||||
| 
 | ||||
| 	// Update a pending challenge
 | ||||
| 	signedURL := "http://localhost/1/2/7TyhFQ" | ||||
| 	_, _, jwsBody := signer.byKeyID(1, nil, signedURL, `{}`) | ||||
| 	responseWriter := httptest.NewRecorder() | ||||
| 	request := makePostRequestWithPath("1/2/7TyhFQ", jwsBody) | ||||
| 
 | ||||
| 	wfe.ChallengeHandlerWithAccount(ctx, newRequestEvent(), responseWriter, request) | ||||
| 
 | ||||
| 	// The result should be an internal server error problem.
 | ||||
| 	body := responseWriter.Body.String() | ||||
|  | @ -1640,13 +1800,13 @@ func TestNewAccountNoID(t *testing.T) { | |||
| 	}`) | ||||
| } | ||||
| 
 | ||||
| func TestGetAuthorization(t *testing.T) { | ||||
| func TestGetAuthorizationHandler(t *testing.T) { | ||||
| 	wfe, _, signer := setupWFE(t) | ||||
| 
 | ||||
| 	// Expired authorizations should be inaccessible
 | ||||
| 	authzURL := "3" | ||||
| 	responseWriter := httptest.NewRecorder() | ||||
| 	wfe.Authorization(ctx, newRequestEvent(), responseWriter, &http.Request{ | ||||
| 	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, &http.Request{ | ||||
| 		Method: "GET", | ||||
| 		URL:    mustParseURL(authzURL), | ||||
| 	}) | ||||
|  | @ -1656,7 +1816,7 @@ func TestGetAuthorization(t *testing.T) { | |||
| 	responseWriter.Body.Reset() | ||||
| 
 | ||||
| 	// Ensure that a valid authorization can't be reached with an invalid URL
 | ||||
| 	wfe.Authorization(ctx, newRequestEvent(), responseWriter, &http.Request{ | ||||
| 	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, &http.Request{ | ||||
| 		URL:    mustParseURL("1d"), | ||||
| 		Method: "GET", | ||||
| 	}) | ||||
|  | @ -1668,7 +1828,7 @@ func TestGetAuthorization(t *testing.T) { | |||
| 
 | ||||
| 	responseWriter = httptest.NewRecorder() | ||||
| 	// Ensure that a POST-as-GET to an authorization works
 | ||||
| 	wfe.Authorization(ctx, newRequestEvent(), responseWriter, postAsGet) | ||||
| 	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, postAsGet) | ||||
| 	test.AssertEquals(t, responseWriter.Code, http.StatusOK) | ||||
| 	body := responseWriter.Body.String() | ||||
| 	test.AssertUnmarshaledEquals(t, body, ` | ||||
|  | @ -1690,13 +1850,63 @@ func TestGetAuthorization(t *testing.T) { | |||
| 	}`) | ||||
| } | ||||
| 
 | ||||
| // TestAuthorization500 tests that internal errors on GetAuthorization result in
 | ||||
| func TestGetAuthorizationHandlerWithAccount(t *testing.T) { | ||||
| 	wfe, _, signer := setupWFE(t) | ||||
| 
 | ||||
| 	// Expired authorizations should be inaccessible
 | ||||
| 	authzURL := "1/3" | ||||
| 	responseWriter := httptest.NewRecorder() | ||||
| 	wfe.AuthorizationHandlerWithAccount(ctx, newRequestEvent(), responseWriter, &http.Request{ | ||||
| 		Method: "GET", | ||||
| 		URL:    mustParseURL(authzURL), | ||||
| 	}) | ||||
| 	test.AssertEquals(t, responseWriter.Code, http.StatusNotFound) | ||||
| 	test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), | ||||
| 		`{"type":"`+probs.ErrorNS+`malformed","detail":"Expired authorization","status":404}`) | ||||
| 	responseWriter.Body.Reset() | ||||
| 
 | ||||
| 	// Ensure that a valid authorization can't be reached with an invalid URL
 | ||||
| 	wfe.AuthorizationHandlerWithAccount(ctx, newRequestEvent(), responseWriter, &http.Request{ | ||||
| 		URL:    mustParseURL("1/1d"), | ||||
| 		Method: "GET", | ||||
| 	}) | ||||
| 	test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), | ||||
| 		`{"type":"`+probs.ErrorNS+`malformed","detail":"Invalid authorization ID","status":400}`) | ||||
| 
 | ||||
| 	_, _, jwsBody := signer.byKeyID(1, nil, "http://localhost/1/1", "") | ||||
| 	postAsGet := makePostRequestWithPath("1/1", jwsBody) | ||||
| 
 | ||||
| 	responseWriter = httptest.NewRecorder() | ||||
| 	// Ensure that a POST-as-GET to an authorization works
 | ||||
| 	wfe.AuthorizationHandlerWithAccount(ctx, newRequestEvent(), responseWriter, postAsGet) | ||||
| 	test.AssertEquals(t, responseWriter.Code, http.StatusOK) | ||||
| 	body := responseWriter.Body.String() | ||||
| 	test.AssertUnmarshaledEquals(t, body, ` | ||||
| 	{ | ||||
| 		"identifier": { | ||||
| 			"type": "dns", | ||||
| 			"value": "not-an-example.com" | ||||
| 		}, | ||||
| 		"status": "valid", | ||||
| 		"expires": "2070-01-01T00:00:00Z", | ||||
| 		"challenges": [ | ||||
| 			{ | ||||
| 			  "status": "valid", | ||||
| 				"type": "http-01", | ||||
| 				"token":"token", | ||||
| 				"url": "http://localhost/acme/chall/1/1/7TyhFQ" | ||||
| 			} | ||||
| 		] | ||||
| 	}`) | ||||
| } | ||||
| 
 | ||||
| // TestAuthorizationHandler500 tests that internal errors on GetAuthorization result in
 | ||||
| // a 500.
 | ||||
| func TestAuthorization500(t *testing.T) { | ||||
| func TestAuthorizationHandler500(t *testing.T) { | ||||
| 	wfe, _, _ := setupWFE(t) | ||||
| 
 | ||||
| 	responseWriter := httptest.NewRecorder() | ||||
| 	wfe.Authorization(ctx, newRequestEvent(), responseWriter, &http.Request{ | ||||
| 	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, &http.Request{ | ||||
| 		Method: "GET", | ||||
| 		URL:    mustParseURL("4"), | ||||
| 	}) | ||||
|  | @ -1708,6 +1918,24 @@ func TestAuthorization500(t *testing.T) { | |||
| 	test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), expected) | ||||
| } | ||||
| 
 | ||||
| // TestAuthorizationHandlerWithAccount500 tests that internal errors on GetAuthorization result in
 | ||||
| // a 500.
 | ||||
| func TestAuthorizationHandlerWithAccount500(t *testing.T) { | ||||
| 	wfe, _, _ := setupWFE(t) | ||||
| 
 | ||||
| 	responseWriter := httptest.NewRecorder() | ||||
| 	wfe.AuthorizationHandlerWithAccount(ctx, newRequestEvent(), responseWriter, &http.Request{ | ||||
| 		Method: "GET", | ||||
| 		URL:    mustParseURL("1/4"), | ||||
| 	}) | ||||
| 	expected := `{ | ||||
|          "type": "urn:ietf:params:acme:error:serverInternal", | ||||
| 				 "detail": "Problem getting authorization", | ||||
| 				 "status": 500 | ||||
|   }` | ||||
| 	test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), expected) | ||||
| } | ||||
| 
 | ||||
| // RAWithFailedChallenges is a fake RA whose GetAuthorization method returns
 | ||||
| // an authz with a failed challenge.
 | ||||
| type RAWithFailedChallenge struct { | ||||
|  | @ -1738,14 +1966,35 @@ func (ra *RAWithFailedChallenge) GetAuthorization(ctx context.Context, id *rapb. | |||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| // TestAuthorizationChallengeNamespace tests that the runtime prefixing of
 | ||||
| // TestAuthorizationChallengeHandlerNamespace tests that the runtime prefixing of
 | ||||
| // Challenge Problem Types works as expected
 | ||||
| func TestAuthorizationChallengeNamespace(t *testing.T) { | ||||
| func TestAuthorizationChallengeHandlerNamespace(t *testing.T) { | ||||
| 	wfe, clk, _ := setupWFE(t) | ||||
| 	wfe.ra = &RAWithFailedChallenge{clk: clk} | ||||
| 
 | ||||
| 	responseWriter := httptest.NewRecorder() | ||||
| 	wfe.Authorization(ctx, newRequestEvent(), responseWriter, &http.Request{ | ||||
| 	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, &http.Request{ | ||||
| 		Method: "GET", | ||||
| 		URL:    mustParseURL("6"), | ||||
| 	}) | ||||
| 
 | ||||
| 	var authz core.Authorization | ||||
| 	err := json.Unmarshal(responseWriter.Body.Bytes(), &authz) | ||||
| 	test.AssertNotError(t, err, "Couldn't unmarshal returned authorization object") | ||||
| 	test.AssertEquals(t, len(authz.Challenges), 1) | ||||
| 	// The Challenge Error Type should have had the probs.ErrorNS prefix added
 | ||||
| 	test.AssertEquals(t, string(authz.Challenges[0].Error.Type), probs.ErrorNS+"things:are:whack") | ||||
| 	responseWriter.Body.Reset() | ||||
| } | ||||
| 
 | ||||
| // TestAuthorizationChallengeHandlerWithAccountNamespace tests that the runtime prefixing of
 | ||||
| // Challenge Problem Types works as expected
 | ||||
| func TestAuthorizationChallengeHandlerWithAccountNamespace(t *testing.T) { | ||||
| 	wfe, clk, _ := setupWFE(t) | ||||
| 	wfe.ra = &RAWithFailedChallenge{clk: clk} | ||||
| 
 | ||||
| 	responseWriter := httptest.NewRecorder() | ||||
| 	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, &http.Request{ | ||||
| 		Method: "GET", | ||||
| 		URL:    mustParseURL("6"), | ||||
| 	}) | ||||
|  | @ -2392,7 +2641,7 @@ func TestHeaderBoulderRequester(t *testing.T) { | |||
| 	test.AssertEquals(t, responseWriter.Header().Get("Boulder-Requester"), "1") | ||||
| } | ||||
| 
 | ||||
| func TestDeactivateAuthorization(t *testing.T) { | ||||
| func TestDeactivateAuthorizationHandler(t *testing.T) { | ||||
| 	wfe, _, signer := setupWFE(t) | ||||
| 	responseWriter := httptest.NewRecorder() | ||||
| 
 | ||||
|  | @ -2402,7 +2651,7 @@ func TestDeactivateAuthorization(t *testing.T) { | |||
| 	_, _, body := signer.byKeyID(1, nil, "http://localhost/1", payload) | ||||
| 	request := makePostRequestWithPath("1", body) | ||||
| 
 | ||||
| 	wfe.Authorization(ctx, newRequestEvent(), responseWriter, request) | ||||
| 	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, request) | ||||
| 	test.AssertUnmarshaledEquals(t, | ||||
| 		responseWriter.Body.String(), | ||||
| 		`{"type": "`+probs.ErrorNS+`malformed","detail": "Invalid status value","status": 400}`) | ||||
|  | @ -2412,7 +2661,48 @@ func TestDeactivateAuthorization(t *testing.T) { | |||
| 	_, _, body = signer.byKeyID(1, nil, "http://localhost/1", payload) | ||||
| 	request = makePostRequestWithPath("1", body) | ||||
| 
 | ||||
| 	wfe.Authorization(ctx, newRequestEvent(), responseWriter, request) | ||||
| 	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, request) | ||||
| 	test.AssertUnmarshaledEquals(t, | ||||
| 		responseWriter.Body.String(), | ||||
| 		`{ | ||||
| 		  "identifier": { | ||||
| 		    "type": "dns", | ||||
| 		    "value": "not-an-example.com" | ||||
| 		  }, | ||||
| 		  "status": "deactivated", | ||||
| 		  "expires": "2070-01-01T00:00:00Z", | ||||
| 		  "challenges": [ | ||||
| 		    { | ||||
| 					"status": "valid", | ||||
| 					"type": "http-01", | ||||
| 					"token": "token", | ||||
| 					"url": "http://localhost/acme/chall-v3/1/7TyhFQ" | ||||
| 		    } | ||||
| 		  ] | ||||
| 		}`) | ||||
| } | ||||
| 
 | ||||
| func TestDeactivateAuthorizationHandlerWithAccount(t *testing.T) { | ||||
| 	wfe, _, signer := setupWFE(t) | ||||
| 	responseWriter := httptest.NewRecorder() | ||||
| 
 | ||||
| 	responseWriter.Body.Reset() | ||||
| 
 | ||||
| 	payload := `{"status":""}` | ||||
| 	_, _, body := signer.byKeyID(1, nil, "http://localhost/1", payload) | ||||
| 	request := makePostRequestWithPath("1", body) | ||||
| 
 | ||||
| 	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, request) | ||||
| 	test.AssertUnmarshaledEquals(t, | ||||
| 		responseWriter.Body.String(), | ||||
| 		`{"type": "`+probs.ErrorNS+`malformed","detail": "Invalid status value","status": 400}`) | ||||
| 
 | ||||
| 	responseWriter.Body.Reset() | ||||
| 	payload = `{"status":"deactivated"}` | ||||
| 	_, _, body = signer.byKeyID(1, nil, "http://localhost/1", payload) | ||||
| 	request = makePostRequestWithPath("1", body) | ||||
| 
 | ||||
| 	wfe.AuthorizationHandler(ctx, newRequestEvent(), responseWriter, request) | ||||
| 	test.AssertUnmarshaledEquals(t, | ||||
| 		responseWriter.Body.String(), | ||||
| 		`{ | ||||
|  | @ -3399,7 +3689,33 @@ func TestPrepAuthzForDisplay(t *testing.T) { | |||
| 	} | ||||
| 
 | ||||
| 	// This modifies the authz in-place.
 | ||||
| 	wfe.prepAuthorizationForDisplay(&http.Request{Host: "localhost"}, authz) | ||||
| 	wfe.prepAuthorizationForDisplay(authzPath, &http.Request{Host: "localhost"}, authz) | ||||
| 
 | ||||
| 	// Ensure ID and RegID are omitted.
 | ||||
| 	authzJSON, err := json.Marshal(authz) | ||||
| 	test.AssertNotError(t, err, "Failed to marshal authz") | ||||
| 	test.AssertNotContains(t, string(authzJSON), "\"id\":\"12345\"") | ||||
| 	test.AssertNotContains(t, string(authzJSON), "\"registrationID\":\"1\"") | ||||
| } | ||||
| 
 | ||||
| func TestPrepAuthzWithAccountForDisplay(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	wfe, _, _ := setupWFE(t) | ||||
| 
 | ||||
| 	authz := &core.Authorization{ | ||||
| 		ID:             "12345", | ||||
| 		Status:         core.StatusPending, | ||||
| 		RegistrationID: 1, | ||||
| 		Identifier:     identifier.NewDNS("example.com"), | ||||
| 		Challenges: []core.Challenge{ | ||||
| 			{Type: core.ChallengeTypeDNS01, Status: core.StatusPending, Token: "token"}, | ||||
| 			{Type: core.ChallengeTypeHTTP01, Status: core.StatusPending, Token: "token"}, | ||||
| 			{Type: core.ChallengeTypeTLSALPN01, Status: core.StatusPending, Token: "token"}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	// This modifies the authz in-place.
 | ||||
| 	wfe.prepAuthorizationForDisplay(authzPathWithAcct, &http.Request{Host: "localhost"}, authz) | ||||
| 
 | ||||
| 	// Ensure ID and RegID are omitted.
 | ||||
| 	authzJSON, err := json.Marshal(authz) | ||||
|  | @ -3425,7 +3741,32 @@ func TestPrepRevokedAuthzForDisplay(t *testing.T) { | |||
| 	} | ||||
| 
 | ||||
| 	// This modifies the authz in-place.
 | ||||
| 	wfe.prepAuthorizationForDisplay(&http.Request{Host: "localhost"}, authz) | ||||
| 	wfe.prepAuthorizationForDisplay(authzPath, &http.Request{Host: "localhost"}, authz) | ||||
| 
 | ||||
| 	// All of the challenges should be revoked as well.
 | ||||
| 	for _, chall := range authz.Challenges { | ||||
| 		test.AssertEquals(t, chall.Status, core.StatusInvalid) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestPrepRevokedAuthzWithAccountForDisplay(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	wfe, _, _ := setupWFE(t) | ||||
| 
 | ||||
| 	authz := &core.Authorization{ | ||||
| 		ID:             "12345", | ||||
| 		Status:         core.StatusInvalid, | ||||
| 		RegistrationID: 1, | ||||
| 		Identifier:     identifier.NewDNS("example.com"), | ||||
| 		Challenges: []core.Challenge{ | ||||
| 			{Type: core.ChallengeTypeDNS01, Status: core.StatusPending, Token: "token"}, | ||||
| 			{Type: core.ChallengeTypeHTTP01, Status: core.StatusPending, Token: "token"}, | ||||
| 			{Type: core.ChallengeTypeTLSALPN01, Status: core.StatusPending, Token: "token"}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	// This modifies the authz in-place.
 | ||||
| 	wfe.prepAuthorizationForDisplay(authzPathWithAcct, &http.Request{Host: "localhost"}, authz) | ||||
| 
 | ||||
| 	// All of the challenges should be revoked as well.
 | ||||
| 	for _, chall := range authz.Challenges { | ||||
|  | @ -3448,7 +3789,30 @@ func TestPrepWildcardAuthzForDisplay(t *testing.T) { | |||
| 	} | ||||
| 
 | ||||
| 	// This modifies the authz in-place.
 | ||||
| 	wfe.prepAuthorizationForDisplay(&http.Request{Host: "localhost"}, authz) | ||||
| 	wfe.prepAuthorizationForDisplay(authzPath, &http.Request{Host: "localhost"}, authz) | ||||
| 
 | ||||
| 	// The identifier should not start with a star, but the authz should be marked
 | ||||
| 	// as a wildcard.
 | ||||
| 	test.AssertEquals(t, strings.HasPrefix(authz.Identifier.Value, "*."), false) | ||||
| 	test.AssertEquals(t, authz.Wildcard, true) | ||||
| } | ||||
| 
 | ||||
| func TestPrepWildcardAuthzWithAcountForDisplay(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	wfe, _, _ := setupWFE(t) | ||||
| 
 | ||||
| 	authz := &core.Authorization{ | ||||
| 		ID:             "12345", | ||||
| 		Status:         core.StatusPending, | ||||
| 		RegistrationID: 1, | ||||
| 		Identifier:     identifier.NewDNS("*.example.com"), | ||||
| 		Challenges: []core.Challenge{ | ||||
| 			{Type: core.ChallengeTypeDNS01, Status: core.StatusPending, Token: "token"}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	// This modifies the authz in-place.
 | ||||
| 	wfe.prepAuthorizationForDisplay(authzPathWithAcct, &http.Request{Host: "localhost"}, authz) | ||||
| 
 | ||||
| 	// The identifier should not start with a star, but the authz should be marked
 | ||||
| 	// as a wildcard.
 | ||||
|  | @ -3484,7 +3848,7 @@ func TestPrepAuthzForDisplayShuffle(t *testing.T) { | |||
| 	// Prep the authz 100 times, and count where each challenge ended up each time.
 | ||||
| 	for range 100 { | ||||
| 		// This modifies the authz in place
 | ||||
| 		wfe.prepAuthorizationForDisplay(&http.Request{Host: "localhost"}, authz) | ||||
| 		wfe.prepAuthorizationForDisplay(challengePath, &http.Request{Host: "localhost"}, authz) | ||||
| 		for i, chall := range authz.Challenges { | ||||
| 			counts[chall.Type][i] += 1 | ||||
| 		} | ||||
|  | @ -3567,7 +3931,7 @@ func TestPrepAccountForDisplay(t *testing.T) { | |||
| 	test.AssertEquals(t, acct.ID, int64(0)) | ||||
| } | ||||
| 
 | ||||
| func TestGETAPIAuthz(t *testing.T) { | ||||
| func TestGETAPIAuthorizationHandler(t *testing.T) { | ||||
| 	wfe, _, _ := setupWFE(t) | ||||
| 	makeGet := func(path, endpoint string) (*http.Request, *web.RequestEvent) { | ||||
| 		return &http.Request{URL: &url.URL{Path: path}, Method: "GET"}, | ||||
|  | @ -3595,7 +3959,46 @@ func TestGETAPIAuthz(t *testing.T) { | |||
| 	for _, tc := range testCases { | ||||
| 		responseWriter := httptest.NewRecorder() | ||||
| 		req, logEvent := makeGet(tc.path, getAuthzPath) | ||||
| 		wfe.Authorization(context.Background(), logEvent, responseWriter, req) | ||||
| 		wfe.AuthorizationHandler(context.Background(), logEvent, responseWriter, req) | ||||
| 
 | ||||
| 		if responseWriter.Code == http.StatusOK && tc.expectTooFreshErr { | ||||
| 			t.Errorf("expected too fresh error, got http.StatusOK") | ||||
| 		} else { | ||||
| 			test.AssertEquals(t, responseWriter.Code, http.StatusForbidden) | ||||
| 			test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tooFreshErr) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestGETAPIAuthorizationHandlerWitAccount(t *testing.T) { | ||||
| 	wfe, _, _ := setupWFE(t) | ||||
| 	makeGet := func(path, endpoint string) (*http.Request, *web.RequestEvent) { | ||||
| 		return &http.Request{URL: &url.URL{Path: path}, Method: "GET"}, | ||||
| 			&web.RequestEvent{Endpoint: endpoint} | ||||
| 	} | ||||
| 
 | ||||
| 	testCases := []struct { | ||||
| 		name              string | ||||
| 		path              string | ||||
| 		expectTooFreshErr bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:              "fresh authz", | ||||
| 			path:              "1/1", | ||||
| 			expectTooFreshErr: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:              "old authz", | ||||
| 			path:              "1/2", | ||||
| 			expectTooFreshErr: false, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	tooFreshErr := `{"type":"` + probs.ErrorNS + `unauthorized","detail":"Authorization is too new for GET API. You should only use this non-standard API to access resources created more than 10s ago","status":403}` | ||||
| 	for _, tc := range testCases { | ||||
| 		responseWriter := httptest.NewRecorder() | ||||
| 		req, logEvent := makeGet(tc.path, getAuthzPath) | ||||
| 		wfe.AuthorizationHandlerWithAccount(context.Background(), logEvent, responseWriter, req) | ||||
| 
 | ||||
| 		if responseWriter.Code == http.StatusOK && tc.expectTooFreshErr { | ||||
| 			t.Errorf("expected too fresh error, got http.StatusOK") | ||||
|  | @ -3634,7 +4037,7 @@ func TestGETAPIChallenge(t *testing.T) { | |||
| 	for _, tc := range testCases { | ||||
| 		responseWriter := httptest.NewRecorder() | ||||
| 		req, logEvent := makeGet(tc.path, getAuthzPath) | ||||
| 		wfe.Challenge(context.Background(), logEvent, responseWriter, req) | ||||
| 		wfe.ChallengeHandler(context.Background(), logEvent, responseWriter, req) | ||||
| 
 | ||||
| 		if responseWriter.Code == http.StatusOK && tc.expectTooFreshErr { | ||||
| 			t.Errorf("expected too fresh error, got http.StatusOK") | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue