Skip to content

Commit 5ee0c93

Browse files
box-sdk-buildbox-sdk-build
andauthored
feat: Support sensitive data sanitization in errors (box/box-codegen#695) (#453)
Co-authored-by: box-sdk-build <[email protected]>
1 parent 4769525 commit 5ee0c93

File tree

9 files changed

+136
-6
lines changed

9 files changed

+136
-6
lines changed

.codegen.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{ "engineHash": "f4f1ff6", "specHash": "c303afc", "version": "1.9.0" }
1+
{ "engineHash": "d69fdc9", "specHash": "c303afc", "version": "1.9.0" }

Box.Sdk.Gen/Box/Errors/BoxApiException.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using Box.Sdk.Gen.Internal;
23

34
namespace Box.Sdk.Gen
45
{
@@ -8,10 +9,20 @@ public class BoxApiException : BoxSdkException
89

910
public ResponseInfo ResponseInfo { get; }
1011

12+
public DataSanitizer DataSanitizer { get; init; } = new DataSanitizer();
13+
1114
public BoxApiException(string message, DateTimeOffset timeStamp, RequestInfo requestInfo, ResponseInfo responseInfo) : base(message, timeStamp, "BoxApiException")
1215
{
1316
RequestInfo = requestInfo;
1417
ResponseInfo = responseInfo;
1518
}
19+
20+
public override string ToString()
21+
{
22+
return $"{base.ToString()}\n" +
23+
$"{RequestInfo.Print(DataSanitizer)}\n" +
24+
$"{ResponseInfo.Print(DataSanitizer)}";
25+
}
26+
1627
}
1728
}

Box.Sdk.Gen/Box/Errors/RequestInfo.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System.Collections.Generic;
22
using System.Collections.ObjectModel;
3+
using System.Linq;
4+
using Box.Sdk.Gen.Internal;
35

46
namespace Box.Sdk.Gen
57
{
@@ -22,5 +24,23 @@ public RequestInfo(string method, string? url, IReadOnlyDictionary<string, strin
2224
QueryParams = queryParams ?? new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
2325
Headers = headers;
2426
}
27+
28+
internal string Print(DataSanitizer dataSanitizer)
29+
{
30+
string queryParamsString = QueryParams.Count > 0
31+
? string.Join(", ", QueryParams.Select(kvp => $"{kvp.Key}: {kvp.Value}"))
32+
: "None";
33+
34+
string headersString = Headers.Count > 0
35+
? string.Join(", ", dataSanitizer.SanitizeHeaders(new Dictionary<string, string>(Headers)).Select(kvp => $"{kvp.Key}: {kvp.Value}"))
36+
: "None";
37+
38+
return $"RequestInfo:\n" +
39+
$"\tMethod: {Method}\n" +
40+
$"\tURL: {Url}\n" +
41+
$"\tQuery Params: {queryParamsString}\n" +
42+
$"\tHeaders: {headersString}\n" +
43+
$"\tBody: {(string.IsNullOrEmpty(Body) ? "None" : Body)}";
44+
}
2545
}
2646
}

Box.Sdk.Gen/Box/Errors/ResponseInfo.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using System.Linq;
23
using System.Text.Json.Serialization;
34
using Box.Sdk.Gen.Internal;
45

@@ -34,6 +35,29 @@ public ResponseInfo(int statusCode, IReadOnlyDictionary<string, string> headers,
3435
RequestId = requestId;
3536
HelpUrl = helpUrl;
3637
}
38+
39+
internal string Print(DataSanitizer dataSanitizer)
40+
{
41+
string headersString = Headers.Count > 0
42+
? string.Join(", ", dataSanitizer.SanitizeHeaders(new Dictionary<string, string>(Headers)).Select(kvp => $"{kvp.Key}: {kvp.Value}"))
43+
: "None";
44+
45+
string contextInfoString = (ContextInfo != null && ContextInfo.Count > 0)
46+
? string.Join(", ", ContextInfo.Select(kvp => $"{kvp.Key}: {kvp.Value}"))
47+
: "None";
48+
49+
string sanitizedBody = Body != null ? JsonUtils.SdToJson(dataSanitizer.SanitizeBody(Body)) : "None";
50+
51+
return $"ResponseInfo:\n" +
52+
$"\tStatus Code: {StatusCode}\n" +
53+
$"\tHeaders: {headersString}\n" +
54+
$"\tBody: {sanitizedBody}\n" +
55+
$"\tCode: {(string.IsNullOrEmpty(Code) ? "None" : Code)}\n" +
56+
$"\tContext Info: {contextInfoString}\n" +
57+
$"\tRequest ID: {(string.IsNullOrEmpty(RequestId) ? "None" : RequestId)}\n" +
58+
$"\tHelp URL: {(string.IsNullOrEmpty(HelpUrl) ? "None" : HelpUrl)}";
59+
}
60+
3761
}
3862

3963
internal class BoxApiExceptionDetails
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Collections.Generic;
2+
using System.Collections.ObjectModel;
3+
using Box.Sdk.Gen.Internal;
4+
5+
namespace Box.Sdk.Gen.Internal {
6+
public class DataSanitizer {
7+
internal Dictionary<string, string> KeysToSanitize { get; }
8+
9+
public DataSanitizer() {
10+
KeysToSanitize = new Dictionary<string, string>() { { "authorization", "" }, { "access_token", "" }, { "refresh_token", "" }, { "subject_token", "" }, { "token", "" }, { "client_id", "" }, { "client_secret", "" }, { "shared_link", "" }, { "download_url", "" }, { "jwt_private_key", "" }, { "jwt_private_key_passphrase", "" }, { "password", "" } };
11+
}
12+
public Dictionary<string, string> SanitizeHeaders(Dictionary<string, string> headers) {
13+
return Utils.SanitizeMap(mapToSanitize: headers, keysToSanitize: this.KeysToSanitize);
14+
}
15+
16+
public SerializedData SanitizeBody(SerializedData body) {
17+
return JsonUtils.SanitizeSerializedData(sd: body, keysToSanitize: this.KeysToSanitize);
18+
}
19+
20+
}
21+
}

Box.Sdk.Gen/Internal/Utils.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,5 +350,25 @@ internal static object GetValueFromObjectRawData(object obj, string key)
350350

351351
return current;
352352
}
353+
354+
/// <summary>
355+
/// Returns a string replacement for sensitive data.
356+
/// </summary>
357+
/// <returns>A string replacement for sensitive data.</returns>
358+
internal static string SanitizedValue() => "---[redacted]---";
359+
360+
/// <summary>
361+
/// Sanitizes a dictionary from sensitive data.
362+
/// </summary>
363+
/// <param name="mapToSanitize">Dictionary to sanitize.</param>
364+
/// <param name="keysToSanitize">Keys to sanitize.</param>
365+
/// <returns>Sanitized dictionary.</returns>
366+
internal static Dictionary<string, string> SanitizeMap(Dictionary<string, string> mapToSanitize, Dictionary<string, string> keysToSanitize)
367+
{
368+
return mapToSanitize.ToDictionary(kvp => kvp.Key,
369+
kvp => keysToSanitize.ContainsKey(kvp.Key.ToLower()) ? SanitizedValue() : kvp.Value);
370+
371+
}
372+
353373
}
354374
}

Box.Sdk.Gen/Networking/BoxNetworkClient/BoxNetworkClient.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,9 @@ private static async Task<Exception> BuildApiException(HttpRequestMessage reques
259259
var responseInfo = new ResponseInfo(statusCode, responseHeaders, responseAsSerializedData, responseContent, errorDetails.Code, errorDetails.ContextInfo,
260260
errorDetails.RequestId, errorDetails.HelpUrl);
261261

262-
return new BoxApiException(responseContent, DateTimeOffset.UtcNow, requestInfo, responseInfo);
262+
var dataSanitizer = options.NetworkSession?.DataSanitizer ?? new DataSanitizer();
263+
264+
return new BoxApiException(responseContent, DateTimeOffset.UtcNow, requestInfo, responseInfo) {DataSanitizer = dataSanitizer};
263265
}
264266

265267
private static async Task<HttpRequestMessage> BuildHttpRequest(FetchOptions options, Stream? stream)

Box.Sdk.Gen/Networking/NetworkSession.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ public class NetworkSession
3838
/// </summary>
3939
public INetworkClient NetworkClient { get; init; } = new BoxNetworkClient();
4040

41+
/// <summary>
42+
/// Data sanitizer used to sanitize sensitive data for logging.
43+
/// </summary>
44+
public DataSanitizer DataSanitizer { get; init; } = new DataSanitizer();
45+
4146
public NetworkSession(Dictionary<string, string>? additionalHeaders = default, BaseUrls? baseUrls = null)
4247
{
4348
AdditionalHeaders = additionalHeaders ?? new Dictionary<string, string>();
@@ -50,7 +55,7 @@ public NetworkSession(Dictionary<string, string>? additionalHeaders = default, B
5055
/// </summary>
5156
public NetworkSession WithNetworkClient(INetworkClient networkClient)
5257
{
53-
return new NetworkSession(this.AdditionalHeaders, this.BaseUrls) { RetryAttempts = this.RetryAttempts, RetryStrategy = this.RetryStrategy, proxyConfig = this.proxyConfig, NetworkClient = networkClient };
58+
return new NetworkSession(this.AdditionalHeaders, this.BaseUrls) { RetryAttempts = this.RetryAttempts, RetryStrategy = this.RetryStrategy, proxyConfig = this.proxyConfig, NetworkClient = networkClient, DataSanitizer = this.DataSanitizer };
5459
}
5560

5661
/// <summary>
@@ -62,7 +67,7 @@ public NetworkSession WithNetworkClient(INetworkClient networkClient)
6267
/// </param>
6368
public NetworkSession WithAdditionalHeaders(Dictionary<string, string> additionalHeaders)
6469
{
65-
return new NetworkSession(DictionaryUtils.MergeDictionaries(this.AdditionalHeaders, additionalHeaders), this.BaseUrls) { RetryAttempts = this.RetryAttempts, RetryStrategy = this.RetryStrategy, proxyConfig = this.proxyConfig, NetworkClient = this.NetworkClient};
70+
return new NetworkSession(DictionaryUtils.MergeDictionaries(this.AdditionalHeaders, additionalHeaders), this.BaseUrls) { RetryAttempts = this.RetryAttempts, RetryStrategy = this.RetryStrategy, proxyConfig = this.proxyConfig, NetworkClient = this.NetworkClient, DataSanitizer = this.DataSanitizer };
6671
}
6772

6873
/// <summary>
@@ -74,7 +79,7 @@ public NetworkSession WithAdditionalHeaders(Dictionary<string, string> additiona
7479
/// </param>
7580
public NetworkSession WithCustomBaseUrls(BaseUrls baseUrls)
7681
{
77-
return new NetworkSession(this.AdditionalHeaders, baseUrls) { RetryAttempts = this.RetryAttempts, RetryStrategy = this.RetryStrategy, proxyConfig = this.proxyConfig, NetworkClient = this.NetworkClient};
82+
return new NetworkSession(this.AdditionalHeaders, baseUrls) { RetryAttempts = this.RetryAttempts, RetryStrategy = this.RetryStrategy, proxyConfig = this.proxyConfig, NetworkClient = this.NetworkClient, DataSanitizer = this.DataSanitizer };
7883
}
7984

8085
/// <summary>
@@ -86,7 +91,15 @@ public NetworkSession WithCustomBaseUrls(BaseUrls baseUrls)
8691
/// </param>
8792
public NetworkSession WithProxy(ProxyConfig config)
8893
{
89-
return new NetworkSession(this.AdditionalHeaders, this.BaseUrls) { RetryAttempts = this.RetryAttempts, RetryStrategy = this.RetryStrategy, proxyConfig = config, NetworkClient = this.NetworkClient};
94+
return new NetworkSession(this.AdditionalHeaders, this.BaseUrls) { RetryAttempts = this.RetryAttempts, RetryStrategy = this.RetryStrategy, proxyConfig = config, NetworkClient = this.NetworkClient, DataSanitizer = this.DataSanitizer };
95+
}
96+
97+
/// <summary>
98+
/// Generate a fresh network session by duplicating the existing configuration and network parameters,
99+
/// while also including a data sanitizer to be used to sanitize sensitive data for logging.
100+
public NetworkSession WithDataSanitizer(DataSanitizer dataSanitizer)
101+
{
102+
return new NetworkSession(this.AdditionalHeaders, this.BaseUrls) { RetryAttempts = this.RetryAttempts, RetryStrategy = this.RetryStrategy, proxyConfig = this.proxyConfig, NetworkClient = this.NetworkClient, DataSanitizer = dataSanitizer };
90103
}
91104
}
92105
}

Box.Sdk.Gen/Serialization/Json/JsonUtils.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,24 @@ internal static string SdToUrlParams(SerializedData data)
7676

7777
return null;
7878
}
79+
80+
81+
/// <summary>
82+
/// Returns a string replacement for sensitive data.
83+
/// </summary>
84+
/// <returns>A string replacement for sensitive data.</returns>
85+
internal static string SanitizedValue() => "---[redacted]---";
86+
87+
88+
/// <summary>
89+
/// Sanitizes a SerializedData from sensitive data.
90+
/// </summary>
91+
/// <param name="sd">SerializedData to sanitize.</param>
92+
/// <param name="keysToSanitize">Keys to sanitize.</param>
93+
/// <returns>Sanitized SerializedData.</returns>
94+
internal static SerializedData SanitizeSerializedData(SerializedData sd, Dictionary<string, string> keysToSanitize)
95+
{
96+
return sd;
97+
}
7998
}
8099
}

0 commit comments

Comments
 (0)