From 12440ca6064640a51da6efe68980180b0eccf286 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 15 Jul 2026 11:49:36 -0500 Subject: [PATCH] Website: Reduce Android Management API usage (#49065) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to: https://github.com/fleetdm/fleet/issues/41908 Changes: - Updated Android proxy endpoints (`create-android-enrollment-token`, `create-enterprise-webapp`, `delete-android-device`, `get-android-device`, `get-android-devices`, `get-enterprise-applications`, `issue-command-on-android-device`, `modify-android-device`, `modify-android-policies`, and `modify-enterprise-app-policy`) to send requests to the Android Management API without verifying that an enterprise is still managed by Fleet with the getIsEnterpriseManagedByFleet helper, and to return a `enterpriseNotAccessible` (notFound) response to Fleet servers if the Android management API returns a 403 response. ## Summary by CodeRabbit * **Bug Fixes** * Improved Android management flows to handle authorization failures more consistently. * Requests now return a clearer “not accessible” response when access to an Android enterprise is denied. * Several Android device, app, policy, and enterprise actions now surface this response instead of falling back to generic errors. * Streamlined Android enterprise operations by removing an extra pre-check, letting the API response determine the final outcome. --- .../android-proxy/create-android-enrollment-token.js | 11 ++++------- .../android-proxy/create-enterprise-webapp.js | 11 ++++------- .../android-proxy/delete-android-device.js | 11 ++++------- .../controllers/android-proxy/get-android-device.js | 11 ++++------- .../controllers/android-proxy/get-android-devices.js | 4 ++++ .../android-proxy/get-enterprise-applications.js | 11 ++++------- .../android-proxy/issue-command-on-android-device.js | 11 ++++------- .../android-proxy/modify-android-device.js | 11 ++++------- .../android-proxy/modify-android-policies.js | 11 ++++------- .../android-proxy/modify-enterprise-app-policy.js | 11 ++++------- 10 files changed, 40 insertions(+), 63 deletions(-) diff --git a/website/api/controllers/android-proxy/create-android-enrollment-token.js b/website/api/controllers/android-proxy/create-android-enrollment-token.js index 7264c4a865..333bd38d4f 100644 --- a/website/api/controllers/android-proxy/create-android-enrollment-token.js +++ b/website/api/controllers/android-proxy/create-android-enrollment-token.js @@ -20,6 +20,7 @@ module.exports = { missingAuthHeader: { description: 'This request was missing an authorization header.', responseType: 'unauthorized'}, unauthorized: { description: 'Invalid authentication token', responseType: 'unauthorized'}, notFound: { description: 'No Android enterprise found for this Fleet server.', responseType: 'notFound'}, + enterpriseNotAccessible: { description: 'Fleet is not authorized to manage this Android enterprise.', responseType: 'notFound' }, managementApiError: { statusCode: 503, description: 'The Android management API returned a transient 5xx error.' }, }, @@ -50,13 +51,6 @@ module.exports = { throw 'unauthorized'; } - // Check the list of Android Enterprises managed by Fleet to see if this Android Enterprise is still managed. - let isEnterpriseManagedByFleet = await sails.helpers.androidProxy.getIsEnterpriseManagedByFleet(androidEnterpriseId); - // Return a 404 response if this Android enterprise is no longer managed by Fleet. - if(!isEnterpriseManagedByFleet) { - throw 'notFound'; - } - let newEnrollmentToken = await sails.helpers.flow.build(async ()=>{ let { google } = require('googleapis'); let androidmanagement = google.androidmanagement('v1'); @@ -81,6 +75,9 @@ module.exports = { // If the Android management API returns a 429 response, log an additional warning that will trigger a help-p1 alert. sails.log.warn(`p1: Android management API rate limit exceeded!`); return new Error(`When attempting to create an enrollment token for an Android enterprise (${androidEnterpriseId}), an error occurred. Error: ${err}`); + }).intercept({status: 403}, ()=>{ + // If the Android management API returns a 403 response, return a enterpriseNotAccessible (notFound) response to the Fleet server. + return {'enterpriseNotAccessible': 'Fleet is not authorized to manage this Android enterprise.'}; }).intercept((err)=>{ if([502, 503, 504].includes(err.status)) { return {'managementApiError': `The Android management API returned a transient 5xx error: ${err}`}; diff --git a/website/api/controllers/android-proxy/create-enterprise-webapp.js b/website/api/controllers/android-proxy/create-enterprise-webapp.js index 907a85aaec..f62a501bf1 100644 --- a/website/api/controllers/android-proxy/create-enterprise-webapp.js +++ b/website/api/controllers/android-proxy/create-enterprise-webapp.js @@ -35,6 +35,7 @@ module.exports = { missingAuthHeader: { description: 'This request was missing an authorization header.', responseType: 'unauthorized'}, unauthorized: { description: 'Invalid authentication token.', responseType: 'unauthorized'}, notFound: { description: 'No Android enterprise found for this Fleet server.', responseType: 'notFound' }, + enterpriseNotAccessible: { description: 'Fleet is not authorized to manage this Android enterprise.', responseType: 'notFound' }, invalidWebApp: { description: 'Invalid post webApp request', responseType: 'badRequest' }, managementApiError: { statusCode: 503, description: 'The Android management API returned a transient 5xx error.' }, }, @@ -66,13 +67,6 @@ module.exports = { throw 'unauthorized'; } - // Check the list of Android Enterprises managed by Fleet to see if this Android Enterprise is still managed. - let isEnterpriseManagedByFleet = await sails.helpers.androidProxy.getIsEnterpriseManagedByFleet(androidEnterpriseId); - // Return a 404 response if this Android enterprise is no longer managed by Fleet. - if(!isEnterpriseManagedByFleet) { - throw 'notFound'; - } - // Create the webApp. // Note: We're using sails.helpers.flow.build here to handle any errors that occur using google's node library. let createWebAppResponse = await sails.helpers.flow.build(async () => { @@ -106,6 +100,9 @@ module.exports = { return new Error(`When attempting to create a webapp for an Android enterprise (${androidEnterpriseId}), an error occurred. Error: ${err}`); }).intercept({ status: 400 }, (err) => { return {'invalidWebApp': `Attempted to create a webApp with an invalid value for an Android enterprise (${androidEnterpriseId}): ${err}`}; + }).intercept({status: 403}, ()=>{ + // If the Android management API returns a 403 response, return a enterpriseNotAccessible (notFound) response to the Fleet server. + return {'enterpriseNotAccessible': 'Fleet is not authorized to manage this Android enterprise.'}; }).intercept((err)=>{ if([502, 503, 504].includes(err.status)){ return {'managementApiError': `The Android management API returned a transient 5xx error: ${err}`}; diff --git a/website/api/controllers/android-proxy/delete-android-device.js b/website/api/controllers/android-proxy/delete-android-device.js index 02167244aa..a5e1aec8c3 100644 --- a/website/api/controllers/android-proxy/delete-android-device.js +++ b/website/api/controllers/android-proxy/delete-android-device.js @@ -24,6 +24,7 @@ module.exports = { missingAuthHeader: { description: 'This request was missing an authorization header.', responseType: 'unauthorized'}, unauthorized: { description: 'Invalid authentication token.', responseType: 'unauthorized'}, notFound: { description: 'No Android enterprise found for this Fleet server.', responseType: 'notFound'}, + enterpriseNotAccessible: { description: 'Fleet is not authorized to manage this Android enterprise.', responseType: 'notFound' }, deviceNoLongerManaged: { description: 'The specified device is no longer managed by the Android enterprise.', responseType: 'notFound' }, managementApiError: { statusCode: 503, description: 'The Android management API returned a transient 5xx error.' }, }, @@ -55,13 +56,6 @@ module.exports = { throw 'unauthorized'; } - // Check the list of Android Enterprises managed by Fleet to see if this Android Enterprise is still managed. - let isEnterpriseManagedByFleet = await sails.helpers.androidProxy.getIsEnterpriseManagedByFleet(androidEnterpriseId); - // Return a 404 response if this Android enterprise is no longer managed by Fleet. - if(!isEnterpriseManagedByFleet) { - throw 'notFound'; - } - // Delete the device for this Android enterprise. // Note: We're using sails.helpers.flow.build here to handle any errors that occur using google's node library. await sails.helpers.flow.build(async () => { @@ -85,6 +79,9 @@ module.exports = { // If the Android management API returns a 429 response, log an additional warning that will trigger a help-p1 alert. sails.log.warn(`p1: Android management API rate limit exceeded!`); return new Error(`When attempting to delete a device for an Android enterprise (${androidEnterpriseId}), an error occurred. Error: ${err}`); + }).intercept({status: 403}, ()=>{ + // If the Android management API returns a 403 response, return a enterpriseNotAccessible (notFound) response to the Fleet server. + return {'enterpriseNotAccessible': 'Fleet is not authorized to manage this Android enterprise.'}; }).intercept((err)=>{ let errorString = err.toString(); if (errorString.includes('Device is no longer being managed')) { diff --git a/website/api/controllers/android-proxy/get-android-device.js b/website/api/controllers/android-proxy/get-android-device.js index 9e7718e261..933cd92cd2 100644 --- a/website/api/controllers/android-proxy/get-android-device.js +++ b/website/api/controllers/android-proxy/get-android-device.js @@ -24,6 +24,7 @@ module.exports = { missingAuthHeader: { description: 'This request was missing an authorization header.', responseType: 'unauthorized'}, unauthorized: { description: 'Invalid authentication token.', responseType: 'unauthorized'}, notFound: { description: 'No Android enterprise found for this Fleet server.', responseType: 'notFound' }, + enterpriseNotAccessible: { description: 'Fleet is not authorized to manage this Android enterprise.', responseType: 'notFound' }, deviceNoLongerManaged: { description: 'The device is no longer managed by the Android enterprise.', responseType: 'notFound' }, }, @@ -54,13 +55,6 @@ module.exports = { throw 'unauthorized'; } - // Check the list of Android Enterprises managed by Fleet to see if this Android Enterprise is still managed. - let isEnterpriseManagedByFleet = await sails.helpers.androidProxy.getIsEnterpriseManagedByFleet(androidEnterpriseId); - // Return a 404 response if this Android enterprise is no longer managed by Fleet. - if(!isEnterpriseManagedByFleet) { - throw 'notFound'; - } - // Get the device for this Android enterprise. // Note: We're using sails.helpers.flow.build here to handle any errors that occur using google's node library. let getDeviceResponse = await sails.helpers.flow.build(async () => { @@ -85,6 +79,9 @@ module.exports = { // If the Android management API returns a 429 response, log an additional warning that will trigger a help-p1 alert. sails.log.warn(`p1: Android management API rate limit exceeded!`); return new Error(`When attempting to get a device for an Android enterprise (${androidEnterpriseId}), an error occurred. Error: ${err}`); + }).intercept({status: 403}, ()=>{ + // If the Android management API returns a 403 response, return a enterpriseNotAccessible (notFound) response to the Fleet server. + return {'enterpriseNotAccessible': 'Fleet is not authorized to manage this Android enterprise.'}; }).intercept((err)=>{ let errorString = err.toString(); if (errorString.includes('Device is no longer being managed')) { diff --git a/website/api/controllers/android-proxy/get-android-devices.js b/website/api/controllers/android-proxy/get-android-devices.js index cabbe55cb9..e74b44ef4b 100644 --- a/website/api/controllers/android-proxy/get-android-devices.js +++ b/website/api/controllers/android-proxy/get-android-devices.js @@ -34,6 +34,7 @@ module.exports = { missingAuthHeader: { description: 'This request was missing an authorization header.', responseType: 'unauthorized'}, missingOriginHeader: { description: 'The request was missing an Origin header', responseType: 'badRequest'}, notFound: { description: 'No Android enterprise found for this Fleet server.', responseType: 'notFound' }, + enterpriseNotAccessible: { description: 'Fleet is not authorized to manage this Android enterprise.', responseType: 'notFound' }, unauthorized: { description: 'Invalid authentication token.', responseType: 'unauthorized'}, }, @@ -97,6 +98,9 @@ module.exports = { // If the Android management API returns a 429 response, log an additional warning that will trigger a help-p1 alert. sails.log.warn(`p1: Android management API rate limit exceeded!`); return new Error(`When attempting to list devices for an Android enterprise (${androidEnterpriseId}), an error occurred. Error: ${err}`); + }).intercept({status: 403}, ()=>{ + // If the Android management API returns a 403 response, return a enterpriseNotAccessible (notFound) response to the Fleet server. + return {'enterpriseNotAccessible': 'Fleet is not authorized to manage this Android enterprise.'}; }).intercept((err)=>{ return new Error(`When attempting to list devices for an Android enterprise (${androidEnterpriseId}), an error occurred. Error: ${require('util').inspect(err)}`); }); diff --git a/website/api/controllers/android-proxy/get-enterprise-applications.js b/website/api/controllers/android-proxy/get-enterprise-applications.js index 49154cb5b8..9b1e053d4c 100644 --- a/website/api/controllers/android-proxy/get-enterprise-applications.js +++ b/website/api/controllers/android-proxy/get-enterprise-applications.js @@ -24,6 +24,7 @@ module.exports = { missingAuthHeader: { description: 'This request was missing an authorization header.', responseType: 'unauthorized'}, unauthorized: { description: 'Invalid authentication token.', responseType: 'unauthorized'}, notFound: { description: 'App not found', responseType: 'notFound' }, + enterpriseNotAccessible: { description: 'Fleet is not authorized to manage this Android enterprise.', responseType: 'notFound' }, deviceNoLongerManaged: { description: 'The device is no longer managed by the Android enterprise.', responseType: 'notFound' }, }, @@ -54,13 +55,6 @@ module.exports = { throw 'unauthorized'; } - // Check the list of Android Enterprises managed by Fleet to see if this Android Enterprise is still managed. - let isEnterpriseManagedByFleet = await sails.helpers.androidProxy.getIsEnterpriseManagedByFleet(androidEnterpriseId); - // Return a 404 response if this Android enterprise is no longer managed by Fleet. - if(!isEnterpriseManagedByFleet) { - throw 'notFound'; - } - // Get the device for this Android enterprise. // Note: We're using sails.helpers.flow.build here to handle any errors that occur using google's node library. let getApplicationsResponse = await sails.helpers.flow.build(async () => { @@ -85,6 +79,9 @@ module.exports = { // If the Android management API returns a 429 response, log an additional warning that will trigger a help-p1 alert. sails.log.warn(`p1: Android management API rate limit exceeded!`); return new Error(`When attempting to get an application for an Android enterprise (${androidEnterpriseId}), an error occurred. Error: ${err}`); + }).intercept({status: 403}, ()=>{ + // If the Android management API returns a 403 response, return a enterpriseNotAccessible (notFound) response to the Fleet server. + return {'enterpriseNotAccessible': 'Fleet is not authorized to manage this Android enterprise.'}; }).intercept({status: 404}, () => { return {'notFound': 'App not found.'}; }).intercept((err) => { diff --git a/website/api/controllers/android-proxy/issue-command-on-android-device.js b/website/api/controllers/android-proxy/issue-command-on-android-device.js index edf7290fee..44b8e032bc 100644 --- a/website/api/controllers/android-proxy/issue-command-on-android-device.js +++ b/website/api/controllers/android-proxy/issue-command-on-android-device.js @@ -82,6 +82,7 @@ module.exports = { missingAuthHeader: { description: 'This request was missing an authorization header.', responseType: 'unauthorized'}, unauthorized: { description: 'Invalid authentication token.', responseType: 'unauthorized'}, notFound: { description: 'No Android enterprise found for this Fleet server.', responseType: 'notFound' }, + enterpriseNotAccessible: { description: 'Fleet is not authorized to manage this Android enterprise.', responseType: 'notFound' }, deviceNoLongerManaged: { description: 'The specified device is no longer managed by the Android enterprise.', responseType: 'notFound' }, managementApiError: { statusCode: 503, description: 'The Android management API returned a transient 5xx error.' }, }, @@ -117,13 +118,6 @@ module.exports = { throw 'unauthorized'; } - // Check the list of Android Enterprises managed by Fleet to see if this Android Enterprise is still managed. - let isEnterpriseManagedByFleet = await sails.helpers.androidProxy.getIsEnterpriseManagedByFleet(androidEnterpriseId); - // Return a 404 response if this Android enterprise is no longer managed by Fleet. - if (!isEnterpriseManagedByFleet) { - throw 'notFound'; - } - // Build the AMAPI Command body from declared inputs (not req.body) so the proxy's accepted surface // stays explicit. Use `!== undefined` rather than truthy checks because Fleet relies on forwarding // an empty newPassword ("") to clear the device passcode and an empty wipeParams ({}) for WIPE. @@ -184,6 +178,9 @@ module.exports = { // If the Android management API returns a 429 response, log an additional warning that will trigger a help-p1 alert. sails.log.warn(`p1: Android management API rate limit exceeded!`); return new Error(`When attempting to issue a command to a device for an Android enterprise (${androidEnterpriseId}), an error occurred. Error: ${err}`); + }).intercept({status: 403}, ()=>{ + // If the Android management API returns a 403 response, return a enterpriseNotAccessible (notFound) response to the Fleet server. + return {'enterpriseNotAccessible': 'Fleet is not authorized to manage this Android enterprise.'}; }).intercept((err)=>{ let errorString = err.toString(); if (errorString.includes('Device is no longer being managed')) { diff --git a/website/api/controllers/android-proxy/modify-android-device.js b/website/api/controllers/android-proxy/modify-android-device.js index b151e4ac46..8c55446776 100644 --- a/website/api/controllers/android-proxy/modify-android-device.js +++ b/website/api/controllers/android-proxy/modify-android-device.js @@ -24,6 +24,7 @@ module.exports = { missingAuthHeader: { description: 'This request was missing an authorization header.', responseType: 'unauthorized'}, unauthorized: { description: 'Invalid authentication token.', responseType: 'unauthorized'}, notFound: { description: 'No Android enterprise found for this Fleet server.', responseType: 'notFound' }, + enterpriseNotAccessible: { description: 'Fleet is not authorized to manage this Android enterprise.', responseType: 'notFound' }, deviceNoLongerManaged: { description: 'The device is no longer managed by the Android enterprise.', responseType: 'notFound' }, invalidPolicyName: {description: 'The specified policy_name is invalid', responseType: 'badRequest' }, managementApiError: { statusCode: 503, description: 'The Android management API returned a transient 5xx error.' }, @@ -56,13 +57,6 @@ module.exports = { throw 'unauthorized'; } - // Check the list of Android Enterprises managed by Fleet to see if this Android Enterprise is still managed. - let isEnterpriseManagedByFleet = await sails.helpers.androidProxy.getIsEnterpriseManagedByFleet(androidEnterpriseId); - // Return a 404 response if this Android enterprise is no longer managed by Fleet. - if(!isEnterpriseManagedByFleet) { - throw 'notFound'; - } - // Update the device for this Android enterprise. // Note: We're using sails.helpers.flow.build here to handle any errors that occur using google's node library. let modifyDeviceResponse = await sails.helpers.flow.build(async () => { @@ -90,6 +84,9 @@ module.exports = { // If the Android management API returns a 429 response, log an additional warning that will trigger a help-p1 alert. sails.log.warn(`p1: Android management API rate limit exceeded!`); return new Error(`When attempting to update a device for an Android enterprise (${androidEnterpriseId}), an error occurred. Error: ${err}`); + }).intercept({status: 403}, ()=>{ + // If the Android management API returns a 403 response, return a enterpriseNotAccessible (notFound) response to the Fleet server. + return {'enterpriseNotAccessible': 'Fleet is not authorized to manage this Android enterprise.'}; }).intercept((err)=>{ let errorString = err.toString(); if (errorString.includes('Device is no longer being managed')) { diff --git a/website/api/controllers/android-proxy/modify-android-policies.js b/website/api/controllers/android-proxy/modify-android-policies.js index a5982d16c6..e4cc7acd30 100644 --- a/website/api/controllers/android-proxy/modify-android-policies.js +++ b/website/api/controllers/android-proxy/modify-android-policies.js @@ -24,6 +24,7 @@ module.exports = { missingAuthHeader: { description: 'This request was missing an authorization header.', responseType: 'unauthorized'}, unauthorized: { description: 'Invalid authentication token.', responseType: 'unauthorized'}, notFound: { description: 'No Android enterprise found for this Fleet server.', responseType: 'notFound'}, + enterpriseNotAccessible: { description: 'Fleet is not authorized to manage this Android enterprise.', responseType: 'notFound' }, invalidPolicy: { description: 'Invalid patch policy request', responseType: 'badRequest' }, policyNotFound: { description: 'The specified policy was not found on this Android enterprise', responseType: 'notFound' }, managementApiError: { statusCode: 503, description: 'The Android management API returned a transient 5xx error.' }, @@ -56,13 +57,6 @@ module.exports = { throw 'unauthorized'; } - // Check the list of Android Enterprises managed by Fleet to see if this Android Enterprise is still managed. - let isEnterpriseManagedByFleet = await sails.helpers.androidProxy.getIsEnterpriseManagedByFleet(androidEnterpriseId); - // Return a 404 response if this Android enterprise is no longer managed by Fleet. - if(!isEnterpriseManagedByFleet) { - throw 'notFound'; - } - // Update the policy for this Android enterprise. // Note: We're using sails.helpers.flow.build here to handle any errors that occurr using google's node library. let modifyPoliciesResponse = await sails.helpers.flow.build(async () => { @@ -93,6 +87,9 @@ module.exports = { return new Error(`When attempting to update a policy for an Android enterprise (${androidEnterpriseId}), an error occurred. Error: ${err}`); }).intercept({ status: 400 }, (err) => { return {'invalidPolicy': `Attempted to update a policy with an invalid value for an Android enterprise (${androidEnterpriseId}): ${err}`}; + }).intercept({status: 403}, ()=>{ + // If the Android management API returns a 403 response, return a enterpriseNotAccessible (notFound) response to the Fleet server. + return {'enterpriseNotAccessible': 'Fleet is not authorized to manage this Android enterprise.'}; }).intercept({ status: 404 }, (err) => { return {'policyNotFound': `Specified policy not found on this Android enterprise (${androidEnterpriseId}): ${err}`}; }).intercept((err) => { diff --git a/website/api/controllers/android-proxy/modify-enterprise-app-policy.js b/website/api/controllers/android-proxy/modify-enterprise-app-policy.js index a218cbfeb3..b1f6d8df31 100644 --- a/website/api/controllers/android-proxy/modify-enterprise-app-policy.js +++ b/website/api/controllers/android-proxy/modify-enterprise-app-policy.js @@ -36,6 +36,7 @@ module.exports = { missingAuthHeader: { description: 'This request was missing an authorization header.', responseType: 'unauthorized'}, unauthorized: { description: 'Invalid authentication token.', responseType: 'unauthorized'}, notFound: { description: 'No Android enterprise found for this Fleet server.', responseType: 'notFound'}, + enterpriseNotAccessible: { description: 'Fleet is not authorized to manage this Android enterprise.', responseType: 'notFound' }, policyNotFound: { description: 'Specified policy not found', responseType: 'notFound' }, managementApiError: { statusCode: 503, description: 'The Android management API returned a transient 5xx error.' }, }, @@ -67,13 +68,6 @@ module.exports = { throw 'unauthorized'; } - // Check the list of Android Enterprises managed by Fleet to see if this Android Enterprise is still managed. - let isEnterpriseManagedByFleet = await sails.helpers.androidProxy.getIsEnterpriseManagedByFleet(androidEnterpriseId); - // Return a 404 response if this Android enterprise is no longer managed by Fleet. - if(!isEnterpriseManagedByFleet) { - throw 'notFound'; - } - // Update the policy applications for this Android enterprise. // Note: We're using sails.helpers.flow.build here to handle any errors that occurr using google's node library. let modifyApplicationPolicyResponse = await sails.helpers.flow.build(async () => { @@ -111,6 +105,9 @@ module.exports = { // If the Android management API returns a 429 response, log an additional warning that will trigger a help-p1 alert. sails.log.warn(`p1: Android management API rate limit exceeded!`); return new Error(`When attempting to update applications for a policy of Android enterprise (${androidEnterpriseId}), an error occurred. Error: ${err}`); + }).intercept({status: 403}, ()=>{ + // If the Android management API returns a 403 response, return a enterpriseNotAccessible (notFound) response to the Fleet server. + return {'enterpriseNotAccessible': 'Fleet is not authorized to manage this Android enterprise.'}; }).intercept({ status: 404 }, (err) => { return {'policyNotFound': `Specified policy not found on this Android enterprise (${androidEnterpriseId}): ${err}`}; }).intercept((err) => {