From f6c1ff609d9574b2402f052b22046f2fcd3a57a8 Mon Sep 17 00:00:00 2001 From: abraunegg Date: Mon, 24 Nov 2025 17:12:58 +1100 Subject: [PATCH] Add debug logging output redaction * Add debug logging output redaction covering sensitive items and PII --- src/onedrive.d | 21 +++++++++++++++------ src/sync.d | 43 +++++++++++++++++++++++++++++++++++-------- src/util.d | 7 ------- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/onedrive.d b/src/onedrive.d index 467f554a..ccc9c606 100644 --- a/src/onedrive.d +++ b/src/onedrive.d @@ -1243,9 +1243,13 @@ class OneDriveApi { private void acquireToken(char[] postData) { JSONValue response; - // Debug this input - if (debugLogging) {addLogEntry("Manual Auth postData = " ~ to!string(postData), ["debug"]);} - + + // Log what we are doing + if (debugLogging) { + addLogEntry("acquireToken: requesting new access token using refresh token (value redacted)", ["debug"]); + } + + // Try and process the 'postData' content try { response = post(tokenUrl, postData, null, true, "application/x-www-form-urlencoded"); } catch (OneDriveException exception) { @@ -1268,9 +1272,14 @@ class OneDriveApi { } if (response.type() == JSONType.object) { - // Debug this response - if (debugLogging) {addLogEntry("Manual Auth Response JSON = " ~ to!string(response), ["debug"]);} - + // Debug the provided response + if (debugLogging) { + string scopes = ("scope" in response) ? response["scope"].str() : ""; + string tokenType = ("token_type" in response) ? response["token_type"].str() : ""; + long expiresIn = ("expires_in" in response) ? response["expires_in"].integer() : -1; + addLogEntry("acquireToken post response: token_type=" ~ tokenType ~ ", expires_in=" ~ to!string(expiresIn) ~ ", scope=" ~ scopes, ["debug"]); + } + // Has the client been configured to use read_only_auth_scope if (appConfig.getValueBool("read_only_auth_scope")) { // read_only_auth_scope has been configured diff --git a/src/sync.d b/src/sync.d index e770b741..bb158d5e 100644 --- a/src/sync.d +++ b/src/sync.d @@ -594,7 +594,7 @@ class SyncEngine { // If the JSON response is a correct JSON object, and has an 'id' we can set these details if ((defaultOneDriveDriveDetails.type() == JSONType.object) && (hasId(defaultOneDriveDriveDetails))) { - if (debugLogging) {addLogEntry("OneDrive Account Default Drive Details: " ~ to!string(defaultOneDriveDriveDetails), ["debug"]);} + if (debugLogging) {addLogEntry("OneDrive Account Default Drive Details: " ~ sanitiseJSONItem(defaultOneDriveDriveDetails), ["debug"]);} appConfig.accountType = defaultOneDriveDriveDetails["driveType"].str; // Issue #3115 - Validate driveId length @@ -754,7 +754,7 @@ class SyncEngine { // If the JSON response is a correct JSON object, and has an 'id' we can set these details if ((defaultOneDriveRootDetails.type() == JSONType.object) && (hasId(defaultOneDriveRootDetails))) { // Read the returned JSON data for the root drive details - if (debugLogging) {addLogEntry("OneDrive Account Default Root Details: " ~ to!string(defaultOneDriveRootDetails), ["debug"]);} + if (debugLogging) {addLogEntry("OneDrive Account Default Root Details: " ~ sanitiseJSONItem(defaultOneDriveRootDetails), ["debug"]);} appConfig.defaultRootId = defaultOneDriveRootDetails["id"].str; if (debugLogging) {addLogEntry("appConfig.defaultRootId = " ~ appConfig.defaultRootId, ["debug"]);} @@ -10321,15 +10321,13 @@ class SyncEngine { // Log that we skipping adding item to the local DB and the reason why if (debugLogging) {addLogEntry("Skipping adding to database as --upload-only & --remove-source-files configured", ["debug"]);} } else { - // What is the JSON item we are trying to create a DB record with? - if (debugLogging) {addLogEntry("saveItem - creating DB item from this JSON: " ~ sanitiseJSONItem(jsonItem), ["debug"]);} - // Takes a JSON input and formats to an item which can be used by the database Item item = makeItem(jsonItem); // Is this JSON item a 'root' item? if ((isItemRoot(jsonItem)) && (item.name == "root")) { if (debugLogging) { + addLogEntry("Creating 'root' DB item from this JSON: " ~ sanitiseJSONItem(jsonItem), ["debug"]); addLogEntry("Updating DB Item object with correct values as this is a 'root' object", ["debug"]); addLogEntry(" item.parentId = null", ["debug"]); addLogEntry(" item.type = ItemType.root", ["debug"]); @@ -14934,14 +14932,17 @@ class SyncEngine { displayFunctionProcessingStart(thisFunctionName, logKey); } - // Eventual output variable - string sanitisedJSONString; - // Validate UTF-8 before serialisation if (!validateUTF8JSON(onedriveJSONItem)) { return "JSON Validation Failed: JSON data from OneDrive API contains invalid UTF-8 characters"; } + // Redact PII in JSON before serialisation + redactPII(onedriveJSONItem); + + // Eventual output variable + string sanitisedJSONString; + // Try and serialise the JSON into a string try { auto app = appender!string(); @@ -14961,6 +14962,32 @@ class SyncEngine { return sanitisedJSONString; } + // Recursively redact PII and sensitive elements from JSONValue + void redactPII(ref JSONValue j) { + if (j.type == JSONType.object) { + foreach (key, ref value; j.object) { + + // Match Graph's actual keys directly + if (key == "email") { + value = JSONValue(""); + continue; + } + + if (key == "displayName") { + value = JSONValue(""); + continue; + } + + // Recurse + redactPII(value); + } + } else if (j.type == JSONType.array) { + foreach (ref value; j.array) { + redactPII(value); + } + } + } + // Obtain the Websocket Notification URL void obtainWebSocketNotificationURL() { // Function Start Time diff --git a/src/util.d b/src/util.d index a3fd26d2..96f1b9c7 100644 --- a/src/util.d +++ b/src/util.d @@ -769,13 +769,6 @@ bool isValidUTF8(string input) { return false; } - // is the string empty? - if (input.empty) { - // input is empty - addLogEntry("UTF-8 validation failed: Input is empty."); - return false; - } - // return true return true; } catch (UTFException) {