Website: Handle unexpected responses from Microsoft's compliance and Graph APIs (#50015)
## Issue #50013 ## Description The Microsoft compliance proxy controller (`website/api/controllers/microsoft-proxy/receive-redirect-from-microsoft.js`) called `JSON.parse` on response bodies from Microsoft's Partner Compliance and Graph APIs without checking for empty bodies or unexpected response shapes. When Microsoft returned an unexpected response — for example, a 2xx status with an empty body, which can happen on partial-setup tenant states or when API permissions on the enterprise app haven't been fully consented — the controller threw a raw `SyntaxError: Unexpected end of JSON input` that surfaced verbatim in the Fleet UI as the `setup_error` string, giving admins a Node.js stack trace instead of a useful message. Changes: - Added explicit empty-body checks before `JSON.parse` at both API-response parse sites, with a friendly `setup_error` message pointing at the likely causes (partial setup / missing API permissions). - On parse failure, expanded the diagnostic log to include response status code, body length, and a 200-char body snippet so we can diagnose future occurrences from server logs instead of asking admins to reproduce. - Added defensive checks on `parsedPoliciesResponse.value` and `parsedGroupResponse.value` before indexing — previously `parsedPoliciesResponse.value[0].Id` would throw `TypeError` if Microsoft returned a well-formed response missing the expected shape. **Note for reviewers:** The new `sails.log.warn` calls interpolate the runtime tenant ID (`informationAboutThisTenant.entraTenantId`) — same pattern as the existing log at line 209 that logs `fleetInstanceUrl`. Heroku logs will contain tenant IDs when these error paths fire, which is intentional so infra can grep by tenant when triaging. If we'd rather rely on request-correlation IDs and keep tenant IDs out of logs, happy to make that a follow-up. ## Screenrecording ## Testing - [ ] Sanity-checked locally by inducing an empty response body - [ ] Verified no changes to the happy-path flow - [ ] Verified existing setup_error strings that the Fleet UI checks for (admin-did-not-consent, missing-conditional-access-group) are unchanged <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved Microsoft integration setup handling when API responses are empty, invalid, or missing expected data. * Added clearer setup error messages for missing policies or the “Fleet conditional access” group. * Enhanced diagnostics to help identify response-related setup failures. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
+24
-5
@@ -168,11 +168,24 @@ module.exports = {
|
||||
|
||||
|
||||
let parsedPoliciesResponse;
|
||||
// If Microsoft returned an empty body, surface a friendlier error rather than blowing up on JSON.parse.
|
||||
// This can happen when the tenant is in a partial-setup state from a previous attempt, or when required API permissions haven't been consented on the enterprise app registration.
|
||||
if(!createPolicyResponse.body) {
|
||||
sails.log.warn(`Microsoft's PartnerCompliancePolicies API returned an empty response body for tenant ${informationAboutThisTenant.entraTenantId}. Status: ${createPolicyResponse.statusCode}. Response headers: ${require('util').inspect(createPolicyResponse.headers, {depth: 1})}`);
|
||||
await MicrosoftComplianceTenant.updateOne({id: informationAboutThisTenant.id}).set({setupError: `Microsoft's PartnerCompliancePolicies API returned an empty response (HTTP ${createPolicyResponse.statusCode}). This may indicate a partial setup state from a previous attempt, or missing API permissions on the Fleet enterprise app registration.`});
|
||||
throw {redirect: fleetInstanceUrlToRedirectTo };
|
||||
}
|
||||
try {
|
||||
parsedPoliciesResponse = JSON.parse(createPolicyResponse.body);
|
||||
} catch(err){
|
||||
sails.log.warn(`An error occured when parsing the JSON response body from the PartnerCompliancePolicies endpoint for a microsoft compliance tenant. full error`, err);
|
||||
await MicrosoftComplianceTenant.updateOne({id: informationAboutThisTenant.id}).set({setupError: `${require('util').inspect(err, {depth: null})}`});
|
||||
sails.log.warn(`An error occured when parsing the JSON response body from the PartnerCompliancePolicies endpoint for a microsoft compliance tenant. Status: ${createPolicyResponse.statusCode}. Body length: ${createPolicyResponse.body.length}. Body snippet: ${String(createPolicyResponse.body).slice(0, 200)}. Full error:`, err);
|
||||
await MicrosoftComplianceTenant.updateOne({id: informationAboutThisTenant.id}).set({setupError: `Could not parse response from Microsoft's PartnerCompliancePolicies API (HTTP ${createPolicyResponse.statusCode}). Underlying error: ${require('util').inspect(err, {depth: null})}`});
|
||||
throw {redirect: fleetInstanceUrlToRedirectTo };
|
||||
}
|
||||
// Defensive check: Microsoft may return a well-formed but unexpected response shape (missing `value` array) for certain tenant configurations.
|
||||
if(!parsedPoliciesResponse.value || !Array.isArray(parsedPoliciesResponse.value) || parsedPoliciesResponse.value.length === 0){
|
||||
sails.log.warn(`The response body from PartnerCompliancePolicies did not contain the expected 'value' array for tenant ${informationAboutThisTenant.entraTenantId}. Parsed response: ${require('util').inspect(parsedPoliciesResponse, {depth: 2})}`);
|
||||
await MicrosoftComplianceTenant.updateOne({id: informationAboutThisTenant.id}).set({setupError: `Microsoft's PartnerCompliancePolicies API returned an unexpected response shape (missing 'value' array). This may indicate a partial setup state or missing API permissions on the Fleet enterprise app registration.`});
|
||||
throw {redirect: fleetInstanceUrlToRedirectTo };
|
||||
}
|
||||
let createdPolicyId = parsedPoliciesResponse.value[0].Id;
|
||||
@@ -196,16 +209,22 @@ module.exports = {
|
||||
}
|
||||
// Get the ID returned in the response.
|
||||
let parsedGroupResponse;
|
||||
// If Microsoft's Graph API returned an empty body, surface a friendlier error rather than blowing up on JSON.parse.
|
||||
if(!groupResponse.body) {
|
||||
sails.log.warn(`Microsoft's Graph API returned an empty response body when searching for the "Fleet conditional access" group on tenant ${informationAboutThisTenant.entraTenantId}. Status: ${groupResponse.statusCode}. Response headers: ${require('util').inspect(groupResponse.headers, {depth: 1})}`);
|
||||
await MicrosoftComplianceTenant.updateOne({id: informationAboutThisTenant.id}).set({setupError: `Microsoft's Graph API returned an empty response (HTTP ${groupResponse.statusCode}) when searching for the "Fleet conditional access" group. This may indicate missing API permissions on the Fleet enterprise app registration.`});
|
||||
throw {redirect: fleetInstanceUrlToRedirectTo };
|
||||
}
|
||||
try {
|
||||
parsedGroupResponse = JSON.parse(groupResponse.body);
|
||||
} catch(err){
|
||||
sails.log.warn(`An error occured when parsing the JSON response body returned by the Microsoft graph API for a new Microsoft compliance tenant. full error`, err);
|
||||
await MicrosoftComplianceTenant.updateOne({id: informationAboutThisTenant.id}).set({setupError: `${require('util').inspect(err, {depth: null})}`});
|
||||
sails.log.warn(`An error occured when parsing the JSON response body returned by the Microsoft graph API for a new Microsoft compliance tenant. Status: ${groupResponse.statusCode}. Body length: ${groupResponse.body.length}. Body snippet: ${String(groupResponse.body).slice(0, 200)}. Full error:`, err);
|
||||
await MicrosoftComplianceTenant.updateOne({id: informationAboutThisTenant.id}).set({setupError: `Could not parse response from Microsoft's Graph API (HTTP ${groupResponse.statusCode}). Underlying error: ${require('util').inspect(err, {depth: null})}`});
|
||||
throw {redirect: fleetInstanceUrlToRedirectTo };
|
||||
}
|
||||
|
||||
// If the response from the Microsoft Graph API did not contain any groups, log a warning and save a setup error on the database record for this tenant.
|
||||
if(parsedGroupResponse.value.length === 0){
|
||||
if(!parsedGroupResponse.value || !Array.isArray(parsedGroupResponse.value) || parsedGroupResponse.value.length === 0){
|
||||
sails.log.warn(`When an Entra tenant (${informationAboutThisTenant.fleetInstanceUrl}) tried setting up a conditional access integration, no "Fleet conditional access" Entra ID group was found on this Entra tenant.`);
|
||||
// IMPORTANT: Don't change the below setupError value. The error string is checked by the Fleet UI frontend.
|
||||
await MicrosoftComplianceTenant.updateOne({id: informationAboutThisTenant.id}).set({setupError: `No "Fleet conditional access" Entra ID group was found on this Entra tenant.`});
|
||||
|
||||
Reference in New Issue
Block a user