Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automated user events #3205

Merged
merged 57 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
947d91a
Add passport local and http instrumentations.
hoolioh Jun 1, 2023
01402c7
Add automated event tracking in appsec.
hoolioh Jun 1, 2023
4287fae
Merge branch 'master' into julio/automated-user-events-local
hoolioh Jun 1, 2023
36644b3
Fix tests.
hoolioh Jun 1, 2023
057e2fd
Merge branch 'julio/automated-user-events-local' of github.com:DataDo…
hoolioh Jun 1, 2023
e2972c0
Fix config tests.
hoolioh Jun 1, 2023
6a581a1
Improve code coverage.
hoolioh Jun 1, 2023
d898cbd
Merge branch 'master' of github.com:DataDog/dd-trace-js into julio/au…
hoolioh Jun 1, 2023
1595f8b
Fix PR comments.
hoolioh Jun 2, 2023
f7338c8
Update index.d.ts and test.ts.
hoolioh Jun 6, 2023
7230524
Merge branch 'master' into julio/automated-user-events-local
hoolioh Jun 6, 2023
b2420c8
Update index.d.ts
hoolioh Jun 6, 2023
0c45328
Fix PR comments.
hoolioh Jun 6, 2023
02ff294
Merge branch 'julio/automated-user-events-local' of github.com:DataDo…
hoolioh Jun 6, 2023
6dec14f
Merge branch 'master' into julio/automated-user-events-local
hoolioh Jun 7, 2023
ec3dc06
Update packages/datadog-instrumentations/src/passport-local.js
hoolioh Jun 21, 2023
503fcf4
Update packages/datadog-instrumentations/src/passport-http.js
hoolioh Jun 21, 2023
1fbd3fc
Update packages/datadog-instrumentations/src/passport-local.js
hoolioh Jun 21, 2023
2449f50
Add support for getting the credentials from passport-http instrument…
hoolioh Jun 21, 2023
4a95b76
Fix systems tests:
hoolioh Jun 23, 2023
ec45d9a
Fix PR comments.
hoolioh Jun 23, 2023
9ae13d2
Move common functions to a single utils file.
hoolioh Jun 26, 2023
966a068
Describe safe and extended mode.
hoolioh Jun 26, 2023
0039d24
Fix passport unit tests.
hoolioh Jun 26, 2023
cede1d0
Merge branch 'master' into julio/automated-user-events-local
hoolioh Jun 26, 2023
c69d016
Change 'call' method for 'apply'.
hoolioh Jun 27, 2023
de0a093
Merge branch 'julio/automated-user-events-local' of github.com:DataDo…
hoolioh Jun 27, 2023
48158f1
Fix log.
hoolioh Jun 27, 2023
e65edb0
Fix PR comments regarding config tests.
hoolioh Jun 27, 2023
201a0dd
Fix version pinning.
hoolioh Jun 27, 2023
379ce10
Fix PR suggestion.
hoolioh Jun 27, 2023
272c490
Apply suggestions from code review
hoolioh Jun 27, 2023
d630a1e
Remove enabled from test since it's not in the public API.
hoolioh Jun 27, 2023
9b396dc
Remove versions from main package.
hoolioh Jun 27, 2023
3f6a494
Merge branch 'julio/automated-user-events-local' of github.com:DataDo…
hoolioh Jun 27, 2023
7ba4d6f
Fix PR commit suggestion.
hoolioh Jun 27, 2023
b11a4fb
Update packages/datadog-instrumentations/src/passport-utils.js
simon-id Jun 27, 2023
dea6579
increase coverage in config test
simon-id Jun 27, 2023
df1e4eb
Add PR suggestions.
hoolioh Jun 28, 2023
a9f9546
Merge branch 'julio/automated-user-events-local' of github.com:DataDo…
hoolioh Jun 28, 2023
d0070af
Add PR suggestions.
hoolioh Jun 28, 2023
74f99c2
Improve test coverage.
hoolioh Jun 28, 2023
195f61c
Add express as a dependency in passport tests.
hoolioh Jun 28, 2023
69afe51
move and simplfiy a test
simon-id Jun 28, 2023
e5bd6b4
fix typo
simon-id Jun 28, 2023
94236c9
Remove unused variables.
hoolioh Jun 28, 2023
a1851f2
Merge branch 'julio/automated-user-events-local' of github.com:DataDo…
hoolioh Jun 28, 2023
7c6968f
fix test and cleanup
simon-id Jun 28, 2023
261f57c
cleanup
simon-id Jun 28, 2023
a752a19
optimize setting user tags
simon-id Jun 28, 2023
b98d94a
Apply comment suggestion
simon-id Jun 28, 2023
95e8a04
Apply comment suggestion
simon-id Jun 28, 2023
4a6527a
Apply comment suggestion
simon-id Jun 28, 2023
e30ae8d
Apply comment suggestion
simon-id Jun 28, 2023
c55380e
missing quote
simon-id Jun 28, 2023
4eed545
Fix PR comments.
hoolioh Jun 28, 2023
866d626
Merge branch 'master' into julio/automated-user-events-local
hoolioh Jun 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/datadog-instrumentations/src/helpers/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ module.exports = {
'next': () => require('../next'),
'oracledb': () => require('../oracledb'),
'paperplane': () => require('../paperplane'),
'passport-http': () => require('../passport-http'),
'passport-local': () => require('../passport-local'),
'pg': () => require('../pg'),
'pino': () => require('../pino'),
'pino-pretty': () => require('../pino'),
Expand Down
47 changes: 47 additions & 0 deletions packages/datadog-instrumentations/src/passport-http.js
CarlesDD marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use strict'

const shimmer = require('../../datadog-shimmer')
const { channel, addHook } = require('./helpers/instrument')

const passportVerifyChannel = channel('datadog:passport:verify:finish')

function wrapVerifiedAndPublish (username, password, verified) {
if (passportVerifyChannel.hasSubscribers) {
return shimmer.wrap(verified, function (err, user, info) {
const credentials = { type: 'http', username: username, password: password }
hoolioh marked this conversation as resolved.
Show resolved Hide resolved
passportVerifyChannel.publish({ credentials, user })
return verified.call(this, err, user, info)
})
} else {
return verified
}
}

function wrapVerify (verify, passReq) {
if (passReq) {
return function (req, username, password, verified) {
arguments[3] = wrapVerifiedAndPublish(username, password, verified)
return verify.apply(this, arguments)
}
} else {
return function (username, password, verified) {
arguments[2] = wrapVerifiedAndPublish(username, password, verified)
return verify.apply(this, arguments)
}
}
}

addHook({
name: 'passport-http',
file: 'lib/passport-http/strategies/basic.js',
versions: ['>=0.3.0']
}, BasicStrategy => {
return shimmer.wrap(BasicStrategy, function () {
if (typeof arguments[0] === 'function') {
arguments[0] = wrapVerify(arguments[0], false)
} else {
arguments[1] = wrapVerify(arguments[1], (arguments[0] && arguments[0].passReqToCallback) || false)
}
return BasicStrategy.apply(this, arguments)
})
})
47 changes: 47 additions & 0 deletions packages/datadog-instrumentations/src/passport-local.js
hoolioh marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use strict'

const shimmer = require('../../datadog-shimmer')
const { channel, addHook } = require('./helpers/instrument')

const passportVerifyChannel = channel('datadog:passport:verify:finish')

function wrapVerifiedAndPublish (username, password, verified) {
if (passportVerifyChannel.hasSubscribers) {
return shimmer.wrap(verified, function (err, user, info) {
const credentials = { type: 'local', username, password }
hoolioh marked this conversation as resolved.
Show resolved Hide resolved
passportVerifyChannel.publish({ credentials, user })
return verified.call(this, err, user, info)
hoolioh marked this conversation as resolved.
Show resolved Hide resolved
})
} else {
return verified
}
hoolioh marked this conversation as resolved.
Show resolved Hide resolved
}

function wrapVerify (verify, passReq) {
if (passReq) {
return function (req, username, password, verified) {
arguments[3] = wrapVerifiedAndPublish(username, password, verified)
return verify.apply(this, arguments)
}
} else {
return function (username, password, verified) {
arguments[2] = wrapVerifiedAndPublish(username, password, verified)
return verify.apply(this, arguments)
}
}
}

addHook({
name: 'passport-local',
file: 'lib/strategy.js',
versions: ['>=1.0.0']
}, Strategy => {
return shimmer.wrap(Strategy, function () {
if (typeof arguments[0] === 'function') {
arguments[0] = wrapVerify(arguments[0], false)
} else {
arguments[1] = wrapVerify(arguments[1], (arguments[0] && arguments[0].passReqToCallback) || false)
hoolioh marked this conversation as resolved.
Show resolved Hide resolved
}
return Strategy.apply(this, arguments)
})
})
115 changes: 115 additions & 0 deletions packages/datadog-instrumentations/test/passport-http.spec.js
simon-id marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
'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',
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(async () => {
simon-id marked this conversation as resolved.
Show resolved Hide resolved
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', password: '1234' },
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', password: '1' },
user: false
}
)
})
})
})
106 changes: 106 additions & 0 deletions packages/datadog-instrumentations/test/passport-local.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
'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',
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(async () => {
simon-id marked this conversation as resolved.
Show resolved Hide resolved
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', password: '1234' },
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', password: '1' },
user: false
}
)
})
})
})
1 change: 1 addition & 0 deletions packages/dd-trace/src/appsec/channels.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
}
20 changes: 20 additions & 0 deletions packages/dd-trace/src/appsec/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const {
incomingHttpRequestStart,
incomingHttpRequestEnd,
bodyParser,
passportVerify,
queryParser
} = require('./channels')
const waf = require('./waf')
Expand All @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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 onPassportVerifySafe')
hoolioh marked this conversation as resolved.
Show resolved Hide resolved
return
}

passportTrackEvent(credentials, user, rootSpan, config.appsec.eventTracking.mode)
}

function handleResults (actions, req, res, rootSpan, abortController) {
if (!actions || !req || !res || !rootSpan || !abortController) return

Expand All @@ -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 = {
Expand Down
Loading