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

[APPSEC-10361] Appsec automated user events check for UUID in safe mode #2952

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
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