Skip to content

Commit

Permalink
Merge pull request #2952 from DataDog/appsec-automated-user-events-ch…
Browse files Browse the repository at this point in the history
…eck-for-uuid-in-safe-mode

[APPSEC-10361] Appsec automated user events check for UUID in safe mode
  • Loading branch information
GustavoCaso authored Jul 7, 2023
2 parents 4890f92 + cfe7763 commit 3438aab
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 86 deletions.
4 changes: 3 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ namespace :spec do
end

namespace :appsec do
task all: [:main, :rack, :rails, :sinatra]
task all: [:main, :rack, :rails, :sinatra, :devise]

# Datadog AppSec main specs
RSpec::Core::RakeTask.new(:main) do |t, args|
Expand All @@ -209,6 +209,7 @@ namespace :spec do
:rack,
:sinatra,
:rails,
:devise,
].each do |contrib|
RSpec::Core::RakeTask.new(contrib) do |t, args|
t.pattern = "spec/datadog/appsec/contrib/#{contrib}/**/*_spec.rb"
Expand Down Expand Up @@ -399,6 +400,7 @@ task :ci do
# AppSec contrib specs
declare '✅ 2.1 / ✅ 2.2 / ✅ 2.3 / ✅ 2.4 / ✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ jruby' => 'bundle exec appraisal contrib rake spec:appsec:rack'
declare '✅ 2.1 / ✅ 2.2 / ✅ 2.3 / ✅ 2.4 / ✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ jruby' => 'bundle exec appraisal contrib rake spec:appsec:sinatra'
declare '✅ 2.1 / ✅ 2.2 / ✅ 2.3 / ✅ 2.4 / ✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ jruby' => 'bundle exec appraisal contrib rake spec:appsec:devise'

# AppSec Rails specs
declare '✅ 2.1 / ✅ 2.2 / ✅ 2.3 / ❌ 2.4 / ❌ 2.5 / ❌ 2.6 / ❌ 2.7 / ❌ 3.0 / ❌ 3.1 / ❌ 3.2 / ❌ 3.3 / ❌ jruby' => 'bundle exec appraisal rails32-mysql2 rake spec:appsec:rails'
Expand Down
57 changes: 57 additions & 0 deletions lib/datadog/appsec/contrib/devise/event.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

module Datadog
module AppSec
module Contrib
module Devise
# Class to extract event information from the resource
class Event
UUID_REGEX = /^\h{8}-\h{4}-\h{4}-\h{4}-\h{12}$/.freeze

SAFE_MODE = 'safe'
EXTENDED_MODE = 'extended'

attr_reader :user_id

def initialize(resource, mode)
@resource = resource
@mode = mode
@user_id = nil
@email = nil
@username = nil

extract if @resource
end

def to_h
return @event if defined?(@event)

@event = {}
@event[:email] = @email if @email
@event[:username] = @username if @username
@event
end

private

def extract
@user_id = @resource.id

case @mode
when EXTENDED_MODE
@email = @resource.email
@username = @resource.username
when SAFE_MODE
@user_id = nil unless @user_id && @user_id.to_s =~ UUID_REGEX
else
Datadog.logger.warn(
"Invalid automated user evenst mode: `#{@mode}`. "\
'Supported modes are: `safe` and `extended`.'
)
end
end
end
end
end
end
end
3 changes: 0 additions & 3 deletions lib/datadog/appsec/contrib/devise/patcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ module Devise
module Patcher
include Datadog::AppSec::Contrib::Patcher

DISABLED_MODE = 'disabled'
EXTENDED_MODE = 'extended'

module_function

def patched?
Expand Down
73 changes: 29 additions & 44 deletions lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require_relative '../tracking'
require_relative '../resource'
require_relative '../event'

module Datadog
module AppSec
Expand All @@ -10,79 +11,63 @@ module Devise
module Patcher
# Hook in devise validate method
module AuthenticatablePatch
# rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
# rubocop:disable Metrics/MethodLength
def validate(resource, &block)
result = super
return result unless AppSec.enabled?

return unless Datadog.configuration.appsec.track_user_events.enabled
track_user_events_configuration = Datadog.configuration.appsec.track_user_events

automated_track_user_events_mode = Datadog.configuration.appsec.track_user_events.mode
return result unless track_user_events_configuration.enabled

automated_track_user_events_mode = track_user_events_configuration.mode

appsec_scope = Datadog::AppSec.active_scope

return result unless appsec_scope

devise_resource = resource ? Resource.new(resource) : nil

event_information = {}
user_id = nil

if automated_track_user_events_mode == Patcher::EXTENDED_MODE && devise_resource
resource_email = devise_resource.email
resource_username = devise_resource.username

event_information[:email] = resource_email if resource_email
event_information[:username] = resource_username if resource_username
end

user_id = devise_resource.id if devise_resource && devise_resource.id
event_information = Event.new(devise_resource, automated_track_user_events_mode)

if result
if user_id
Tracking.track_login_success(
appsec_scope.trace,
appsec_scope.service_entry_span,
user_id: user_id,
**event_information
)
if event_information.user_id
Datadog.logger.debug { 'User Login Event success' }
else
Tracking.track_login_success(
appsec_scope.trace,
appsec_scope.service_entry_span,
user_id: nil,
**event_information
)
Datadog.logger.debug { 'User Login Event success, but can\'t extract user ID. Tracking empty event' }
end

return result
end

if devise_resource
Tracking.track_login_failure(
Tracking.track_login_success(
appsec_scope.trace,
appsec_scope.service_entry_span,
user_id: user_id,
user_exists: true,
**event_information
user_id: event_information.user_id,
**event_information.to_h
)

return result
end

user_exists = nil

if resource
user_exists = true
Datadog.logger.debug { 'User Login Event failure users exists' }
else
Tracking.track_login_failure(
appsec_scope.trace,
appsec_scope.service_entry_span,
user_id: nil,
user_exists: false,
**event_information
)
user_exists = false
Datadog.logger.debug { 'User Login Event failure user do not exists' }
end

Tracking.track_login_failure(
appsec_scope.trace,
appsec_scope.service_entry_span,
user_id: event_information.user_id,
user_exists: user_exists,
**event_information.to_h
)

result
end
# rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
# rubocop:enable Metrics/MethodLength
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require_relative '../tracking'
require_relative '../resource'
require_relative '../event'

module Datadog
module AppSec
Expand All @@ -13,9 +14,11 @@ module RegistrationControllerPatch
def create
return super unless AppSec.enabled?

return unless Datadog.configuration.appsec.track_user_events.enabled
track_user_events_configuration = Datadog.configuration.appsec.track_user_events

automated_track_user_events_mode = Datadog.configuration.appsec.track_user_events.mode
return super unless track_user_events_configuration.enabled

automated_track_user_events_mode = track_user_events_configuration.mode

appsec_scope = Datadog::AppSec.active_scope
return super unless appsec_scope
Expand All @@ -24,34 +27,20 @@ def create
if resource.persisted?
devise_resource = Resource.new(resource)

event_information = {}
user_id = devise_resource.id if devise_resource && devise_resource.id

if automated_track_user_events_mode == Patcher::EXTENDED_MODE
resource_email = devise_resource.email
resource_username = devise_resource.username
event_information = Event.new(devise_resource, automated_track_user_events_mode)

event_information[:email] = resource_email if resource_email
event_information[:username] = resource_username if resource_username
end

if user_id
Tracking.track_signup(
appsec_scope.trace,
appsec_scope.service_entry_span,
user_id: user_id,
**event_information
)
if event_information.user_id
Datadog.logger.debug { 'User Signup Event' }
else
Tracking.track_signup(
appsec_scope.trace,
appsec_scope.service_entry_span,
user_id: nil,
**event_information
)
Datadog.logger.warn { 'User Signup Event, but can\'t extract user ID. Tracking empty event' }
end

Tracking.track_signup(
appsec_scope.trace,
appsec_scope.service_entry_span,
user_id: event_information.user_id,
**event_information.to_h
)
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/datadog/kit/appsec/events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def track(event, trace = nil, span = nil, **others)
check_trace_span_integrity(trace, span)

span.set_tag("appsec.events.#{event}.track", 'true')
span.set_tag("_dd.appsec.appsec.events.#{event}.sdk", 'true')
span.set_tag("_dd.appsec.events.#{event}.sdk", 'true')

others.each do |k, v|
raise ArgumentError, 'key cannot be :track' if k.to_sym == :track
Expand All @@ -122,7 +122,7 @@ def track(event, trace = nil, span = nil, **others)
else
set_trace_and_span_context('track', trace, span) do |active_trace, active_span|
active_span.set_tag("appsec.events.#{event}.track", 'true')
active_span.set_tag("_dd.appsec.appsec.events.#{event}.sdk", 'true')
active_span.set_tag("_dd.appsec.events.#{event}.sdk", 'true')

others.each do |k, v|
raise ArgumentError, 'key cannot be :track' if k.to_sym == :track
Expand Down
26 changes: 26 additions & 0 deletions sig/datadog/appsec/contrib/devise/event.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module Datadog
module AppSec
module Contrib
module Devise
class Event
UUID_REGEX: ::Regexp

SAFE_MODE: String

EXTENDED_MODE: String

attr_reader user_id: untyped

def initialize: (Devise::Resource? resource, String mode) -> void


def to_h: () -> Hash[Symbol, untyped]

private

def extract: () -> void
end
end
end
end
end
Loading

0 comments on commit 3438aab

Please sign in to comment.