510 Not Extended during Analytics module setup
- Dominant language
- JavaScript
- Stars
- 1.4k
- Forks
- 383
- Avg merge
- 4d 14h
- Merged PRs (30d)
- 77
Description
## Bug Description
An "Invalid JSON response" error when setting up the Analytics module turns out to be due to a "security policy rule" and perhaps related to my ModSecurity settings. These are two known issues (linked below) that were closed even though they apparently still occur, confuse users and leave them without proper debugging techniques. In particular, googling the "Invalid JSON response" error alone leads to many entirely unrelated results.
## Steps to reproduce
(I don't think it will reproduce exactly in your environment, I presume the cause is somewhere in my Wordpress configs, still I would like the google-site-kit plugin to behave in a more user-friendly way to help debugging the root cause.)
1. I follow [these instructions](https://support.google.com/google-ads/answer/11898619).
2. In step 21 "Choose your pre-existing analytics property then click Configure Analytics.", upon clicking that button and thus upon submitting that form, it displays an error "Not a valid JSON response". #4997 (also reported as #5025) is closed and claims to have replaced all such unhelpful errors with something more helpful, but this doesn't seem to work here.
3. When I debug in DevTools, I get to see the real error:
```
POST https:///wp-json/google-site-kit/v1/modules/analytics/data/settings?_locale=user
510 Not Extended
Access denied by security policy
Access denied by security policy
Your request is blocked by a security policy rule.
Please contact the support team and inform them of the time the error occurred, and anything you might have done that may have caused the error.
More information about this error may be available in the server error log.
Please provide the following information to our support team:
{redacted}
```
Seeing the modsec deactivation link there makes me think this is an instance of #7165. That one was closed due to "no recent reports", so perhaps it should be reopened?
I'll investigate whether the server's ModSecurity settings need to be tuned. However, it shouldn't require such advanced debugging techniques to find this out. So:
1. google-site-kit should not display that "Invalid JSON" error. Instead, at a minimum, it should display "510 Not Extended" and "Access denied by security policy" from the actual response.
2. Bonus points for finding and returning that modsec deactivation link, together with a little explanation or pointer to #7165 on github.com or so.
## Additional Context
- PHP Version: sorry, I don't know this at the moment, as it's not my own server
- OS: Windows
- Browser: Chrome
- Plugin Version: The latest that was installable yesterday.
---------------
_Do not alter or remove anything below. The following sections will be managed by moderators only._
## Acceptance criteria
* When a Site Kit request fails because the server returned a response that is not valid JSON, the message shown to the user reads "The server provided an invalid response", on every screen that already shows that request's error.
* No raw HTML from the server's response is shown anywhere in the interface.
* The browser console entry for the failed request includes whatever details Site Kit could read from the response, such as the HTTP status and the title or heading of the page the server returned.
* When the server returned a ModSecurity block page, the console entry names ModSecurity and includes the rule deactivation link if the page contained one.
* When no details can be read from the response, the console entry keeps the wording it has today.
* Which screens show an error, and the retry options offered on them, are unchanged.
## Implementation Brief
The details read from the response are written to the browser console only. They are not added to the message shown in the interface, so a blocked request never displays server HTML or a hosting provider link to the user.
* [ ] In `assets/js/util/errors.ts`:
* [ ] Add `getInvalidJSONResponseError( response, body )`, returning an `ErrorObject` whose `code` is `ERROR_INVALID_JSON`, whose `message` is the `'The server provided an invalid response'` string already returned by `getReportErrorMessage()` for that code, and whose `data.status` is the HTTP status.
* [ ] Set `data.reason` from `body`, taking the first of: the document ``, the first `<h1>`, the response's status text. Strip the tags, collapse whitespace and cap the result at a single short line.
* [ ] When `body` looks like a ModSecurity block page, meaning it mentions ModSecurity or "security policy", prefix `data.reason` with `ModSecurity` and append the `href` of the deactivation link when the page contains one.
* [ ] Leave `data.reason` unset when nothing can be read from `body`.
* [ ] In `assets/js/googlesitekit/api/middleware/error-response.ts` (new file, following `deduplication.ts`):
* [ ] Export `createErrorResponseMiddleware()` returning an `APIFetchMiddleware`.
* [ ] Return `next( options )` untouched when the caller already passed `parse: false`.
* [ ] Otherwise call `next( { ...options, parse: false } )` and do the parsing in the middleware: return `null` for a 204, and the parsed JSON body for anything else.
* [ ] Read the body from a `clone()` of the response so the text is still available after a failed `json()` call.
* [ ] When `next()` rejects with a `Response`, which is what a failing status produces once `parse` is false, parse it as JSON and throw the parsed body so codes such as `rest_cookie_invalid_nonce` still reach the retry in `apiFetch`.
* [ ] Throw the object from `getInvalidJSONResponseError()` whenever the body will not parse as JSON, on both the success and the failure path.
* [ ] Rethrow any rejection that is not a `Response`, leaving `AbortError`, `offline_error` and `fetch_error` unchanged.
* [ ] In `assets/js/api-fetch-shim.ts`:
* [ ] Register the new middleware as the first `apiFetch.use()` call, above `apiFetch.nonceMiddleware`. `use()` prepends to the middleware list, so the first registration is the one that runs closest to the fetch handler and sees the raw response.
* [ ] In `assets/js/googlesitekit/api/index.js`:
* [ ] Include `reason:"${ error.data.reason }"` in the existing `global.console.error( 'Google Site Kit API Error', ... )` call when the error carries one.
### Test Coverage
* Add cases to `assets/js/util/errors.test.ts` for `getInvalidJSONResponseError`:
* [ ] Reads the detail from the page title, falls back to the first heading, then to the status text.
* [ ] Names ModSecurity and includes the deactivation link for a ModSecurity block page, and omits the link when the page has none.
* [ ] Returns the error without a reason when the body is empty.
* Add `assets/js/googlesitekit/api/middleware/error-response.test.ts`:
* [ ] Returns the parsed body for a successful JSON response, and `null` for a 204.
* [ ] Throws the parsed error body for a failing response whose body is valid JSON, leaving `rest_cookie_invalid_nonce` intact.
* [ ] Throws an `invalid_json` error carrying the status and the extracted detail for a 510 ModSecurity HTML response.
* [ ] Returns the raw response when the caller passed `parse: false`.
* [ ] Rethrows abort and network rejections unchanged.
* Add cases to `assets/js/googlesitekit/api/index.test.js`:
* [ ] Records the error against the store and includes the extracted detail in the console error when a request returns a non-JSON response.
* Add a case to `assets/js/components/StoreErrorNotices.test.tsx`:
* [ ] Shows "The server provided an invalid response" for an `invalid_json` error.
## QA Brief
* <!-- One or more bullet points for how to test that the issue has been resolved. -->
## Changelog entry
* <!-- One sentence summarizing the PR, to be used in the changelog. -->
Contributor guide
Assessment
This issue has not been assessed yet.