From f09e07eb62baa3693e9d6d0a0daa6766514409c6 Mon Sep 17 00:00:00 2001 From: Julio Gonzalez <107922352+hoolioh@users.noreply.github.com> Date: Thu, 29 Jun 2023 15:54:52 +0200 Subject: [PATCH] Automated user events (#3205) * Add passport local and http instrumentations. * Add automated event tracking in appsec.. --------- Co-authored-by: Carles Capell <107924659+CarlesDD@users.noreply.github.com> Co-authored-by: simon-id --- docs/test.ts | 5 +- index.d.ts | 13 + .../src/helpers/hooks.js | 2 + .../src/passport-http.js | 22 ++ .../src/passport-local.js | 22 ++ .../src/passport-utils.js | 36 +++ .../test/passport-http.spec.js | 143 ++++++++++ .../test/passport-local.spec.js | 129 +++++++++ .../test/passport-utils.spec.js | 36 +++ packages/dd-trace/src/appsec/channels.js | 1 + packages/dd-trace/src/appsec/index.js | 20 ++ packages/dd-trace/src/appsec/passport.js | 110 ++++++++ .../dd-trace/src/appsec/sdk/track_event.js | 19 +- packages/dd-trace/src/config.js | 11 +- packages/dd-trace/test/appsec/index.spec.js | 71 ++++- .../dd-trace/test/appsec/passport.spec.js | 244 ++++++++++++++++++ .../test/appsec/sdk/track_event.spec.js | 77 ++++-- packages/dd-trace/test/config.spec.js | 29 ++- packages/dd-trace/test/plugins/externals.json | 21 ++ 19 files changed, 972 insertions(+), 39 deletions(-) create mode 100644 packages/datadog-instrumentations/src/passport-http.js create mode 100644 packages/datadog-instrumentations/src/passport-local.js create mode 100644 packages/datadog-instrumentations/src/passport-utils.js create mode 100644 packages/datadog-instrumentations/test/passport-http.spec.js create mode 100644 packages/datadog-instrumentations/test/passport-local.spec.js create mode 100644 packages/datadog-instrumentations/test/passport-utils.spec.js create mode 100644 packages/dd-trace/src/appsec/passport.js create mode 100644 packages/dd-trace/test/appsec/passport.spec.js diff --git a/docs/test.ts b/docs/test.ts index f25c75697a8..d09ebe0a979 100644 --- a/docs/test.ts +++ b/docs/test.ts @@ -107,7 +107,10 @@ tracer.init({ obfuscatorKeyRegex: '.*', obfuscatorValueRegex: '.*', blockedTemplateHtml: './blocked.html', - blockedTemplateJson: './blocked.json' + blockedTemplateJson: './blocked.json', + eventTracking: { + mode: 'safe' + } } }); diff --git a/index.d.ts b/index.d.ts index cc71043c05b..861f6b934f6 100644 --- a/index.d.ts +++ b/index.d.ts @@ -556,6 +556,19 @@ export declare interface TracerOptions { * Specifies a path to a custom blocking template json file. */ blockedTemplateJson?: string, + + /** + * Controls the automated user event tracking configuration + */ + eventTracking?: { + /** + * Controls the automated user event tracking mode. Possible values are disabled, safe and extended. + * On safe mode, any detected Personally Identifiable Information (PII) about the user will be redacted from the event. + * On extended mode, no redaction will take place. + * @default 'safe' + */ + mode?: 'safe' | 'extended' | 'disabled' + } }; /** diff --git a/packages/datadog-instrumentations/src/helpers/hooks.js b/packages/datadog-instrumentations/src/helpers/hooks.js index 3de3189bf2e..1d068b24fee 100644 --- a/packages/datadog-instrumentations/src/helpers/hooks.js +++ b/packages/datadog-instrumentations/src/helpers/hooks.js @@ -71,6 +71,8 @@ module.exports = { 'oracledb': () => require('../oracledb'), 'openai': () => require('../openai'), 'paperplane': () => require('../paperplane'), + 'passport-http': () => require('../passport-http'), + 'passport-local': () => require('../passport-local'), 'pg': () => require('../pg'), 'pino': () => require('../pino'), 'pino-pretty': () => require('../pino'), diff --git a/packages/datadog-instrumentations/src/passport-http.js b/packages/datadog-instrumentations/src/passport-http.js new file mode 100644 index 00000000000..3ffc369a395 --- /dev/null +++ b/packages/datadog-instrumentations/src/passport-http.js @@ -0,0 +1,22 @@ +'use strict' + +const shimmer = require('../../datadog-shimmer') +const { addHook } = require('./helpers/instrument') +const { wrapVerify } = require('./passport-utils') + +addHook({ + name: 'passport-http', + file: 'lib/passport-http/strategies/basic.js', + versions: ['>=0.3.0'] +}, BasicStrategy => { + return shimmer.wrap(BasicStrategy, function () { + const type = 'http' + + if (typeof arguments[0] === 'function') { + arguments[0] = wrapVerify(arguments[0], false, type) + } else { + arguments[1] = wrapVerify(arguments[1], (arguments[0] && arguments[0].passReqToCallback), type) + } + return BasicStrategy.apply(this, arguments) + }) +}) diff --git a/packages/datadog-instrumentations/src/passport-local.js b/packages/datadog-instrumentations/src/passport-local.js new file mode 100644 index 00000000000..d0c48c56ccb --- /dev/null +++ b/packages/datadog-instrumentations/src/passport-local.js @@ -0,0 +1,22 @@ +'use strict' + +const shimmer = require('../../datadog-shimmer') +const { addHook } = require('./helpers/instrument') +const { wrapVerify } = require('./passport-utils') + +addHook({ + name: 'passport-local', + file: 'lib/strategy.js', + versions: ['>=1.0.0'] +}, Strategy => { + return shimmer.wrap(Strategy, function () { + const type = 'local' + + if (typeof arguments[0] === 'function') { + arguments[0] = wrapVerify(arguments[0], false, type) + } else { + arguments[1] = wrapVerify(arguments[1], (arguments[0] && arguments[0].passReqToCallback), type) + } + return Strategy.apply(this, arguments) + }) +}) diff --git a/packages/datadog-instrumentations/src/passport-utils.js b/packages/datadog-instrumentations/src/passport-utils.js new file mode 100644 index 00000000000..5af55ca94c0 --- /dev/null +++ b/packages/datadog-instrumentations/src/passport-utils.js @@ -0,0 +1,36 @@ +'use strict' + +const shimmer = require('../../datadog-shimmer') +const { channel } = require('./helpers/instrument') + +const passportVerifyChannel = channel('datadog:passport:verify:finish') + +function wrapVerifiedAndPublish (username, password, verified, type) { + if (!passportVerifyChannel.hasSubscribers) { + return verified + } + + return shimmer.wrap(verified, function (err, user, info) { + const credentials = { type, username } + passportVerifyChannel.publish({ credentials, user }) + return verified.apply(this, arguments) + }) +} + +function wrapVerify (verify, passReq, type) { + if (passReq) { + return function (req, username, password, verified) { + arguments[3] = wrapVerifiedAndPublish(username, password, verified, type) + return verify.apply(this, arguments) + } + } else { + return function (username, password, verified) { + arguments[2] = wrapVerifiedAndPublish(username, password, verified, type) + return verify.apply(this, arguments) + } + } +} + +module.exports = { + wrapVerify +} diff --git a/packages/datadog-instrumentations/test/passport-http.spec.js b/packages/datadog-instrumentations/test/passport-http.spec.js new file mode 100644 index 00000000000..e772a6680b3 --- /dev/null +++ b/packages/datadog-instrumentations/test/passport-http.spec.js @@ -0,0 +1,143 @@ +'use strict' + +const agent = require('../../dd-trace/test/plugins/agent') +const getPort = require('get-port') +const axios = require('axios') +const dc = require('../../diagnostics_channel') + +withVersions('passport-http', 'passport-http', version => { + describe('passport-http instrumentation', () => { + const passportVerifyChannel = dc.channel('datadog:passport:verify:finish') + let port, server, subscriberStub + + before(() => { + return agent.load(['express', 'passport', 'passport-http'], { client: false }) + }) + before((done) => { + const express = require('../../../versions/express').get() + const passport = require('../../../versions/passport').get() + const BasicStrategy = require(`../../../versions/passport-http@${version}`).get().BasicStrategy + const app = express() + + passport.use(new BasicStrategy((username, password, done) => { + const users = [{ + _id: 1, + username: 'test', + password: '1234', + email: 'testuser@ddog.com' + }] + + const user = users.find(user => (user.username === username) && (user.password === password)) + + if (!user) { + return done(null, false) + } else { + return done(null, user) + } + } + )) + + app.use(passport.initialize()) + app.use(express.json()) + + app.get('/', + passport.authenticate('basic', { + successRedirect: '/grant', + failureRedirect: '/deny', + passReqToCallback: false, + session: false + }) + ) + + app.post('/req', + passport.authenticate('basic', { + successRedirect: '/grant', + failureRedirect: '/deny', + passReqToCallback: true, + session: false + }) + ) + + app.get('/grant', (req, res) => { + res.send('Granted') + }) + + app.get('/deny', (req, res) => { + res.send('Denied') + }) + + passportVerifyChannel.subscribe(function ({ credentials, user, err, info }) { + subscriberStub(arguments[0]) + }) + + getPort().then(newPort => { + port = newPort + server = app.listen(port, () => { + done() + }) + }) + }) + beforeEach(() => { + subscriberStub = sinon.stub() + }) + + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) + + it('should call subscriber with proper arguments on success', async () => { + const res = await axios.get(`http://localhost:${port}/`, { + headers: { + // test:1234 + 'Authorization': 'Basic dGVzdDoxMjM0' + } + }) + + expect(res.status).to.equal(200) + expect(res.data).to.equal('Granted') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'http', username: 'test' }, + user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } + } + ) + }) + + it('should call subscriber with proper arguments on success with passReqToCallback set to true', async () => { + const res = await axios.get(`http://localhost:${port}/`, { + headers: { + // test:1234 + 'Authorization': 'Basic dGVzdDoxMjM0' + } + }) + + expect(res.status).to.equal(200) + expect(res.data).to.equal('Granted') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'http', username: 'test' }, + user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } + } + ) + }) + + it('should call subscriber with proper arguments on failure', async () => { + const res = await axios.get(`http://localhost:${port}/`, { + headers: { + // test:1 + 'Authorization': 'Basic dGVzdDox' + } + }) + + expect(res.status).to.equal(200) + expect(res.data).to.equal('Denied') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'http', username: 'test' }, + user: false + } + ) + }) + }) +}) diff --git a/packages/datadog-instrumentations/test/passport-local.spec.js b/packages/datadog-instrumentations/test/passport-local.spec.js new file mode 100644 index 00000000000..78c9521ab20 --- /dev/null +++ b/packages/datadog-instrumentations/test/passport-local.spec.js @@ -0,0 +1,129 @@ +'use strict' + +const agent = require('../../dd-trace/test/plugins/agent') +const getPort = require('get-port') +const axios = require('axios') +const dc = require('../../diagnostics_channel') + +withVersions('passport-local', 'passport-local', version => { + describe('passport-local instrumentation', () => { + const passportVerifyChannel = dc.channel('datadog:passport:verify:finish') + let port, server, subscriberStub + + before(() => { + return agent.load(['express', 'passport', 'passport-local'], { client: false }) + }) + before((done) => { + const express = require('../../../versions/express').get() + const passport = require(`../../../versions/passport`).get() + const LocalStrategy = require(`../../../versions/passport-local@${version}`).get().Strategy + const app = express() + + passport.use(new LocalStrategy({ usernameField: 'username', passwordField: 'password' }, + (username, password, done) => { + const users = [{ + _id: 1, + username: 'test', + password: '1234', + email: 'testuser@ddog.com' + }] + + const user = users.find(user => (user.username === username) && (user.password === password)) + + if (!user) { + return done(null, false) + } else { + return done(null, user) + } + } + )) + + app.use(passport.initialize()) + app.use(express.json()) + + app.post('/', + passport.authenticate('local', { + successRedirect: '/grant', + failureRedirect: '/deny', + passReqToCallback: false, + session: false + }) + ) + + app.post('/req', + passport.authenticate('local', { + successRedirect: '/grant', + failureRedirect: '/deny', + passReqToCallback: true, + session: false + }) + ) + + app.get('/grant', (req, res) => { + res.send('Granted') + }) + + app.get('/deny', (req, res) => { + res.send('Denied') + }) + + passportVerifyChannel.subscribe(function ({ credentials, user, err, info }) { + subscriberStub(arguments[0]) + }) + + getPort().then(newPort => { + port = newPort + server = app.listen(port, () => { + done() + }) + }) + }) + beforeEach(() => { + subscriberStub = sinon.stub() + }) + + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) + + it('should call subscriber with proper arguments on success', async () => { + const res = await axios.post(`http://localhost:${port}/`, { username: 'test', password: '1234' }) + + expect(res.status).to.equal(200) + expect(res.data).to.equal('Granted') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'local', username: 'test' }, + user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } + } + ) + }) + + it('should call subscriber with proper arguments on success with passReqToCallback set to true', async () => { + const res = await axios.post(`http://localhost:${port}/req`, { username: 'test', password: '1234' }) + + expect(res.status).to.equal(200) + expect(res.data).to.equal('Granted') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'local', username: 'test' }, + user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } + } + ) + }) + + it('should call subscriber with proper arguments on failure', async () => { + const res = await axios.post(`http://localhost:${port}/`, { username: 'test', password: '1' }) + + expect(res.status).to.equal(200) + expect(res.data).to.equal('Denied') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'local', username: 'test' }, + user: false + } + ) + }) + }) +}) diff --git a/packages/datadog-instrumentations/test/passport-utils.spec.js b/packages/datadog-instrumentations/test/passport-utils.spec.js new file mode 100644 index 00000000000..3cf6a64a60a --- /dev/null +++ b/packages/datadog-instrumentations/test/passport-utils.spec.js @@ -0,0 +1,36 @@ +'use strict' + +const proxyquire = require('proxyquire') +const { channel } = require('../src/helpers/instrument') + +const passportVerifyChannel = channel('datadog:passport:verify:finish') + +describe('passport-utils', () => { + const shimmer = { + wrap: sinon.stub() + } + + let passportUtils + + beforeEach(() => { + passportUtils = proxyquire('../src/passport-utils', { + '../../datadog-shimmer': shimmer + }) + }) + + it('should not call wrap when there is no subscribers', () => { + const wrap = passportUtils.wrapVerify(() => {}, false, 'type') + + wrap() + expect(shimmer.wrap).not.to.have.been.called + }) + + it('should call wrap when there is subscribers', () => { + const wrap = passportUtils.wrapVerify(() => {}, false, 'type') + + passportVerifyChannel.subscribe(() => {}) + + wrap() + expect(shimmer.wrap).to.have.been.called + }) +}) diff --git a/packages/dd-trace/src/appsec/channels.js b/packages/dd-trace/src/appsec/channels.js index 6e0eb907cf8..e018f70b37e 100644 --- a/packages/dd-trace/src/appsec/channels.js +++ b/packages/dd-trace/src/appsec/channels.js @@ -7,6 +7,7 @@ module.exports = { bodyParser: dc.channel('datadog:body-parser:read:finish'), incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'), incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'), + passportVerify: dc.channel('datadog:passport:verify:finish'), queryParser: dc.channel('datadog:query:read:finish'), setCookieChannel: dc.channel('datadog:iast:set-cookie') } diff --git a/packages/dd-trace/src/appsec/index.js b/packages/dd-trace/src/appsec/index.js index 5112a20ed93..82d0b6d2d48 100644 --- a/packages/dd-trace/src/appsec/index.js +++ b/packages/dd-trace/src/appsec/index.js @@ -7,6 +7,7 @@ const { incomingHttpRequestStart, incomingHttpRequestEnd, bodyParser, + passportVerify, queryParser } = require('./channels') const waf = require('./waf') @@ -16,6 +17,8 @@ const web = require('../plugins/util/web') const { extractIp } = require('../plugins/util/ip_extractor') const { HTTP_CLIENT_IP } = require('../../../../ext/tags') const { block, setTemplates } = require('./blocking') +const { passportTrackEvent } = require('./passport') +const { storage } = require('../../../datadog-core') let isEnabled = false let config @@ -37,6 +40,10 @@ function enable (_config) { bodyParser.subscribe(onRequestBodyParsed) queryParser.subscribe(onRequestQueryParsed) + if (_config.appsec.eventTracking.enabled) { + passportVerify.subscribe(onPassportVerify) + } + isEnabled = true config = _config } catch (err) { @@ -139,6 +146,18 @@ function onRequestQueryParsed ({ req, res, abortController }) { handleResults(results, req, res, rootSpan, abortController) } +function onPassportVerify ({ credentials, user }) { + const store = storage.getStore() + const rootSpan = store && store.req && web.root(store.req) + + if (!rootSpan) { + log.warn('No rootSpan found in onPassportVerify') + return + } + + passportTrackEvent(credentials, user, rootSpan, config.appsec.eventTracking.mode) +} + function handleResults (actions, req, res, rootSpan, abortController) { if (!actions || !req || !res || !rootSpan || !abortController) return @@ -160,6 +179,7 @@ function disable () { if (incomingHttpRequestEnd.hasSubscribers) incomingHttpRequestEnd.unsubscribe(incomingHttpEndTranslator) if (bodyParser.hasSubscribers) bodyParser.unsubscribe(onRequestBodyParsed) if (queryParser.hasSubscribers) queryParser.unsubscribe(onRequestQueryParsed) + if (passportVerify.hasSubscribers) passportVerify.unsubscribe(onPassportVerify) } module.exports = { diff --git a/packages/dd-trace/src/appsec/passport.js b/packages/dd-trace/src/appsec/passport.js new file mode 100644 index 00000000000..0e745288bd9 --- /dev/null +++ b/packages/dd-trace/src/appsec/passport.js @@ -0,0 +1,110 @@ +'use strict' + +const log = require('../log') +const { trackEvent } = require('./sdk/track_event') +const { setUserTags } = require('./sdk/set_user') + +const UUID_PATTERN = '^[0-9A-F]{8}-[0-9A-F]{4}-[1-5][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$' +const regexUsername = new RegExp(UUID_PATTERN, 'i') + +const SDK_USER_EVENT_PATTERN = '^_dd\\.appsec\\.events\\.users\\.[\\W\\w+]+\\.sdk$' +const regexSdkEvent = new RegExp(SDK_USER_EVENT_PATTERN, 'i') + +function isSdkCalled (tags) { + let called = false + + if (tags && typeof tags === 'object') { + called = Object.entries(tags).some(([key, value]) => regexSdkEvent.test(key) && value === 'true') + } + + return called +} + +// delete this function later if we know it's always credential.username +function getLogin (credentials) { + const type = credentials && credentials.type + let login + if (type === 'local' || type === 'http') { + login = credentials.username + } + + return login +} + +function parseUser (login, passportUser, mode) { + const user = { + 'usr.id': login + } + + if (!user['usr.id']) { + return user + } + + if (passportUser) { + // Guess id + if (passportUser.id) { + user['usr.id'] = passportUser.id + } else if (passportUser._id) { + user['usr.id'] = passportUser._id + } + + if (mode === 'extended') { + if (login) { + user['usr.login'] = login + } + + if (passportUser.email) { + user['usr.email'] = passportUser.email + } + + // Guess username + if (passportUser.username) { + user['usr.username'] = passportUser.username + } else if (passportUser.name) { + user['usr.username'] = passportUser.name + } + } + } + + if (mode === 'safe') { + // Remove PII in safe mode + if (!regexUsername.test(user['usr.id'])) { + user['usr.id'] = ' ' + } + } + + return user +} + +function passportTrackEvent (credentials, passportUser, rootSpan, mode) { + const tags = rootSpan && rootSpan.context() && rootSpan.context()._tags + + if (isSdkCalled(tags)) { + // Don't overwrite tags set by SDK callings + return + } + const user = parseUser(getLogin(credentials), passportUser, mode) + + if (user['usr.id'] === undefined) { + log.warn('No user ID found in authentication instrumentation') + return + } + + if (passportUser) { + // If a passportUser object is published then the login succeded + const userTags = {} + Object.entries(user).forEach(([k, v]) => { + const attr = k.split('.', 2)[1] + userTags[attr] = v + }) + + setUserTags(userTags, rootSpan) + trackEvent('users.login.success', null, 'passportTrackEvent', rootSpan, mode) + } else { + trackEvent('users.login.failure', user, 'passportTrackEvent', rootSpan, mode) + } +} + +module.exports = { + passportTrackEvent +} diff --git a/packages/dd-trace/src/appsec/sdk/track_event.js b/packages/dd-trace/src/appsec/sdk/track_event.js index fa1b46b69c5..8debb932090 100644 --- a/packages/dd-trace/src/appsec/sdk/track_event.js +++ b/packages/dd-trace/src/appsec/sdk/track_event.js @@ -20,7 +20,7 @@ function trackUserLoginSuccessEvent (tracer, user, metadata) { setUserTags(user, rootSpan) - trackEvent(tracer, 'users.login.success', metadata, 'trackUserLoginSuccessEvent', rootSpan) + trackEvent('users.login.success', metadata, 'trackUserLoginSuccessEvent', rootSpan, 'sdk') } function trackUserLoginFailureEvent (tracer, userId, exists, metadata) { @@ -35,7 +35,7 @@ function trackUserLoginFailureEvent (tracer, userId, exists, metadata) { ...metadata } - trackEvent(tracer, 'users.login.failure', fields, 'trackUserLoginFailureEvent') + trackEvent('users.login.failure', fields, 'trackUserLoginFailureEvent', getRootSpan(tracer), 'sdk') } function trackCustomEvent (tracer, eventName, metadata) { @@ -44,10 +44,10 @@ function trackCustomEvent (tracer, eventName, metadata) { return } - trackEvent(tracer, eventName, metadata, 'trackCustomEvent') + trackEvent(eventName, metadata, 'trackCustomEvent', getRootSpan(tracer), 'sdk') } -function trackEvent (tracer, eventName, fields, sdkMethodName, rootSpan = getRootSpan(tracer)) { +function trackEvent (eventName, fields, sdkMethodName, rootSpan, mode) { if (!rootSpan) { log.warn(`Root span not available in ${sdkMethodName}`) return @@ -58,6 +58,14 @@ function trackEvent (tracer, eventName, fields, sdkMethodName, rootSpan = getRoo [MANUAL_KEEP]: 'true' } + if (mode === 'sdk') { + tags[`_dd.appsec.events.${eventName}.sdk`] = 'true' + } + + if (mode === 'safe' || mode === 'extended') { + tags[`_dd.appsec.events.${eventName}.auto.mode`] = mode + } + if (fields) { for (const metadataKey of Object.keys(fields)) { tags[`appsec.events.${eventName}.${metadataKey}`] = '' + fields[metadataKey] @@ -70,5 +78,6 @@ function trackEvent (tracer, eventName, fields, sdkMethodName, rootSpan = getRoo module.exports = { trackUserLoginSuccessEvent, trackUserLoginFailureEvent, - trackCustomEvent + trackCustomEvent, + trackEvent } diff --git a/packages/dd-trace/src/config.js b/packages/dd-trace/src/config.js index 0d7abc397de..2cb18e93ed0 100644 --- a/packages/dd-trace/src/config.js +++ b/packages/dd-trace/src/config.js @@ -387,6 +387,11 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?) maybeFile(appsec.blockedTemplateJson), maybeFile(process.env.DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON) ) + const DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING = coalesce( + appsec.eventTracking && appsec.eventTracking.mode, + process.env.DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING, + 'safe' + ).toLowerCase() const remoteConfigOptions = options.remoteConfig || {} const DD_REMOTE_CONFIGURATION_ENABLED = coalesce( @@ -555,7 +560,11 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?) obfuscatorKeyRegex: DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP, obfuscatorValueRegex: DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP, blockedTemplateHtml: DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML, - blockedTemplateJson: DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON + blockedTemplateJson: DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON, + eventTracking: { + enabled: ['extended', 'safe'].includes(DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING), + mode: DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING + } } this.remoteConfig = { enabled: DD_REMOTE_CONFIGURATION_ENABLED, diff --git a/packages/dd-trace/test/appsec/index.spec.js b/packages/dd-trace/test/appsec/index.spec.js index 035397c423c..d186d5e21cb 100644 --- a/packages/dd-trace/test/appsec/index.spec.js +++ b/packages/dd-trace/test/appsec/index.spec.js @@ -1,7 +1,6 @@ 'use strict' const proxyquire = require('proxyquire') -const log = require('../../src/log') const waf = require('../../src/appsec/waf') const RuleManager = require('../../src/appsec/rule_manager') const appsec = require('../../src/appsec') @@ -9,7 +8,8 @@ const { incomingHttpRequestStart, incomingHttpRequestEnd, bodyParser, - queryParser + queryParser, + passportVerify } = require('../../src/appsec/channels') const Reporter = require('../../src/appsec/reporter') const agent = require('../plugins/agent') @@ -17,12 +17,15 @@ const Config = require('../../src/config') const axios = require('axios') const getPort = require('get-port') const blockedTemplate = require('../../src/appsec/blocked_templates') +const { storage } = require('../../../datadog-core') describe('AppSec Index', () => { let config let AppSec let web let blocking + let passport + let log const RULES = { rules: [{ a: 1 }] } @@ -36,7 +39,11 @@ describe('AppSec Index', () => { obfuscatorKeyRegex: '.*', obfuscatorValueRegex: '.*', blockedTemplateHtml: blockedTemplate.html, - blockedTemplateJson: blockedTemplate.json + blockedTemplateJson: blockedTemplate.json, + eventTracking: { + enabled: true, + mode: 'safe' + } } } @@ -48,9 +55,21 @@ describe('AppSec Index', () => { setTemplates: sinon.stub() } + passport = { + passportTrackEvent: sinon.stub() + } + + log = { + debug: sinon.stub(), + warn: sinon.stub(), + error: sinon.stub() + } + AppSec = proxyquire('../../src/appsec', { + '../log': log, '../plugins/util/web': web, - './blocking': blocking + './blocking': blocking, + './passport': passport }) sinon.stub(waf, 'init').callThrough() @@ -79,7 +98,6 @@ describe('AppSec Index', () => { }) it('should log when enable fails', () => { - sinon.stub(log, 'error') RuleManager.applyRules.restore() const err = new Error('Invalid Rules') @@ -97,11 +115,22 @@ describe('AppSec Index', () => { it('should subscribe to blockable channels', () => { expect(bodyParser.hasSubscribers).to.be.false expect(queryParser.hasSubscribers).to.be.false + expect(passportVerify.hasSubscribers).to.be.false AppSec.enable(config) expect(bodyParser.hasSubscribers).to.be.true expect(queryParser.hasSubscribers).to.be.true + expect(passportVerify.hasSubscribers).to.be.true + }) + + it('should not subscribe to passportVerify if eventTracking is disabled', () => { + config.appsec.eventTracking.enabled = false + + AppSec.disable() + AppSec.enable(config) + + expect(passportVerify.hasSubscribers).to.be.false }) }) @@ -142,6 +171,7 @@ describe('AppSec Index', () => { expect(bodyParser.hasSubscribers).to.be.false expect(queryParser.hasSubscribers).to.be.false + expect(passportVerify.hasSubscribers).to.be.false }) }) @@ -333,7 +363,7 @@ describe('AppSec Index', () => { }) }) - describe('checkRequestData', () => { + describe('Channel handlers', () => { let abortController, req, res, rootSpan beforeEach(() => { @@ -448,6 +478,35 @@ describe('AppSec Index', () => { expect(res.end).to.have.been.called }) }) + + describe('onPassportVerify', () => { + it('Should call passportTrackEvent', () => { + const credentials = { type: 'local', username: 'test' } + const user = { id: '1234', username: 'Test' } + + sinon.stub(storage, 'getStore').returns({ req: {} }) + + passportVerify.publish({ credentials, user }) + + expect(passport.passportTrackEvent).to.have.been.calledOnceWithExactly( + credentials, + user, + rootSpan, + config.appsec.eventTracking.mode) + }) + + it('Should call log if no rootSpan is found', () => { + const credentials = { type: 'local', username: 'test' } + const user = { id: '1234', username: 'Test' } + + sinon.stub(storage, 'getStore').returns(undefined) + + passportVerify.publish({ credentials, user }) + + expect(log.warn).to.have.been.calledOnceWithExactly('No rootSpan found in onPassportVerify') + expect(passport.passportTrackEvent).not.to.have.been.called + }) + }) }) }) diff --git a/packages/dd-trace/test/appsec/passport.spec.js b/packages/dd-trace/test/appsec/passport.spec.js new file mode 100644 index 00000000000..9d1ad8a0e00 --- /dev/null +++ b/packages/dd-trace/test/appsec/passport.spec.js @@ -0,0 +1,244 @@ +'use strict' + +const proxyquire = require('proxyquire') + +describe('Passport', () => { + const rootSpan = { + context: () => { return {} } + } + const loginLocal = { type: 'local', username: 'test' } + const userUuid = { + id: '591dc126-8431-4d0f-9509-b23318d3dce4', + email: 'testUser@test.com', + username: 'Test User' + } + + let passportModule, log, events, setUser + beforeEach(() => { + rootSpan.context = () => { return {} } + + log = { + warn: sinon.stub() + } + + events = { + trackEvent: sinon.stub() + } + + setUser = { + setUserTags: sinon.stub() + } + + passportModule = proxyquire('../../src/appsec/passport', { + '../log': log, + './sdk/track_event': events, + './sdk/set_user': setUser + }) + }) + + describe('passportTrackEvent', () => { + it('should call log when credentials is undefined', () => { + passportModule.passportTrackEvent(undefined, undefined, undefined, 'safe') + + expect(log.warn).to.have.been.calledOnceWithExactly('No user ID found in authentication instrumentation') + }) + + it('should call log when type is not known', () => { + const credentials = { type: 'unknown', username: 'test' } + + passportModule.passportTrackEvent(credentials, undefined, undefined, 'safe') + + expect(log.warn).to.have.been.calledOnceWithExactly('No user ID found in authentication instrumentation') + }) + + it('should call log when type is known but username not present', () => { + const credentials = { type: 'http' } + + passportModule.passportTrackEvent(credentials, undefined, undefined, 'safe') + + expect(log.warn).to.have.been.calledOnceWithExactly('No user ID found in authentication instrumentation') + }) + + it('should report login failure when passportUser is not present', () => { + passportModule.passportTrackEvent(loginLocal, undefined, undefined, 'safe') + + expect(setUser.setUserTags).not.to.have.been.called + expect(events.trackEvent).to.have.been.calledOnceWithExactly( + 'users.login.failure', + { 'usr.id': ' ' }, + 'passportTrackEvent', + undefined, + 'safe' + ) + }) + + it('should report login success when passportUser is present', () => { + passportModule.passportTrackEvent(loginLocal, userUuid, rootSpan, 'safe') + + expect(setUser.setUserTags).to.have.been.calledOnceWithExactly({ id: userUuid.id }, rootSpan) + expect(events.trackEvent).to.have.been.calledOnceWithExactly( + 'users.login.success', + null, + 'passportTrackEvent', + rootSpan, + 'safe' + ) + }) + + it('should report login success and blank id in safe mode when id is not a uuid', () => { + const user = { + id: 'publicName', + email: 'testUser@test.com', + username: 'Test User' + } + passportModule.passportTrackEvent(loginLocal, user, rootSpan, 'safe') + + expect(setUser.setUserTags).to.have.been.calledOnceWithExactly({ id: ' ' }, rootSpan) + expect(events.trackEvent).to.have.been.calledOnceWithExactly( + 'users.login.success', + null, + 'passportTrackEvent', + rootSpan, + 'safe' + ) + }) + + it('should report login success and the extended fields in extended mode', () => { + const user = { + id: 'publicName', + email: 'testUser@test.com', + username: 'Test User' + } + + passportModule.passportTrackEvent(loginLocal, user, rootSpan, 'extended') + expect(setUser.setUserTags).to.have.been.calledOnceWithExactly( + { + id: 'publicName', + login: 'test', + email: 'testUser@test.com', + username: 'Test User' + }, + rootSpan + ) + expect(events.trackEvent).to.have.been.calledOnceWithExactly( + 'users.login.success', + null, + 'passportTrackEvent', + rootSpan, + 'extended' + ) + }) + + it('should not call trackEvent in safe mode if sdk user event functions are already called', () => { + rootSpan.context = () => { + return { + _tags: { + '_dd.appsec.events.users.login.success.sdk': 'true' + } + } + } + + passportModule.passportTrackEvent(loginLocal, userUuid, rootSpan, 'safe') + expect(setUser.setUserTags).not.to.have.been.called + expect(events.trackEvent).not.to.have.been.called + }) + + it('should not call trackEvent in extended mode if trackUserLoginSuccessEvent is already called', () => { + rootSpan.context = () => { + return { + _tags: { + '_dd.appsec.events.users.login.success.sdk': 'true' + } + } + } + + passportModule.passportTrackEvent(loginLocal, userUuid, rootSpan, 'extended') + expect(setUser.setUserTags).not.to.have.been.called + expect(events.trackEvent).not.to.have.been.called + }) + + it('should call trackEvent in extended mode if trackCustomEvent function is already called', () => { + rootSpan.context = () => { + return { + _tags: { + '_dd.appsec.events.custom.event.sdk': 'true' + } + } + } + + passportModule.passportTrackEvent(loginLocal, userUuid, rootSpan, 'extended') + expect(events.trackEvent).to.have.been.calledOnceWithExactly( + 'users.login.success', + null, + 'passportTrackEvent', + rootSpan, + 'extended' + ) + }) + + it('should not call trackEvent in extended mode if trackUserLoginFailureEvent is already called', () => { + rootSpan.context = () => { + return { + _tags: { + '_dd.appsec.events.users.login.failure.sdk': 'true' + } + } + } + + passportModule.passportTrackEvent(loginLocal, userUuid, rootSpan, 'extended') + expect(setUser.setUserTags).not.to.have.been.called + expect(events.trackEvent).not.to.have.been.called + }) + + it('should report login success with the _id field', () => { + const user = { + _id: '591dc126-8431-4d0f-9509-b23318d3dce4', + email: 'testUser@test.com', + username: 'Test User' + } + + passportModule.passportTrackEvent(loginLocal, user, rootSpan, 'extended') + expect(setUser.setUserTags).to.have.been.calledOnceWithExactly( + { + id: '591dc126-8431-4d0f-9509-b23318d3dce4', + login: 'test', + email: 'testUser@test.com', + username: 'Test User' + }, + rootSpan + ) + expect(events.trackEvent).to.have.been.calledOnceWithExactly( + 'users.login.success', + null, + 'passportTrackEvent', + rootSpan, + 'extended' + ) + }) + + it('should report login success with the username field passport name', () => { + const user = { + email: 'testUser@test.com', + name: 'Test User' + } + + rootSpan.context = () => { return {} } + + passportModule.passportTrackEvent(loginLocal, user, rootSpan, 'extended') + expect(setUser.setUserTags).to.have.been.calledOnceWithExactly( + { + id: 'test', + login: 'test', + email: 'testUser@test.com', + username: 'Test User' + }, rootSpan) + expect(events.trackEvent).to.have.been.calledOnceWithExactly( + 'users.login.success', + null, + 'passportTrackEvent', + rootSpan, + 'extended' + ) + }) + }) +}) diff --git a/packages/dd-trace/test/appsec/sdk/track_event.spec.js b/packages/dd-trace/test/appsec/sdk/track_event.spec.js index d9b4059b8c1..93d8959783e 100644 --- a/packages/dd-trace/test/appsec/sdk/track_event.spec.js +++ b/packages/dd-trace/test/appsec/sdk/track_event.spec.js @@ -13,7 +13,7 @@ describe('track_event', () => { let rootSpan let getRootSpan let setUserTags - let trackUserLoginSuccessEvent, trackUserLoginFailureEvent, trackCustomEvent + let trackUserLoginSuccessEvent, trackUserLoginFailureEvent, trackCustomEvent, trackEvent beforeEach(() => { log = { @@ -28,7 +28,7 @@ describe('track_event', () => { setUserTags = sinon.stub() - const trackEvent = proxyquire('../../../src/appsec/sdk/track_event', { + const trackEvents = proxyquire('../../../src/appsec/sdk/track_event', { '../../log': log, './utils': { getRootSpan @@ -38,9 +38,10 @@ describe('track_event', () => { } }) - trackUserLoginSuccessEvent = trackEvent.trackUserLoginSuccessEvent - trackUserLoginFailureEvent = trackEvent.trackUserLoginFailureEvent - trackCustomEvent = trackEvent.trackCustomEvent + trackUserLoginSuccessEvent = trackEvents.trackUserLoginSuccessEvent + trackUserLoginFailureEvent = trackEvents.trackUserLoginFailureEvent + trackCustomEvent = trackEvents.trackCustomEvent + trackEvent = trackEvents.trackEvent }) describe('trackUserLoginSuccessEvent', () => { @@ -76,13 +77,15 @@ describe('track_event', () => { expect(log.warn).to.not.have.been.called expect(setUserTags).to.have.been.calledOnceWithExactly(user, rootSpan) - expect(rootSpan.addTags).to.have.been.calledOnceWithExactly({ - 'appsec.events.users.login.success.track': 'true', - 'appsec.events.users.login.success.metakey1': 'metaValue1', - 'appsec.events.users.login.success.metakey2': 'metaValue2', - 'appsec.events.users.login.success.metakey3': 'metaValue3', - 'manual.keep': 'true' - }) + expect(rootSpan.addTags).to.have.been.calledOnceWithExactly( + { + 'appsec.events.users.login.success.track': 'true', + 'manual.keep': 'true', + '_dd.appsec.events.users.login.success.sdk': 'true', + 'appsec.events.users.login.success.metakey1': 'metaValue1', + 'appsec.events.users.login.success.metakey2': 'metaValue2', + 'appsec.events.users.login.success.metakey3': 'metaValue3' + }) }) it('should call setUser and addTags without metadata', () => { @@ -94,7 +97,8 @@ describe('track_event', () => { expect(setUserTags).to.have.been.calledOnceWithExactly(user, rootSpan) expect(rootSpan.addTags).to.have.been.calledOnceWithExactly({ 'appsec.events.users.login.success.track': 'true', - 'manual.keep': 'true' + 'manual.keep': 'true', + '_dd.appsec.events.users.login.success.sdk': 'true' }) }) }) @@ -131,12 +135,13 @@ describe('track_event', () => { expect(setUserTags).to.not.have.been.called expect(rootSpan.addTags).to.have.been.calledOnceWithExactly({ 'appsec.events.users.login.failure.track': 'true', + 'manual.keep': 'true', + '_dd.appsec.events.users.login.failure.sdk': 'true', 'appsec.events.users.login.failure.usr.id': 'user_id', 'appsec.events.users.login.failure.usr.exists': 'true', 'appsec.events.users.login.failure.metakey1': 'metaValue1', 'appsec.events.users.login.failure.metakey2': 'metaValue2', - 'appsec.events.users.login.failure.metakey3': 'metaValue3', - 'manual.keep': 'true' + 'appsec.events.users.login.failure.metakey3': 'metaValue3' }) }) @@ -149,12 +154,13 @@ describe('track_event', () => { expect(setUserTags).to.not.have.been.called expect(rootSpan.addTags).to.have.been.calledOnceWithExactly({ 'appsec.events.users.login.failure.track': 'true', + 'manual.keep': 'true', + '_dd.appsec.events.users.login.failure.sdk': 'true', 'appsec.events.users.login.failure.usr.id': 'user_id', 'appsec.events.users.login.failure.usr.exists': 'false', 'appsec.events.users.login.failure.metakey1': 'metaValue1', 'appsec.events.users.login.failure.metakey2': 'metaValue2', - 'appsec.events.users.login.failure.metakey3': 'metaValue3', - 'manual.keep': 'true' + 'appsec.events.users.login.failure.metakey3': 'metaValue3' }) }) @@ -165,9 +171,10 @@ describe('track_event', () => { expect(setUserTags).to.not.have.been.called expect(rootSpan.addTags).to.have.been.calledOnceWithExactly({ 'appsec.events.users.login.failure.track': 'true', + 'manual.keep': 'true', + '_dd.appsec.events.users.login.failure.sdk': 'true', 'appsec.events.users.login.failure.usr.id': 'user_id', - 'appsec.events.users.login.failure.usr.exists': 'true', - 'manual.keep': 'true' + 'appsec.events.users.login.failure.usr.exists': 'true' }) }) }) @@ -200,9 +207,10 @@ describe('track_event', () => { expect(setUserTags).to.not.have.been.called expect(rootSpan.addTags).to.have.been.calledOnceWithExactly({ 'appsec.events.custom_event.track': 'true', + 'manual.keep': 'true', + '_dd.appsec.events.custom_event.sdk': 'true', 'appsec.events.custom_event.metaKey1': 'metaValue1', - 'appsec.events.custom_event.metakey2': 'metaValue2', - 'manual.keep': 'true' + 'appsec.events.custom_event.metakey2': 'metaValue2' }) }) @@ -213,7 +221,32 @@ describe('track_event', () => { expect(setUserTags).to.not.have.been.called expect(rootSpan.addTags).to.have.been.calledOnceWithExactly({ 'appsec.events.custom_event.track': 'true', - 'manual.keep': 'true' + 'manual.keep': 'true', + '_dd.appsec.events.custom_event.sdk': 'true' + }) + }) + }) + + describe('trackEvent', () => { + it('should call addTags with safe mode', () => { + trackEvent('event', { metaKey1: 'metaValue1', metakey2: 'metaValue2' }, 'trackEvent', rootSpan, 'safe') + expect(rootSpan.addTags).to.have.been.calledOnceWithExactly({ + 'appsec.events.event.track': 'true', + 'manual.keep': 'true', + '_dd.appsec.events.event.auto.mode': 'safe', + 'appsec.events.event.metaKey1': 'metaValue1', + 'appsec.events.event.metakey2': 'metaValue2' + }) + }) + + it('should call addTags with extended mode', () => { + trackEvent('event', { metaKey1: 'metaValue1', metakey2: 'metaValue2' }, 'trackEvent', rootSpan, 'extended') + expect(rootSpan.addTags).to.have.been.calledOnceWithExactly({ + 'appsec.events.event.track': 'true', + 'manual.keep': 'true', + '_dd.appsec.events.event.auto.mode': 'extended', + 'appsec.events.event.metaKey1': 'metaValue1', + 'appsec.events.event.metakey2': 'metaValue2' }) }) }) diff --git a/packages/dd-trace/test/config.spec.js b/packages/dd-trace/test/config.spec.js index 7165fda0d20..0fff0e0a637 100644 --- a/packages/dd-trace/test/config.spec.js +++ b/packages/dd-trace/test/config.spec.js @@ -108,6 +108,8 @@ describe('Config', () => { expect(config).to.have.nested.property('appsec.obfuscatorValueRegex').with.length(443) expect(config).to.have.nested.property('appsec.blockedTemplateHtml', undefined) expect(config).to.have.nested.property('appsec.blockedTemplateJson', undefined) + expect(config).to.have.nested.property('appsec.eventTracking.enabled', true) + expect(config).to.have.nested.property('appsec.eventTracking.mode', 'safe') expect(config).to.have.nested.property('remoteConfig.enabled', true) expect(config).to.have.nested.property('remoteConfig.pollInterval', 5) expect(config).to.have.nested.property('iast.enabled', false) @@ -198,6 +200,7 @@ describe('Config', () => { process.env.DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP = '.*' process.env.DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML = BLOCKED_TEMPLATE_HTML_PATH process.env.DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON = BLOCKED_TEMPLATE_JSON_PATH + process.env.DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING = 'extended' process.env.DD_REMOTE_CONFIGURATION_ENABLED = 'false' process.env.DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS = '42' process.env.DD_IAST_ENABLED = 'true' @@ -265,6 +268,8 @@ describe('Config', () => { expect(config).to.have.nested.property('appsec.obfuscatorValueRegex', '.*') expect(config).to.have.nested.property('appsec.blockedTemplateHtml', BLOCKED_TEMPLATE_HTML) expect(config).to.have.nested.property('appsec.blockedTemplateJson', BLOCKED_TEMPLATE_JSON) + expect(config).to.have.nested.property('appsec.eventTracking.enabled', true) + expect(config).to.have.nested.property('appsec.eventTracking.mode', 'extended') expect(config).to.have.nested.property('remoteConfig.enabled', false) expect(config).to.have.nested.property('remoteConfig.pollInterval', 42) expect(config).to.have.nested.property('iast.enabled', true) @@ -573,6 +578,7 @@ describe('Config', () => { process.env.DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP = '^$' process.env.DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML = BLOCKED_TEMPLATE_JSON // note the inversion between process.env.DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON = BLOCKED_TEMPLATE_HTML // json and html here + process.env.DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING = 'disabled' process.env.DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS = 11 process.env.DD_IAST_ENABLED = 'false' process.env.DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED = 'true' @@ -623,7 +629,10 @@ describe('Config', () => { obfuscatorKeyRegex: '.*', obfuscatorValueRegex: '.*', blockedTemplateHtml: BLOCKED_TEMPLATE_HTML_PATH, - blockedTemplateJson: BLOCKED_TEMPLATE_JSON_PATH + blockedTemplateJson: BLOCKED_TEMPLATE_JSON_PATH, + eventTracking: { + mode: 'safe' + } }, remoteConfig: { pollInterval: 42 @@ -666,6 +675,8 @@ describe('Config', () => { expect(config).to.have.nested.property('appsec.obfuscatorValueRegex', '.*') expect(config).to.have.nested.property('appsec.blockedTemplateHtml', BLOCKED_TEMPLATE_HTML) expect(config).to.have.nested.property('appsec.blockedTemplateJson', BLOCKED_TEMPLATE_JSON) + expect(config).to.have.nested.property('appsec.eventTracking.enabled', true) + expect(config).to.have.nested.property('appsec.eventTracking.mode', 'safe') expect(config).to.have.nested.property('remoteConfig.pollInterval', 42) expect(config).to.have.nested.property('iast.enabled', true) expect(config).to.have.nested.property('iast.requestSampling', 30) @@ -685,7 +696,10 @@ describe('Config', () => { obfuscatorKeyRegex: '.*', obfuscatorValueRegex: '.*', blockedTemplateHtml: undefined, - blockedTemplateJson: undefined + blockedTemplateJson: undefined, + eventTracking: { + mode: 'disabled' + } }, experimental: { appsec: { @@ -696,7 +710,10 @@ describe('Config', () => { obfuscatorKeyRegex: '^$', obfuscatorValueRegex: '^$', blockedTemplateHtml: BLOCKED_TEMPLATE_HTML_PATH, - blockedTemplateJson: BLOCKED_TEMPLATE_JSON_PATH + blockedTemplateJson: BLOCKED_TEMPLATE_JSON_PATH, + eventTracking: { + mode: 'safe' + } } } }) @@ -710,7 +727,11 @@ describe('Config', () => { obfuscatorKeyRegex: '.*', obfuscatorValueRegex: '.*', blockedTemplateHtml: undefined, - blockedTemplateJson: undefined + blockedTemplateJson: undefined, + eventTracking: { + enabled: false, + mode: 'disabled' + } }) }) diff --git a/packages/dd-trace/test/plugins/externals.json b/packages/dd-trace/test/plugins/externals.json index 391fb5733f5..9fce297df9d 100644 --- a/packages/dd-trace/test/plugins/externals.json +++ b/packages/dd-trace/test/plugins/externals.json @@ -170,6 +170,27 @@ "name": "react-dom", "dep": true } + ], + "passport-http": [ + { + "name": "passport", + "versions": [">=0.4.1"] + }, + { + "name": "express", + "versions": [">=4.16.2"] + } + ], + "passport-local": [ + { + "name": "passport", + "versions": [">=0.4.1"] + }, + { + "name": "express", + "versions": [">=4.16.2"] + } + ], "pg": [ {