Skip to content

Commit

Permalink
[Feature] Add option to block the creation and usage of the cookies b…
Browse files Browse the repository at this point in the history
…y name #1901 (#1902)
  • Loading branch information
MSNev committed Sep 16, 2022
2 parents ac1d628 + ef4b7ff commit 5a0396b
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 6 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,9 @@ Cookie Configuration for instance based cookie management added in version 2.6.0
| enabled | boolean | true | A boolean that indicates whether the use of cookies by the SDK is enabled by the current instance. If false, the instance of the SDK initialized by this configuration will not store or read any data from cookies |
| domain | string | null | Custom cookie domain. This is helpful if you want to share Application Insights cookies across subdomains. If not provided uses the value from root `cookieDomain` value. |
| path | string | / | Specifies the path to use for the cookie, if not provided it will use any value from the root `cookiePath` value. |
| getCookie | `(name: string) => string` | null | Function to fetch the named cookie value, if not provided it will use the internal cookie parsing / caching. |
| ignoreCookies | string[] | undefined | Specify the cookie name(s) to be ignored, this will cause any matching cookie name to never be read or written. They may still be explicitly purged or deleted. You do not need to repeat the name in the `blockedCookies` configuration.(Since v2.8.8)
| blockedCookies | string[] | undefined | Specify the cookie name(s) to never be written, this will cause any cookie name to never be created or updated, they will still be read unless also included in the ignoreCookies and may still be explicitly purged or deleted. If not provided defaults to the same list provided in ignoreCookies. (Since v2.8.8)
| getCookie | `(name: string) => string` | null | Function to fetch the named cookie value, if not provided it will use the internal cookie parsing / caching. |
| setCookie | `(name: string, value: string) => void` | null | Function to set the named cookie with the specified value, only called when adding or updating a cookie. |
| delCookie | `(name: string, value: string) => void` | null | Function to delete the named cookie with the specified value, separated from setCookie to avoid the need to parse the value to determine whether the cookie is being added or removed.if not provided it will use the internal cookie parsing / caching. |
Expand Down
99 changes: 97 additions & 2 deletions shared/AppInsightsCore/Tests/Unit/src/CookieManager.Tests.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Assert, AITestClass } from "@microsoft/ai-test-framework";
import { AppInsightsCore, CoreUtils, createCookieMgr, IAppInsightsCore, IConfiguration, ICookieMgrConfig, IPlugin, ITelemetryItem, newId, _legacyCookieMgr } from "../../../src/applicationinsights-core-js"
import { AppInsightsCore, CoreUtils, createCookieMgr, IAppInsightsCore, IConfiguration, ICookieMgrConfig, IPlugin, ITelemetryItem, newId, objExtend, _legacyCookieMgr } from "../../../src/applicationinsights-core-js"
import { _InternalMessageId, LoggingSeverity } from "../../../src/JavaScriptSDK.Enums/LoggingEnums";
import { _InternalLogMessage, DiagnosticLogger } from "../../../src/JavaScriptSDK/DiagnosticLogger";

Expand Down Expand Up @@ -396,7 +396,6 @@ export class CookieManagerTests extends AITestClass {
}
});


this.testCase({
name: "CookieManager: validate setting _canUseCookies correctly enables or blocks cookie usage",
test: () => {
Expand Down Expand Up @@ -427,6 +426,102 @@ export class CookieManagerTests extends AITestClass {
Assert.equal(true, manager.isEnabled());
}
});

this.testCase({
name: "CookieManager: validate ignore Cookies empty setting",
test: () => {

let cookieCfg: ICookieMgrConfig = objExtend(true, {}, this._cookieMgrCfg);
cookieCfg.ignoreCookies = [];

let core = new AppInsightsCore();
core.initialize({
instrumentationKey: "testiKey",
cookieDomain: "MyDomain.com",
cookieCfg: cookieCfg
}, [new ChannelPlugin()]);

let manager = core.getCookieMgr();

let newKey = "test." + newId();
let newValue = newId();
manager.set(newKey, newValue);
Assert.equal(newValue, manager.get(newKey));
Assert.equal(newValue + "; domain=MyDomain.com; path=/", this._testCookies[newKey]);

manager.del(newKey);
Assert.equal("", manager.get(newKey));
Assert.equal(undefined, this._testCookies[newKey]);
}
});

this.testCase({
name: "CookieManager: validate ignore Cookies with a single cookie",
test: () => {

let cookieCfg: ICookieMgrConfig = objExtend(true, {}, this._cookieMgrCfg);
cookieCfg.ignoreCookies = [ "testCookie" ];

let core = new AppInsightsCore();
core.initialize({
instrumentationKey: "testiKey",
cookieDomain: "MyDomain.com",
cookieCfg: cookieCfg
}, [new ChannelPlugin()]);

let manager = core.getCookieMgr();

this._testCookies["testCookie"] = "test value";
Assert.equal("", manager.get("testCookie"), "Check that it can't read the cookie value");

manager.set("testCookie", "new value");
Assert.equal("test value", this._testCookies["testCookie"], "The value was not overwritten");

let newKey = "test." + newId();
let newValue = newId();
manager.set(newKey, newValue);
Assert.equal(newValue, manager.get(newKey));
Assert.equal(newValue + "; domain=MyDomain.com; path=/", this._testCookies[newKey]);

manager.del(newKey);
Assert.equal("", manager.get(newKey));
Assert.equal(undefined, this._testCookies[newKey]);
}
});

this.testCase({
name: "CookieManager: validate blocked Cookies with a single cookie",
test: () => {

let cookieCfg: ICookieMgrConfig = objExtend(true, {}, this._cookieMgrCfg);
cookieCfg.blockedCookies = [ "testCookie" ];

let core = new AppInsightsCore();
core.initialize({
instrumentationKey: "testiKey",
cookieDomain: "MyDomain.com",
cookieCfg: cookieCfg
}, [new ChannelPlugin()]);

let manager = core.getCookieMgr();

this._testCookies["testCookie"] = "test value";
Assert.equal("test value", manager.get("testCookie"), "Check that it can't read the cookie value");

manager.set("testCookie", "new value");
Assert.equal("test value", this._testCookies["testCookie"], "The value was not overwritten");

let newKey = "test." + newId();
let newValue = newId();
manager.set(newKey, newValue);
Assert.equal(newValue, manager.get(newKey));
Assert.equal(newValue + "; domain=MyDomain.com; path=/", this._testCookies[newKey]);

manager.del(newKey);
Assert.equal("", manager.get(newKey));
Assert.equal(undefined, this._testCookies[newKey]);
}
});
}
}

Expand Down
14 changes: 14 additions & 0 deletions shared/AppInsightsCore/src/JavaScriptSDK.Interfaces/ICookieMgr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,20 @@ export interface ICookieMgrConfig {
*/
path?: string;

/**
* Specify the cookie name(s) to be ignored, this will cause any matching cookie name to never be read or written.
* They may still be explicitly purged or deleted. You do not need to repeat the name in the `blockedCookies`
* configuration.(Since v2.8.8)
*/
ignoreCookies?: string[];

/**
* Specify the cookie name(s) to never be written, this will cause any cookie name to never be created or updated,
* they will still be read unless also included in the ignoreCookies and may still be explicitly purged or deleted.
* If not provided defaults to the same list provided in ignoreCookies. (Since v2.8.8)
*/
blockedCookies?: string[];

/**
* Hook function to fetch the named cookie value.
* @param name - The name of the cookie
Expand Down
24 changes: 21 additions & 3 deletions shared/AppInsightsCore/src/JavaScriptSDK/CookieMgr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { IDiagnosticLogger } from "../JavaScriptSDK.Interfaces/IDiagnosticLogger
import { _throwInternal } from "./DiagnosticLogger";
import { dumpObj, getDocument, getLocation, getNavigator, isIE } from "./EnvUtils";
import {
arrForEach, dateNow, getExceptionName, isFunction, isNotNullOrUndefined, isNullOrUndefined, isString, isTruthy, isUndefined,
arrForEach, dateNow, getExceptionName, isArray, isFunction, isNotNullOrUndefined, isNullOrUndefined, isString, isTruthy, isUndefined,
objForEachKey, setValue, strContains, strEndsWith, strTrim
} from "./HelperFuncs";
import { STR_EMPTY } from "./InternalConstants";
Expand Down Expand Up @@ -81,6 +81,24 @@ function _createCookieMgrConfig(rootConfig: IConfiguration): ICookieMgrConfig {
return cookieMgrCfg;
}

function _isIgnoredCookie(cookieMgrCfg: ICookieMgrConfig, name: string) {
if (name && cookieMgrCfg && isArray(cookieMgrCfg.ignoreCookies)) {
return cookieMgrCfg.ignoreCookies.indexOf(name) !== -1;
}

return false;
}

function _isBlockedCookie(cookieMgrCfg: ICookieMgrConfig, name: string) {
if (name && cookieMgrCfg && isArray(cookieMgrCfg.blockedCookies)) {
if (cookieMgrCfg.blockedCookies.indexOf(name) !== -1) {
return true;
}
}

return _isIgnoredCookie(cookieMgrCfg, name);
}

/**
* Helper to return the ICookieMgr from the core (if not null/undefined) or a default implementation
* associated with the configuration or a legacy default.
Expand Down Expand Up @@ -138,7 +156,7 @@ export function createCookieMgr(rootConfig?: IConfiguration, logger?: IDiagnosti
},
set: (name: string, value: string, maxAgeSec?: number, domain?: string, path?: string) => {
let result = false;
if (_isMgrEnabled(cookieMgr)) {
if (_isMgrEnabled(cookieMgr) && !_isBlockedCookie(cookieMgrConfig, name)) {
let values: any = {};
let theValue = strTrim(value || STR_EMPTY);
let idx = theValue.indexOf(";");
Expand Down Expand Up @@ -198,7 +216,7 @@ export function createCookieMgr(rootConfig?: IConfiguration, logger?: IDiagnosti
},
get: (name: string): string => {
let value = STR_EMPTY
if (_isMgrEnabled(cookieMgr)) {
if (_isMgrEnabled(cookieMgr) && !_isIgnoredCookie(cookieMgrConfig, name)) {
value = (cookieMgrConfig.getCookie || _getCookieValue)(name);
}

Expand Down

0 comments on commit 5a0396b

Please sign in to comment.