contrast-agent 3.16.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/contrast/agent.rb +2 -3
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +17 -6
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +3 -2
- data/lib/contrast/agent/inventory.rb +15 -0
- data/lib/contrast/agent/inventory/dependencies.rb +50 -0
- data/lib/contrast/agent/inventory/dependency_analysis.rb +37 -0
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +104 -0
- data/lib/contrast/agent/inventory/gemfile_digest_cache.rb +38 -0
- data/lib/contrast/agent/middleware.rb +1 -2
- data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +4 -3
- data/lib/contrast/agent/request_handler.rb +1 -1
- data/lib/contrast/agent/static_analysis.rb +2 -2
- data/lib/contrast/agent/tracepoint_hook.rb +1 -1
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/decorators.rb +3 -0
- data/lib/contrast/api/decorators/address.rb +0 -1
- data/lib/contrast/api/decorators/application_update.rb +1 -1
- data/lib/contrast/api/decorators/library.rb +53 -0
- data/lib/contrast/api/decorators/library_usage_update.rb +30 -0
- data/lib/contrast/components/agent.rb +6 -5
- data/lib/contrast/components/config.rb +29 -37
- data/lib/contrast/components/interface.rb +25 -3
- data/lib/contrast/components/inventory.rb +6 -1
- data/lib/contrast/config/inventory_configuration.rb +2 -2
- data/lib/contrast/framework/rails/support.rb +3 -0
- data/lib/contrast/logger/application.rb +1 -1
- data/lib/contrast/utils/inventory_util.rb +0 -7
- data/lib/contrast/utils/sha256_builder.rb +0 -12
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +9 -4
- data/lib/contrast/utils/boolean_util.rb +0 -30
- data/lib/contrast/utils/gemfile_reader.rb +0 -193
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ce67e5e10275cf1425bc05d510e6a2b0b436f90c180027bfce2abfeccf11e86e
|
4
|
+
data.tar.gz: 5644a75c92b7502f285030c83f3469820c6efdf8c582eb32328d8aefe2f4821a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c777194316965c247ced91d30731f9d263db482975fe4702d20332f224ef17c1f23ed281066b07398a900b5f5561019fe15ef6bb882f64c38efb05a693100073
|
7
|
+
data.tar.gz: 6a3cf5245ba8448d4023b9e29e608058747dc66c7f1296265d1b2f589ee55bedabe192058eb638839667546acb14650397a35ae36127a0ce316d2a8c876be3a8
|
data/lib/contrast/agent.rb
CHANGED
@@ -23,7 +23,6 @@ require 'contrast/extension/protect'
|
|
23
23
|
require 'contrast/extension/protect/kernel'
|
24
24
|
|
25
25
|
require 'contrast/utils/object_share'
|
26
|
-
require 'contrast/utils/boolean_util'
|
27
26
|
require 'contrast/utils/string_utils'
|
28
27
|
require 'contrast/utils/io_util'
|
29
28
|
require 'contrast/utils/os'
|
@@ -88,8 +87,8 @@ require 'contrast/agent/assess'
|
|
88
87
|
# protect rules
|
89
88
|
require 'contrast/agent/protect/rule'
|
90
89
|
|
91
|
-
# application libraries
|
92
|
-
require 'contrast/
|
90
|
+
# application libraries and technologies
|
91
|
+
require 'contrast/agent/inventory'
|
93
92
|
|
94
93
|
# rack event monitoring
|
95
94
|
require 'contrast/agent/middleware'
|
@@ -17,22 +17,33 @@ module Contrast
|
|
17
17
|
access_component :analysis
|
18
18
|
|
19
19
|
class << self
|
20
|
+
# Use the given trace_point, built from an :end event, to determine
|
21
|
+
# where the loaded code lives and scan that code for policy
|
22
|
+
# violations.
|
23
|
+
#
|
24
|
+
# @param trace_point [TracePoint] the TracePoint generated by an
|
25
|
+
# :end event at the end of a Module definition.
|
20
26
|
def scan trace_point
|
21
27
|
return unless ASSESS.enabled?
|
22
28
|
return unless ASSESS.require_scan?
|
23
29
|
|
30
|
+
provider_values = policy.providers.values
|
31
|
+
return if provider_values.all?(&:disabled?)
|
32
|
+
|
24
33
|
return unless trace_point.path
|
25
34
|
return if trace_point.path.start_with?(Gem.dir)
|
26
35
|
|
27
36
|
mod = trace_point.self
|
28
37
|
return if mod.cs__frozen? || mod.singleton_class?
|
29
38
|
|
30
|
-
# TODO: RUBY-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
provider.parse(trace_point)
|
35
|
-
|
39
|
+
# TODO: RUBY-1014 - remove non-AST approach
|
40
|
+
if RUBY_VERSION >= '2.6.0'
|
41
|
+
ast = RubyVM::AbstractSyntaxTree.parse_file(trace_point.path)
|
42
|
+
provider_values.each do |provider|
|
43
|
+
provider.parse(trace_point, ast)
|
44
|
+
end
|
45
|
+
else
|
46
|
+
provider_values.each do |provider|
|
36
47
|
provider.analyze(mod)
|
37
48
|
end
|
38
49
|
end
|
@@ -85,10 +85,11 @@ module Contrast
|
|
85
85
|
#
|
86
86
|
# @param trace_point [TracePoint] the TracePoint event created on
|
87
87
|
# the :end of a Module being loaded
|
88
|
-
|
88
|
+
# @param ast [RubyVM::AbstractSyntaxTree::Node] the abstract syntax
|
89
|
+
# tree of the Module defined in the TracePoint end event
|
90
|
+
def parse trace_point, ast
|
89
91
|
return if disabled?
|
90
92
|
|
91
|
-
ast = RubyVM::AbstractSyntaxTree.parse_file(trace_point.path)
|
92
93
|
parse_ast(trace_point.self, ast)
|
93
94
|
rescue StandardError => e
|
94
95
|
logger.error('Unable to parse AST for hardcoded keys', e, module: trace_point.self)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Contrast
|
5
|
+
module Agent
|
6
|
+
# Namespace used for inventory behavior
|
7
|
+
module Inventory
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'contrast/agent/inventory/dependencies'
|
13
|
+
require 'contrast/agent/inventory/gemfile_digest_cache'
|
14
|
+
require 'contrast/agent/inventory/dependency_usage_analysis'
|
15
|
+
require 'contrast/agent/inventory/dependency_analysis'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Contrast
|
5
|
+
module Agent
|
6
|
+
module Inventory
|
7
|
+
# this module is included in classes that need access to the applications dependencies
|
8
|
+
module Dependencies
|
9
|
+
CONTRAST_AGENT = 'contrast-agent'
|
10
|
+
|
11
|
+
# the #clone is necessary here, as a require in another thread could
|
12
|
+
# potentially result in adding a key to the loaded_specs hash during
|
13
|
+
# iteration. (as in RUBY-330)
|
14
|
+
# this takes care of filtering out contrast-only dependencies
|
15
|
+
def loaded_specs
|
16
|
+
specs = Gem.loaded_specs.clone
|
17
|
+
specs.delete_if { |name, _v| contrast?(name) }
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def contrast_gems
|
23
|
+
@_contrast_gems ||= find_contrast_gems
|
24
|
+
end
|
25
|
+
|
26
|
+
def contrast? name
|
27
|
+
contrast_gems.include?(name)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Go through all dependents, given as a pair from the DependencyList: `dependency`
|
31
|
+
# is the dependency itself, filled with all its specs. `dependents` is the array of reverse
|
32
|
+
# dependencies for the aforementioned dependency. If the dependency is also in contrast_dep_set,
|
33
|
+
# then contrast depends on it. If its array of dependents is 1, then contrast is the
|
34
|
+
# only dependency in that list. Since only contrast depends on it, we should ignore it.
|
35
|
+
def find_contrast_gems
|
36
|
+
ignore = Set.new([CONTRAST_AGENT])
|
37
|
+
contrast_specs = Gem::DependencyList.from_specs.specs.find do |dependency|
|
38
|
+
dependency.name == CONTRAST_AGENT
|
39
|
+
end
|
40
|
+
contrast_dep_set = contrast_specs.dependencies.map(&:name).to_set
|
41
|
+
|
42
|
+
Gem::DependencyList.from_specs.spec_predecessors.each_pair do |dependency, dependents|
|
43
|
+
ignore.add(dependency.name) if contrast_dep_set.include?(dependency.name) && dependents.length == 1
|
44
|
+
end
|
45
|
+
ignore
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/inventory/dependencies'
|
5
|
+
require 'contrast/components/interface'
|
6
|
+
require 'contrast/utils/object_share'
|
7
|
+
|
8
|
+
module Contrast
|
9
|
+
module Agent
|
10
|
+
module Inventory
|
11
|
+
# Used to collect dependencies of the application for reporting
|
12
|
+
class DependencyAnalysis
|
13
|
+
include Singleton
|
14
|
+
include Contrast::Agent::Inventory::Dependencies
|
15
|
+
include Contrast::Components::Interface
|
16
|
+
|
17
|
+
access_component :analysis
|
18
|
+
|
19
|
+
# Report the dependencies of this application
|
20
|
+
#
|
21
|
+
# @return [Array<Contrast::Api::Dtm::Library>] protobuf form of the
|
22
|
+
# Gem::Specification that have been loaded for this application.
|
23
|
+
def library_pb_list
|
24
|
+
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless INVENTORY.enabled?
|
25
|
+
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless INVENTORY.analyze_libraries?
|
26
|
+
|
27
|
+
loaded_specs.each_with_object([]) do |(_name, spec), reported_lib_list|
|
28
|
+
next unless spec
|
29
|
+
next unless (digest = Contrast::Utils::Sha256Builder.instance.build_from_spec(spec))
|
30
|
+
|
31
|
+
reported_lib_list << Contrast::Api::Dtm::Library.build(digest, spec)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/inventory/gemfile_digest_cache'
|
5
|
+
require 'contrast/agent/inventory/dependencies'
|
6
|
+
require 'contrast/components/interface'
|
7
|
+
require 'contrast/utils/object_share'
|
8
|
+
|
9
|
+
module Contrast
|
10
|
+
module Agent
|
11
|
+
module Inventory
|
12
|
+
# Used to analyze class usage for reporting
|
13
|
+
class DependencyUsageAnalysis
|
14
|
+
include Singleton
|
15
|
+
include Contrast::Components::Interface
|
16
|
+
include Contrast::Agent::Inventory::Dependencies
|
17
|
+
|
18
|
+
access_component :analysis, :config, :logging
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
return unless enabled?
|
22
|
+
|
23
|
+
@gemdigest_cache = Contrast::Agent::Inventory::GemfileDigestCache.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# This method is invoked once, along with the rest of our catchup code
|
27
|
+
# to report libraries and their associated files that have already been loaded pre-contrast
|
28
|
+
def catchup
|
29
|
+
return unless enabled?
|
30
|
+
|
31
|
+
loaded_specs.each do |_name, spec|
|
32
|
+
# Get a digest of the Gem file itself
|
33
|
+
next unless (digest = Contrast::Utils::Sha256Builder.instance.build_from_spec(spec))
|
34
|
+
|
35
|
+
@gemdigest_cache.use_cache(digest) do |existing_files|
|
36
|
+
loaded_files_from_gem = $LOADED_FEATURES.select { |f| f.start_with?(spec.full_gem_path) }
|
37
|
+
loaded_files_from_gem.each do |file_path|
|
38
|
+
logger.trace('Recording loaded file for inventory analysis', line: file_path)
|
39
|
+
existing_files << adjust_path_for_reporting(file_path, spec)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# This method is invoked once per TracePoint :end - to map a specific
|
46
|
+
# file being required to the gem it belongs to
|
47
|
+
#
|
48
|
+
# @param path [String] the result of TracePoint#path from the :end
|
49
|
+
# event in which the Module was defined
|
50
|
+
def associate_file path
|
51
|
+
return unless enabled?
|
52
|
+
|
53
|
+
spec_lookup_path = adjust_path_for_spec_lookup(path)
|
54
|
+
|
55
|
+
spec = Gem::Specification.find_by_path(spec_lookup_path)
|
56
|
+
unless spec
|
57
|
+
logger.debug('Unable to resolve gem spec for path', path: path)
|
58
|
+
return
|
59
|
+
end
|
60
|
+
|
61
|
+
digest = Contrast::Utils::Sha256Builder.instance.build_from_spec(spec)
|
62
|
+
unless digest
|
63
|
+
logger.debug('Unable to resolve digest for gem spec', spec: spec.to_s)
|
64
|
+
return
|
65
|
+
end
|
66
|
+
report_path = adjust_path_for_reporting(path, spec)
|
67
|
+
@gemdigest_cache.get(digest) << report_path
|
68
|
+
rescue StandardError => e
|
69
|
+
logger.error('Unable to inventory file path', e, path: path)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Populate the library_usages filed of the Activity message using the
|
73
|
+
# data stored in the @gemdigest_cache
|
74
|
+
#
|
75
|
+
# @param activity [Contrast::Api::Dtm::Activity] the message to which
|
76
|
+
# to append the usage data
|
77
|
+
def generate_library_usage activity
|
78
|
+
return unless enabled?
|
79
|
+
return if @gemdigest_cache.empty?
|
80
|
+
|
81
|
+
@gemdigest_cache.generate_usage_data(activity)
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def adjust_path_for_spec_lookup path
|
87
|
+
idx = path.index('/lib/')
|
88
|
+
path = path[(idx + 4)..-1] if idx
|
89
|
+
path
|
90
|
+
end
|
91
|
+
|
92
|
+
def adjust_path_for_reporting path, gem_spec
|
93
|
+
path.delete_prefix(gem_spec.full_gem_path)
|
94
|
+
end
|
95
|
+
|
96
|
+
# We only use this if inventory and library analysis are enabled
|
97
|
+
def enabled?
|
98
|
+
@_enabled = INVENTORY.enabled? && INVENTORY.analyze_libraries? if @_enabled.nil?
|
99
|
+
@_enabled
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Contrast
|
5
|
+
module Agent
|
6
|
+
module Inventory
|
7
|
+
# Keeps a map of gem digest to files for reporting file usage
|
8
|
+
class GemfileDigestCache
|
9
|
+
extend Forwardable
|
10
|
+
def_delegator :@gem_spec_digest_to_files,
|
11
|
+
:empty?
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@gem_spec_digest_to_files = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate_usage_data activity
|
18
|
+
return unless activity
|
19
|
+
|
20
|
+
@gem_spec_digest_to_files.each_pair do |digest, files|
|
21
|
+
usage = Contrast::Api::Dtm::LibraryUsageUpdate.build(digest, files)
|
22
|
+
activity.library_usages[usage.hash_code] = usage if activity
|
23
|
+
end
|
24
|
+
@gem_spec_digest_to_files.clear
|
25
|
+
end
|
26
|
+
|
27
|
+
def use_cache digest
|
28
|
+
yield get(digest)
|
29
|
+
end
|
30
|
+
|
31
|
+
def get digest
|
32
|
+
@gem_spec_digest_to_files[digest] = Set.new unless @gem_spec_digest_to_files.key?(digest)
|
33
|
+
@gem_spec_digest_to_files[digest]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -94,8 +94,7 @@ module Contrast
|
|
94
94
|
if CONFIG.invalid?
|
95
95
|
AGENT.disable!
|
96
96
|
logger.error('!!! CONFIG FILE IS INVALID - DISABLING CONTRAST AGENT !!!')
|
97
|
-
elsif
|
98
|
-
AGENT.disable!
|
97
|
+
elsif AGENT.disabled?
|
99
98
|
logger.warn('Contrast disabled by configuration. Continuing without instrumentation.')
|
100
99
|
else
|
101
100
|
AGENT.enable!
|
@@ -26,7 +26,7 @@ module Contrast
|
|
26
26
|
|
27
27
|
action = properties['action']
|
28
28
|
write_marker = write?(action, *args)
|
29
|
-
possible_write = write_marker && possible_write(write_marker)
|
29
|
+
possible_write = write_marker && possible_write?(write_marker)
|
30
30
|
path_traversal_rule(path, possible_write, object, method)
|
31
31
|
|
32
32
|
# If the action was copy, we need to handle the write half of it.
|
@@ -48,7 +48,7 @@ module Contrast
|
|
48
48
|
|
49
49
|
private
|
50
50
|
|
51
|
-
def possible_write input
|
51
|
+
def possible_write? input
|
52
52
|
input.cs__respond_to?(:to_s) &&
|
53
53
|
input.to_s.include?(Contrast::Utils::ObjectShare::WRITE_FLAG)
|
54
54
|
end
|
@@ -62,7 +62,7 @@ module Contrast
|
|
62
62
|
return true if action == WRITE
|
63
63
|
|
64
64
|
write_marker = args.length > 1 ? args[1] : nil
|
65
|
-
write_marker && possible_write(write_marker)
|
65
|
+
write_marker && possible_write?(write_marker)
|
66
66
|
end
|
67
67
|
|
68
68
|
def path_traversal_rule path, possible_write, object, method
|
@@ -83,6 +83,7 @@ module Contrast
|
|
83
83
|
tmp = CS__SAFER_REL_PATHS.map { |r| "#{ pwd }/#{ r }" }
|
84
84
|
gems = ENV['GEM_PATH']
|
85
85
|
tmp += gems.split(Contrast::Utils::ObjectShare::COLON) if gems
|
86
|
+
tmp.map!(&:downcase)
|
86
87
|
tmp
|
87
88
|
else
|
88
89
|
[]
|
@@ -18,7 +18,7 @@ module Contrast
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def send_activity_messages
|
21
|
-
Contrast::
|
21
|
+
Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.generate_library_usage(context.activity)
|
22
22
|
[context.server_activity, context.activity, context.observed_route].each do |message|
|
23
23
|
Contrast::Agent.messaging_queue.send_event_eventually(message)
|
24
24
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'contrast/components/interface'
|
5
|
-
require 'contrast/
|
5
|
+
require 'contrast/agent/inventory'
|
6
6
|
require 'contrast/api/decorators/application_update'
|
7
7
|
|
8
8
|
module Contrast
|
@@ -18,7 +18,7 @@ module Contrast
|
|
18
18
|
def catchup
|
19
19
|
@_catchup ||= begin
|
20
20
|
with_contrast_scope do
|
21
|
-
Contrast::
|
21
|
+
Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.catchup
|
22
22
|
send_inventory_message
|
23
23
|
true
|
24
24
|
end
|
@@ -35,7 +35,7 @@ module Contrast
|
|
35
35
|
path = tracepoint_event.path
|
36
36
|
return if path&.include?('contrast')
|
37
37
|
|
38
|
-
Contrast::
|
38
|
+
Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.associate_file(path) if path
|
39
39
|
Contrast::Agent::Patching::Policy::Patcher.patch_specific_module(loaded_module)
|
40
40
|
Contrast::Agent::Assess::Policy::RewriterPatch.rewrite_interpolation(loaded_module)
|
41
41
|
Contrast::Agent::Assess::Policy::PolicyScanner.scan(tracepoint_event)
|
@@ -8,11 +8,14 @@ module Contrast
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
11
|
+
|
11
12
|
require 'contrast/api/decorators/message'
|
12
13
|
require 'contrast/api/decorators/application_update'
|
13
14
|
require 'contrast/api/decorators/input_analysis'
|
14
15
|
require 'contrast/api/decorators/application_settings'
|
15
16
|
require 'contrast/api/decorators/server_features'
|
17
|
+
require 'contrast/api/decorators/library'
|
18
|
+
require 'contrast/api/decorators/library_usage_update'
|
16
19
|
require 'contrast/api/decorators/route_coverage'
|
17
20
|
require 'contrast/api/decorators/trace_event_object'
|
18
21
|
require 'contrast/api/decorators/trace_event_signature'
|
@@ -44,7 +44,7 @@ module Contrast
|
|
44
44
|
msg = new
|
45
45
|
msg.append_route_coverage_data(Contrast::Agent.framework_manager.find_route_discovery_data)
|
46
46
|
msg.append_platform_version(Contrast::Agent.framework_manager.platform_version)
|
47
|
-
msg.append_library_update(Contrast::
|
47
|
+
msg.append_library_update(Contrast::Agent::Inventory::DependencyAnalysis.instance.library_pb_list)
|
48
48
|
msg
|
49
49
|
end
|
50
50
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/utils/string_utils'
|
5
|
+
require 'contrast/utils/sha256_builder'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
module Contrast
|
9
|
+
module Api
|
10
|
+
module Decorators
|
11
|
+
# Used to decorate the Library protobuf model to handle Gem::Specification translation
|
12
|
+
module Library
|
13
|
+
def self.included klass
|
14
|
+
klass.extend(ClassMethods)
|
15
|
+
end
|
16
|
+
# Used to add class methods to the Library class on inclusion of the decorator
|
17
|
+
module ClassMethods
|
18
|
+
def build digest, gem_specification
|
19
|
+
msg = new
|
20
|
+
msg.file_path = Contrast::Utils::StringUtils.force_utf8(gem_specification.name)
|
21
|
+
msg.hash_code = Contrast::Utils::StringUtils.force_utf8(digest)
|
22
|
+
msg.version = Contrast::Utils::StringUtils.force_utf8(gem_specification.version)
|
23
|
+
msg.manifest = Contrast::Utils::StringUtils.force_utf8(build_manifest(gem_specification))
|
24
|
+
msg.external_ms = date_to_ms(gem_specification.date)
|
25
|
+
msg.internal_ms = msg.external_ms
|
26
|
+
msg.url = Contrast::Utils::StringUtils.force_utf8(gem_specification.homepage)
|
27
|
+
msg.class_count = file_count(gem_specification.full_gem_path.to_s)
|
28
|
+
msg.used_class_count = 0
|
29
|
+
msg
|
30
|
+
end
|
31
|
+
|
32
|
+
# These are all the code files that are located in the Gem directory loaded
|
33
|
+
# by the current environment; this includes more than Ruby files
|
34
|
+
def file_count path
|
35
|
+
Contrast::Utils::Sha256Builder.instance.files(path).length
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_manifest spec
|
39
|
+
Contrast::Utils::StringUtils.force_utf8(spec.to_yaml.to_s)
|
40
|
+
rescue StandardError
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def date_to_ms date
|
45
|
+
(date.to_f * 1000.0).to_i
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
Contrast::Api::Dtm::Library.include(Contrast::Api::Decorators::Library)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/utils/string_utils'
|
5
|
+
|
6
|
+
module Contrast
|
7
|
+
module Api
|
8
|
+
module Decorators
|
9
|
+
# Used to decorate the LibraryUsageUpdate protobuf
|
10
|
+
module LibraryUsageUpdate
|
11
|
+
def self.included klass
|
12
|
+
klass.extend(ClassMethods)
|
13
|
+
end
|
14
|
+
# Used to add class methods to the LibraryUsageUpdate class on inclusion of the decorator
|
15
|
+
module ClassMethods
|
16
|
+
def build digest, files
|
17
|
+
msg = new
|
18
|
+
msg.hash_code = Contrast::Utils::StringUtils.force_utf8(digest)
|
19
|
+
files.each do |required_file|
|
20
|
+
msg.class_names[required_file] = true
|
21
|
+
end
|
22
|
+
msg
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Contrast::Api::Dtm::LibraryUsageUpdate.include(Contrast::Api::Decorators::LibraryUsageUpdate)
|
@@ -17,7 +17,8 @@ module Contrast
|
|
17
17
|
access_component :analysis, :config, :settings
|
18
18
|
|
19
19
|
def enabled?
|
20
|
-
|
20
|
+
@_enabled = !false?(CONFIG.root.enable) if @_enabled.nil?
|
21
|
+
@_enabled
|
21
22
|
end
|
22
23
|
|
23
24
|
def disabled?
|
@@ -25,11 +26,11 @@ module Contrast
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def enable!
|
28
|
-
@
|
29
|
+
@_enabled = true
|
29
30
|
end
|
30
31
|
|
31
32
|
def disable!
|
32
|
-
@
|
33
|
+
@_enabled = false
|
33
34
|
end
|
34
35
|
|
35
36
|
def ruleset
|
@@ -49,12 +50,12 @@ module Contrast
|
|
49
50
|
end
|
50
51
|
|
51
52
|
def patch_yield?
|
52
|
-
@_patch_yield = !
|
53
|
+
@_patch_yield = !false?(CONFIG.root.agent.ruby.propagate_yield) if @_patch_yield.nil?
|
53
54
|
@_patch_yield
|
54
55
|
end
|
55
56
|
|
56
57
|
def interpolation_enabled?
|
57
|
-
@_interpolation_enabled = !
|
58
|
+
@_interpolation_enabled = !false?(CONFIG.root.agent.ruby.interpolate) if @_interpolation_enabled.nil?
|
58
59
|
@_interpolation_enabled
|
59
60
|
end
|
60
61
|
|
@@ -1,9 +1,7 @@
|
|
1
1
|
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require 'contrast/utils/boolean_util'
|
5
4
|
require 'contrast/utils/env_configuration_item'
|
6
|
-
require 'contrast/utils/object_share'
|
7
5
|
require 'contrast/configuration'
|
8
6
|
|
9
7
|
module Contrast
|
@@ -39,41 +37,9 @@ module Contrast
|
|
39
37
|
end
|
40
38
|
alias_method :rebuild, :build
|
41
39
|
|
42
|
-
#
|
43
|
-
# grep 'CONFIG.raw' for opportunities to refactor.
|
44
|
-
def raw
|
45
|
-
@config
|
46
|
-
end
|
47
|
-
|
40
|
+
# @return [Contrast::Config::RootConfiguration]
|
48
41
|
def root
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
def enabled?
|
53
|
-
@_enabled = !Contrast::Utils::BooleanUtil.false?(raw.enable) if @_enabled.nil?
|
54
|
-
@_enabled
|
55
|
-
end
|
56
|
-
|
57
|
-
def disabled?
|
58
|
-
!enabled?
|
59
|
-
end
|
60
|
-
|
61
|
-
def protect?
|
62
|
-
@_protect = Contrast::Utils::BooleanUtil.true?(raw.protect.enable) if @_protect.nil?
|
63
|
-
@_protect
|
64
|
-
end
|
65
|
-
|
66
|
-
def assess?
|
67
|
-
@_assess = Contrast::Utils::BooleanUtil.true?(raw.assess.enable) if @_assess.nil?
|
68
|
-
@_assess
|
69
|
-
end
|
70
|
-
|
71
|
-
def session_id
|
72
|
-
@_session_id ||= raw.application.session_id
|
73
|
-
end
|
74
|
-
|
75
|
-
def session_metadata
|
76
|
-
@_session_metadata ||= raw.application.session_metadata
|
42
|
+
@config.root
|
77
43
|
end
|
78
44
|
|
79
45
|
def valid?
|
@@ -84,6 +50,10 @@ module Contrast
|
|
84
50
|
!valid?
|
85
51
|
end
|
86
52
|
|
53
|
+
def loggable
|
54
|
+
@config.loggable
|
55
|
+
end
|
56
|
+
|
87
57
|
private
|
88
58
|
|
89
59
|
SESSION_VARIABLES = "Invalid configuration. Setting both application.session_id and application.session_metadata is not allowed.\n"
|
@@ -111,9 +81,31 @@ module Contrast
|
|
111
81
|
next unless env_key.to_s.start_with?(CONTRAST_ENV_MARKER)
|
112
82
|
|
113
83
|
config_item = Contrast::Utils::EnvConfigurationItem.new(env_key, env_value)
|
114
|
-
|
84
|
+
@config.assign_value_to_path_array(config_item.dot_path_array, config_item.value)
|
115
85
|
end
|
116
86
|
end
|
87
|
+
|
88
|
+
# Typically, this would be accessed through
|
89
|
+
# Contrast::Components::AppContext, but we're too early in the
|
90
|
+
# initialization of the Agent to use that mechanism, so we look it up
|
91
|
+
# directly for ourselves
|
92
|
+
#
|
93
|
+
# @return [String,nil] the value of the session id set in the
|
94
|
+
# configuration, or nil if unset
|
95
|
+
def session_id
|
96
|
+
@config.application.session_id
|
97
|
+
end
|
98
|
+
|
99
|
+
# Typically, this would be accessed through
|
100
|
+
# Contrast::Components::AppContext, but we're too early in the
|
101
|
+
# initialization of the Agent to use that mechanism, so we look it up
|
102
|
+
# directly for ourselves
|
103
|
+
#
|
104
|
+
# @return [String,nil] the value of the session metadata set in the
|
105
|
+
# configuration, or nil if unset
|
106
|
+
def session_metadata
|
107
|
+
@config.application.session_metadata
|
108
|
+
end
|
117
109
|
end
|
118
110
|
|
119
111
|
COMPONENT_INTERFACE = Interface.new
|
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
require 'delegate'
|
5
5
|
require 'contrast/extension/module'
|
6
|
-
require 'contrast/utils/
|
6
|
+
require 'contrast/utils/object_share'
|
7
7
|
|
8
8
|
module Contrast
|
9
9
|
# This is the base module for our components classes. It is intended to
|
@@ -49,12 +49,34 @@ module Contrast
|
|
49
49
|
end
|
50
50
|
|
51
51
|
module Methods # :nodoc:
|
52
|
+
# use this to determine if the configuration value is literally boolean
|
53
|
+
# false or some form of the word `false`, regardless of case. It should
|
54
|
+
# be used for those values which default to `true` as they should only
|
55
|
+
# treat a value explicitly set to `false` as such.
|
56
|
+
#
|
57
|
+
# @param config_param [Boolean,String] the value to check
|
58
|
+
# @return [Boolean] should the value be treated as `false`
|
52
59
|
def false? config_param
|
53
|
-
|
60
|
+
return false if config_param == true
|
61
|
+
return true if config_param == false
|
62
|
+
return false unless config_param.cs__is_a?(String)
|
63
|
+
|
64
|
+
Contrast::Utils::ObjectShare::FALSE.casecmp?(config_param)
|
54
65
|
end
|
55
66
|
|
67
|
+
# use this to determine if the configuration value is literally boolean
|
68
|
+
# true or some form of the word `true`, regardless of case. It should
|
69
|
+
# be used for those values which default to `false` as they should only
|
70
|
+
# treat a value explicitly set to `true` as such.
|
71
|
+
#
|
72
|
+
# @param config_param [Boolean,String] the value to check
|
73
|
+
# @return [Boolean] should the value be treated as `true`
|
56
74
|
def true? config_param
|
57
|
-
|
75
|
+
return false if config_param == false
|
76
|
+
return true if config_param == true
|
77
|
+
return false unless config_param.cs__is_a?(String)
|
78
|
+
|
79
|
+
Contrast::Utils::ObjectShare::TRUE.casecmp?(config_param)
|
58
80
|
end
|
59
81
|
end
|
60
82
|
end
|
@@ -13,12 +13,17 @@ module Contrast
|
|
13
13
|
include Contrast::Components::ComponentBase
|
14
14
|
include Contrast::Components::Interface
|
15
15
|
|
16
|
-
access_component :config
|
16
|
+
access_component :config, :settings
|
17
17
|
|
18
18
|
def enabled?
|
19
19
|
@_enabled = !false?(CONFIG.root.inventory.enable) if @_enabled.nil?
|
20
20
|
@_enabled
|
21
21
|
end
|
22
|
+
|
23
|
+
def analyze_libraries?
|
24
|
+
@_analyze_libraries = !false?(CONFIG.root.inventory.analyze_libraries) if @_analyze_libraries.nil?
|
25
|
+
@_analyze_libraries
|
26
|
+
end
|
22
27
|
end
|
23
28
|
|
24
29
|
COMPONENT_INTERFACE = Interface.new
|
@@ -7,8 +7,8 @@ module Contrast
|
|
7
7
|
# inventory functionality of the Agent.
|
8
8
|
class InventoryConfiguration < BaseConfiguration
|
9
9
|
KEYS = {
|
10
|
-
enable:
|
11
|
-
|
10
|
+
enable: Contrast::Config::DefaultValue.new(true),
|
11
|
+
analyze_libraries: Contrast::Config::DefaultValue.new(true),
|
12
12
|
tags: EMPTY_VALUE
|
13
13
|
}.cs__freeze
|
14
14
|
|
@@ -45,6 +45,9 @@ module Contrast
|
|
45
45
|
find_all_routes(::Rails.application, [])
|
46
46
|
end
|
47
47
|
|
48
|
+
# Find the current route, based on the provided Request wrapper
|
49
|
+
# @param request[Contrast::Agent::Request]
|
50
|
+
# @return [Contrast::Api::Dtm::RouteCoverage]
|
48
51
|
def current_route request
|
49
52
|
return unless ::Rails.cs__respond_to?(:application)
|
50
53
|
|
@@ -33,7 +33,7 @@ module Contrast
|
|
33
33
|
def application_configuration
|
34
34
|
return unless info?
|
35
35
|
|
36
|
-
loggable = CONFIG.
|
36
|
+
loggable = CONFIG.loggable
|
37
37
|
info('Current configuration', configuration: loggable)
|
38
38
|
env_keys = ENV.keys.select { |env_key| env_key&.to_s&.start_with?(Contrast::Components::Config::CONTRAST_ENV_MARKER) }
|
39
39
|
env_items = env_keys.map { |env_key| Contrast::Utils::EnvConfigurationItem.new(env_key, nil) }
|
@@ -3,7 +3,6 @@
|
|
3
3
|
|
4
4
|
require 'contrast/utils/timer'
|
5
5
|
require 'contrast/utils/object_share'
|
6
|
-
require 'contrast/utils/gemfile_reader'
|
7
6
|
require 'contrast/components/interface'
|
8
7
|
|
9
8
|
module Contrast
|
@@ -25,12 +24,6 @@ module Contrast
|
|
25
24
|
DEFAULT = 'default'
|
26
25
|
LOCALHOST = 'localhost'
|
27
26
|
|
28
|
-
def self.inventory_class class_path
|
29
|
-
Contrast::Utils::GemfileReader.instance.map_class(class_path)
|
30
|
-
rescue StandardError => e
|
31
|
-
logger.error('Unable to inventory module', e, path: class_path)
|
32
|
-
end
|
33
|
-
|
34
27
|
def self.active_record_config
|
35
28
|
return @_active_record_config if instance_variable_defined?(:@_active_record_config)
|
36
29
|
|
@@ -52,18 +52,6 @@ module Contrast
|
|
52
52
|
parent_dir = File.dirname(gems_dir)
|
53
53
|
File.join(parent_dir, Contrast::Utils::ObjectShare::CACHE)
|
54
54
|
end
|
55
|
-
|
56
|
-
def self.files path
|
57
|
-
instance.files(path)
|
58
|
-
end
|
59
|
-
|
60
|
-
def self.sha256 path
|
61
|
-
instance.sha256(path)
|
62
|
-
end
|
63
|
-
|
64
|
-
def self.build_from_spec spec
|
65
|
-
instance.build_from_spec(spec)
|
66
|
-
end
|
67
55
|
end
|
68
56
|
end
|
69
57
|
end
|
data/service_executables/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.14.
|
1
|
+
2.14.4
|
Binary file
|
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: contrast-agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- galen.palmer@contrastsecurity.com
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: exe
|
14
14
|
cert_chain: []
|
15
|
-
date: 2020-
|
15
|
+
date: 2020-11-05 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: amazing_print
|
@@ -770,6 +770,11 @@ files:
|
|
770
770
|
- lib/contrast/agent/deadzone/policy/policy.rb
|
771
771
|
- lib/contrast/agent/disable_reaction.rb
|
772
772
|
- lib/contrast/agent/exclusion_matcher.rb
|
773
|
+
- lib/contrast/agent/inventory.rb
|
774
|
+
- lib/contrast/agent/inventory/dependencies.rb
|
775
|
+
- lib/contrast/agent/inventory/dependency_analysis.rb
|
776
|
+
- lib/contrast/agent/inventory/dependency_usage_analysis.rb
|
777
|
+
- lib/contrast/agent/inventory/gemfile_digest_cache.rb
|
773
778
|
- lib/contrast/agent/inventory/policy/datastores.rb
|
774
779
|
- lib/contrast/agent/inventory/policy/policy.rb
|
775
780
|
- lib/contrast/agent/inventory/policy/trigger_node.rb
|
@@ -847,6 +852,8 @@ files:
|
|
847
852
|
- lib/contrast/api/decorators/application_update.rb
|
848
853
|
- lib/contrast/api/decorators/http_request.rb
|
849
854
|
- lib/contrast/api/decorators/input_analysis.rb
|
855
|
+
- lib/contrast/api/decorators/library.rb
|
856
|
+
- lib/contrast/api/decorators/library_usage_update.rb
|
850
857
|
- lib/contrast/api/decorators/message.rb
|
851
858
|
- lib/contrast/api/decorators/rasp_rule_sample.rb
|
852
859
|
- lib/contrast/api/decorators/route_coverage.rb
|
@@ -941,11 +948,9 @@ files:
|
|
941
948
|
- lib/contrast/tasks/service.rb
|
942
949
|
- lib/contrast/utils/assess/sampling_util.rb
|
943
950
|
- lib/contrast/utils/assess/tracking_util.rb
|
944
|
-
- lib/contrast/utils/boolean_util.rb
|
945
951
|
- lib/contrast/utils/class_util.rb
|
946
952
|
- lib/contrast/utils/duck_utils.rb
|
947
953
|
- lib/contrast/utils/env_configuration_item.rb
|
948
|
-
- lib/contrast/utils/gemfile_reader.rb
|
949
954
|
- lib/contrast/utils/hash_digest.rb
|
950
955
|
- lib/contrast/utils/heap_dump_util.rb
|
951
956
|
- lib/contrast/utils/invalid_configuration_util.rb
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'contrast/utils/object_share'
|
5
|
-
|
6
|
-
module Contrast
|
7
|
-
module Utils
|
8
|
-
# Utility methods for asserting truthy or falsy state of a value expected
|
9
|
-
# to equate to a boolean
|
10
|
-
class BooleanUtil
|
11
|
-
class << self
|
12
|
-
def false? config
|
13
|
-
return false if config == true
|
14
|
-
return true if config == false
|
15
|
-
return false unless config.cs__is_a?(String)
|
16
|
-
|
17
|
-
Contrast::Utils::ObjectShare::FALSE.casecmp?(config)
|
18
|
-
end
|
19
|
-
|
20
|
-
def true? config
|
21
|
-
return false if config == false
|
22
|
-
return true if config == true
|
23
|
-
return false unless config.cs__is_a?(String)
|
24
|
-
|
25
|
-
Contrast::Utils::ObjectShare::TRUE.casecmp?(config)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,193 +0,0 @@
|
|
1
|
-
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'set'
|
5
|
-
require 'contrast/utils/sha256_builder'
|
6
|
-
require 'contrast/utils/string_utils'
|
7
|
-
require 'contrast/components/interface'
|
8
|
-
require 'contrast/api'
|
9
|
-
|
10
|
-
module Contrast
|
11
|
-
module Utils
|
12
|
-
# GemfileReader has methods for extracting information from gem specs
|
13
|
-
# it also has a cache of library file digests to the lines of code found.
|
14
|
-
class GemfileReader
|
15
|
-
include Singleton
|
16
|
-
include Contrast::Components::Interface
|
17
|
-
|
18
|
-
access_component :config, :logging
|
19
|
-
|
20
|
-
CONTRAST_AGENT = 'contrast-agent'
|
21
|
-
|
22
|
-
def initialize
|
23
|
-
# Map of a Gem's Spec Digest to all loaded files from that Gem
|
24
|
-
@spec_to_files = {}
|
25
|
-
end
|
26
|
-
|
27
|
-
# the #clone is necessary here, as a require in another thread could
|
28
|
-
# potentially result in adding a key to the loaded_specs hash during
|
29
|
-
# iteration. (as in RUBY-330)
|
30
|
-
def loaded_specs
|
31
|
-
Gem.loaded_specs.clone
|
32
|
-
end
|
33
|
-
|
34
|
-
# indicates if there's been an update to library information, allowing us
|
35
|
-
# to only serialize this information on change.
|
36
|
-
def updated?
|
37
|
-
@updated
|
38
|
-
end
|
39
|
-
|
40
|
-
def updated!
|
41
|
-
@updated = true
|
42
|
-
end
|
43
|
-
|
44
|
-
# Once we're Contrasted, we intercept require calls to do inventory.
|
45
|
-
# In order to catch up, we do a one-time manual catchup, & inventory
|
46
|
-
# all the already-loaded gems.
|
47
|
-
def map_loaded_classes
|
48
|
-
loaded_specs.each do |name, spec|
|
49
|
-
# Don't count Contrast gems
|
50
|
-
next if contrast_gems.include? name
|
51
|
-
|
52
|
-
# Get a digest of the Gem file itself
|
53
|
-
next unless (digest = Contrast::Utils::Sha256Builder.build_from_spec(spec))
|
54
|
-
|
55
|
-
paths = get_by_digest(digest)
|
56
|
-
path = spec.full_gem_path
|
57
|
-
$LOADED_FEATURES.each do |line|
|
58
|
-
next unless line.cs__is_a?(String)
|
59
|
-
next unless line.start_with?(path)
|
60
|
-
|
61
|
-
logger.trace('Recording loaded gem for inventory analysis', line: line)
|
62
|
-
updated!
|
63
|
-
paths << adjust_lib(line)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def map_class path
|
69
|
-
path = adjust_lib(path)
|
70
|
-
|
71
|
-
return unless (spec = Gem::Specification.find_by_path(path))
|
72
|
-
return unless (digest = Contrast::Utils::Sha256Builder.build_from_spec(spec))
|
73
|
-
|
74
|
-
updated!
|
75
|
-
get_by_digest(digest) << path
|
76
|
-
end
|
77
|
-
|
78
|
-
def library_pb_list
|
79
|
-
loaded_specs.each_with_object([]) do |(name, spec), arr|
|
80
|
-
next if contrast_gems.include? name
|
81
|
-
next unless spec
|
82
|
-
next unless (digest = Contrast::Utils::Sha256Builder.build_from_spec(spec))
|
83
|
-
|
84
|
-
arr << build_library_pb(digest, spec)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def generate_library_usage activity = nil
|
89
|
-
return unless updated?
|
90
|
-
|
91
|
-
@spec_to_files.each_pair do |digest, files|
|
92
|
-
usage = Contrast::Api::Dtm::LibraryUsageUpdate.new
|
93
|
-
usage.hash_code = Contrast::Utils::StringUtils.force_utf8(digest)
|
94
|
-
activity.library_usages[usage.hash_code] = usage if activity
|
95
|
-
# TODO: RUBY-882 once TS switches to take filenames, remove the count setter and
|
96
|
-
# send the class names in usage.class_names
|
97
|
-
usage.count = files.size
|
98
|
-
end
|
99
|
-
# TODO: RUBY-882 once TS switches to take filenames, clear this and remove the
|
100
|
-
# @updated variable
|
101
|
-
|
102
|
-
# @spec_to_files.clear
|
103
|
-
@updated = false
|
104
|
-
end
|
105
|
-
|
106
|
-
private
|
107
|
-
|
108
|
-
# marker for the lib dir in an absolute file path. purposefully includes
|
109
|
-
# the trailing '/'
|
110
|
-
LIB = '/lib/'
|
111
|
-
|
112
|
-
# Kernel#load uses the absolute path, but Gems / Specs use the path after
|
113
|
-
# `/lib`. This method accounts for that and trims out the `/lib/` section
|
114
|
-
# and starts with the first `/` after and the trailing file extension, if
|
115
|
-
# present.
|
116
|
-
#
|
117
|
-
# @param path [String] the path to parse
|
118
|
-
# @return [String] the relative path of the file, after the lib directory
|
119
|
-
def adjust_lib path
|
120
|
-
idx = path.index(LIB)
|
121
|
-
path = path[(idx + 4)..-1] if idx
|
122
|
-
idx = path.rindex(Contrast::Utils::ObjectShare::PERIOD)
|
123
|
-
path = path[0..idx] if idx
|
124
|
-
path
|
125
|
-
end
|
126
|
-
|
127
|
-
def get_by_digest digest
|
128
|
-
@spec_to_files[digest] = Set.new unless @spec_to_files.key?(digest)
|
129
|
-
@spec_to_files[digest]
|
130
|
-
end
|
131
|
-
|
132
|
-
def build_library_pb digest, spec
|
133
|
-
lib = Contrast::Api::Dtm::Library.new
|
134
|
-
lib.file_path = Contrast::Utils::StringUtils.force_utf8(spec.name)
|
135
|
-
lib.hash_code = Contrast::Utils::StringUtils.force_utf8(digest)
|
136
|
-
lib.version = Contrast::Utils::StringUtils.force_utf8(spec.version)
|
137
|
-
lib.manifest = Contrast::Utils::StringUtils.force_utf8(build_manifest(spec))
|
138
|
-
lib.external_ms = date_to_ms(spec.date)
|
139
|
-
lib.internal_ms = lib.external_ms
|
140
|
-
lib.url = Contrast::Utils::StringUtils.force_utf8(spec.homepage)
|
141
|
-
# Library tags are appended in the ApplicationUpdate delegator
|
142
|
-
update_class_counts(lib, digest, spec)
|
143
|
-
lib
|
144
|
-
end
|
145
|
-
|
146
|
-
def date_to_ms date
|
147
|
-
(date.to_f * 1000.0).to_i
|
148
|
-
end
|
149
|
-
|
150
|
-
def update_class_counts lib, digest, spec
|
151
|
-
# Updating the class counts
|
152
|
-
path = spec.full_gem_path.to_s
|
153
|
-
lib.class_count = all_files(path).length
|
154
|
-
lib.used_class_count = @spec_to_files.key?(digest) ? get_by_digest(digest).size : 0
|
155
|
-
lib
|
156
|
-
end
|
157
|
-
|
158
|
-
def build_manifest spec
|
159
|
-
Contrast::Utils::StringUtils.force_utf8(spec.to_yaml.to_s) if defined?(YAML)
|
160
|
-
rescue StandardError
|
161
|
-
nil
|
162
|
-
end
|
163
|
-
|
164
|
-
# These are all the code files that are located in the Gem directory loaded
|
165
|
-
# by the current environment; this includes more than Ruby files
|
166
|
-
def all_files path
|
167
|
-
Contrast::Utils::Sha256Builder.instance.files(path)
|
168
|
-
end
|
169
|
-
|
170
|
-
# Go through all dependents, given as a pair from the DependencyList: `dependency`
|
171
|
-
# is the dependency itself, filled with all its specs. `dependents` is the array of reverse
|
172
|
-
# dependencies for the aforementioned dependency. If the dependency is also in contrast_dep_set,
|
173
|
-
# then contrast depends on it. If its array of dependents is 1, then contrast is the
|
174
|
-
# only dependency in that list. Since only contrast depends on it, we should ignore it.
|
175
|
-
def contrast_gems
|
176
|
-
@_contrast_gems ||= find_contrast_gems
|
177
|
-
end
|
178
|
-
|
179
|
-
def find_contrast_gems
|
180
|
-
ignore = Set.new([CONTRAST_AGENT])
|
181
|
-
contrast_specs = Gem::DependencyList.from_specs.specs.find do |dependency|
|
182
|
-
dependency.name == CONTRAST_AGENT
|
183
|
-
end
|
184
|
-
contrast_dep_set = contrast_specs.dependencies.map(&:name).to_set
|
185
|
-
|
186
|
-
Gem::DependencyList.from_specs.spec_predecessors.each_pair do |dependency, dependents|
|
187
|
-
ignore.add(dependency.name) if contrast_dep_set.include?(dependency.name) && dependents.length == 1
|
188
|
-
end
|
189
|
-
ignore
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|