/**
* Does this request appear to be a web-service request?
*
+ * This is used to mitigate CSRF risks.
+ *
* @return bool
- * TRUE if the current request appears to be web-service request (ie AJAX).
- * FALSE if the current request appears to be a standalone browser page-view.
+ * TRUE if the current request appears to either XMLHttpRequest or non-browser-based.
+ * Indicated by either (a) custom headers like `X-Request-With`/`X-Civi-Auth`
+ * or (b) strong-secret-params that could theoretically appear in URL bar but which
+ * cannot be meaningfully forged for CSRF purposes (like `?api_key=SECRET` or `?_authx=SECRET`).
+ * FALSE if the current request looks like a standard browser request. This request may be generated by
+ * <A HREF>, <IFRAME>, <IMG>, `Location:`, or similar CSRF vector.
*/
protected static function isWebServiceRequest(): bool {
- return array_key_exists('HTTP_X_REQUESTED_WITH', $_SERVER) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
+ if (($_SERVER['HTTP_X_REQUESTED_WITH'] ?? NULL) === 'XMLHttpRequest') {
+ return TRUE;
+ }
+
+ $authx = \CRM_Core_Session::singleton()->get('authx');
+ $allowFlows = ['legacyrest', 'param', 'xheader'];
+ // <legacyrest> Current request has valid `?api_key=SECRET&key=SECRET` ==> Strong-secret params
+ // <param> Current request has valid `?_authx=SECRET` ==> Strong-secret param
+ // <xheader> Current request has valid `X-Civi-Auth:` ==> Custom header AND strong-secret param
+ // NOTE: Prohibited flows: `login`, `auto`, and `header` are driven by standard headers (`Cookie:`/`Authorization:`)
+ if (!empty($authx) && in_array($authx['flow'], $allowFlows)) {
+ return TRUE;
+ }
+
+ return FALSE;
}
}