stratagem 0.1.7
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.
- data/Manifest +99 -0
- data/Rakefile +17 -0
- data/bin/stratagem +10 -0
- data/init.rb +2 -0
- data/lib/bootstrap.rb +31 -0
- data/lib/stratagem/authentication.rb +64 -0
- data/lib/stratagem/auto_mock/aquifer.rb +86 -0
- data/lib/stratagem/auto_mock/factory.rb +213 -0
- data/lib/stratagem/auto_mock/value_generator.rb +174 -0
- data/lib/stratagem/auto_mock.rb +6 -0
- data/lib/stratagem/blocker.rb +16 -0
- data/lib/stratagem/client.rb +32 -0
- data/lib/stratagem/command.rb +13 -0
- data/lib/stratagem/commands/analyze.rb +22 -0
- data/lib/stratagem/commands/base.rb +11 -0
- data/lib/stratagem/commands/devel_crawl.rb +27 -0
- data/lib/stratagem/commands/devel_mock.rb +10 -0
- data/lib/stratagem/commands.rb +7 -0
- data/lib/stratagem/crawler/authentication.rb +109 -0
- data/lib/stratagem/crawler/form.rb +101 -0
- data/lib/stratagem/crawler/html_utils.rb +92 -0
- data/lib/stratagem/crawler/session.rb +296 -0
- data/lib/stratagem/crawler/site_model.rb +138 -0
- data/lib/stratagem/crawler/trace_utils.rb +10 -0
- data/lib/stratagem/crawler.rb +9 -0
- data/lib/stratagem/extensions/class.rb +9 -0
- data/lib/stratagem/extensions/hash.rb +16 -0
- data/lib/stratagem/extensions/module.rb +11 -0
- data/lib/stratagem/extensions/object.rb +15 -0
- data/lib/stratagem/extensions/red_parse.rb +86 -0
- data/lib/stratagem/extensions/string.rb +20 -0
- data/lib/stratagem/extensions.rb +6 -0
- data/lib/stratagem/framework_extensions/controllers/action_controller.rb +10 -0
- data/lib/stratagem/framework_extensions/controllers/action_mailer.rb +12 -0
- data/lib/stratagem/framework_extensions/controllers.rb +5 -0
- data/lib/stratagem/framework_extensions/models/adapters/active_model/detect.rb +7 -0
- data/lib/stratagem/framework_extensions/models/adapters/active_model/extensions.rb +35 -0
- data/lib/stratagem/framework_extensions/models/adapters/active_model/metadata.rb +103 -0
- data/lib/stratagem/framework_extensions/models/adapters/active_model/tracing.rb +50 -0
- data/lib/stratagem/framework_extensions/models/adapters/authlogic/detect.rb +11 -0
- data/lib/stratagem/framework_extensions/models/adapters/authlogic/extensions.rb +10 -0
- data/lib/stratagem/framework_extensions/models/adapters/authlogic/metadata.rb +30 -0
- data/lib/stratagem/framework_extensions/models/adapters/authlogic/tracing.rb +4 -0
- data/lib/stratagem/framework_extensions/models/adapters/common/authentication_metadata.rb +21 -0
- data/lib/stratagem/framework_extensions/models/adapters/restful_authentication/detect.rb +13 -0
- data/lib/stratagem/framework_extensions/models/adapters/restful_authentication/extensions.rb +19 -0
- data/lib/stratagem/framework_extensions/models/adapters/restful_authentication/metadata.rb +30 -0
- data/lib/stratagem/framework_extensions/models/adapters/restful_authentication/tracing.rb +4 -0
- data/lib/stratagem/framework_extensions/models/annotations.rb +79 -0
- data/lib/stratagem/framework_extensions/models/detect.rb +7 -0
- data/lib/stratagem/framework_extensions/models/metadata.rb +85 -0
- data/lib/stratagem/framework_extensions/models/mocking.rb +23 -0
- data/lib/stratagem/framework_extensions/models/tracing.rb +71 -0
- data/lib/stratagem/framework_extensions/models.rb +21 -0
- data/lib/stratagem/framework_extensions/rails.rb +8 -0
- data/lib/stratagem/framework_extensions.rb +6 -0
- data/lib/stratagem/interface/browser.rb +37 -0
- data/lib/stratagem/interface/public/images/backgrounds/content.png +0 -0
- data/lib/stratagem/interface/public/images/backgrounds/shadow.png +0 -0
- data/lib/stratagem/interface/public/javascripts/jquery-1.4.2.min.js +154 -0
- data/lib/stratagem/interface/public/javascripts/stratagem.js +27 -0
- data/lib/stratagem/interface/public/javascripts/stratagem_debug.js +53 -0
- data/lib/stratagem/interface/public/stylesheets/960.css +1 -0
- data/lib/stratagem/interface/public/stylesheets/reset.css +10 -0
- data/lib/stratagem/interface/public/stylesheets/stratagem.css +20 -0
- data/lib/stratagem/interface/public/stylesheets/stratagem_debug.css +20 -0
- data/lib/stratagem/interface/views/debug.haml +43 -0
- data/lib/stratagem/interface/views/index.haml +35 -0
- data/lib/stratagem/labs/auto_mock.rb +7 -0
- data/lib/stratagem/labs/crawler.rb +0 -0
- data/lib/stratagem/logger.rb +46 -0
- data/lib/stratagem/model/application.rb +157 -0
- data/lib/stratagem/model/components/base.rb +55 -0
- data/lib/stratagem/model/components/controller.rb +118 -0
- data/lib/stratagem/model/components/model.rb +170 -0
- data/lib/stratagem/model/components/reference.rb +30 -0
- data/lib/stratagem/model/components/route.rb +53 -0
- data/lib/stratagem/model/components/static_file.rb +18 -0
- data/lib/stratagem/model/components/view.rb +186 -0
- data/lib/stratagem/model/parse_util.rb +61 -0
- data/lib/stratagem/model.rb +12 -0
- data/lib/stratagem/model_builder.rb +146 -0
- data/lib/stratagem/recipes/deploy.rb +30 -0
- data/lib/stratagem/scan/checks/capistrano/secure_deploy.rb +43 -0
- data/lib/stratagem/scan/checks/email_address.rb +15 -0
- data/lib/stratagem/scan/checks/error_pages.rb +25 -0
- data/lib/stratagem/scan/checks/filter_parameter_logging.rb +6 -0
- data/lib/stratagem/scan/checks/mongo_mapper/base.rb +19 -0
- data/lib/stratagem/scan/checks/mongo_mapper/foreign_keys_exposed.rb +32 -0
- data/lib/stratagem/scan/checks/routes.rb +16 -0
- data/lib/stratagem/scan/checks/ssl/secure_login_page.rb +19 -0
- data/lib/stratagem/scan/checks/ssl/secure_login_submit.rb +18 -0
- data/lib/stratagem/scan/result.rb +45 -0
- data/lib/stratagem/scan.rb +19 -0
- data/lib/stratagem/scanner.rb +32 -0
- data/lib/stratagem/site_crawler.rb +47 -0
- data/lib/stratagem/snapshot.rb +33 -0
- data/lib/stratagem.rb +77 -0
- data/lib/tasks/_old_stratagem.rake +99 -0
- data/stratagem.gemspec +56 -0
- metadata +380 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
require 'nokogiri'
|
|
2
|
+
|
|
3
|
+
require 'action_pack'
|
|
4
|
+
require 'action_controller'
|
|
5
|
+
|
|
6
|
+
class CrawlError < StratagemError; end
|
|
7
|
+
|
|
8
|
+
# require 'rack/lint'
|
|
9
|
+
# module Rack
|
|
10
|
+
# # Rack::Lint validates your application and the requests and
|
|
11
|
+
# # responses according to the Rack spec.
|
|
12
|
+
#
|
|
13
|
+
# class Lint
|
|
14
|
+
# alias_method :old_call, :call
|
|
15
|
+
#
|
|
16
|
+
# def call(env)
|
|
17
|
+
# data = env['rack.input']
|
|
18
|
+
# data.set_encoding(Encoding::ASCII_8BIT) if data.respond_to?(:set_encoding)
|
|
19
|
+
#
|
|
20
|
+
# status, headers, body = old_call(env)
|
|
21
|
+
# body.close # fix for Rack
|
|
22
|
+
# [status,headers,body]
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
|
|
27
|
+
PHASES = [:unauthenticated,:authenticated]
|
|
28
|
+
|
|
29
|
+
module Stratagem::Crawler::Session
|
|
30
|
+
include ActionController::Integration::Runner
|
|
31
|
+
include Stratagem::Crawler::HtmlUtils
|
|
32
|
+
include Stratagem::Crawler::TraceUtils
|
|
33
|
+
include Stratagem::Crawler::Authentication
|
|
34
|
+
|
|
35
|
+
def log(msg)
|
|
36
|
+
Stratagem.logger.debug msg
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def crawler_session(application_model=nil)
|
|
40
|
+
@model = application_model
|
|
41
|
+
@model ||= Stratagem::ModelBuilder.new.run
|
|
42
|
+
@redirect_proc = Proc.new {|redirect_url| handle_redirect(redirect_url) }
|
|
43
|
+
@parameter_types = {} # routecontainer -> {:route_segment => Model, :route_segment => Model}
|
|
44
|
+
open_session do |session|
|
|
45
|
+
@session = session
|
|
46
|
+
phase(:unauthenticated)
|
|
47
|
+
yield
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def application_model
|
|
52
|
+
@model
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def site_models
|
|
56
|
+
@site_models ||= {} # builds models of the site for various phases of analysis, see PHASES
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def site_model
|
|
60
|
+
raise Stratagem::Crawler::CrawlError.new("Phase not specified") unless @current_model
|
|
61
|
+
@current_model
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def phase(new_phase)
|
|
65
|
+
@current_phase = new_phase
|
|
66
|
+
@current_model = site_models[@current_phase] ||= Stratagem::Crawler::SiteModel.new
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def phases
|
|
70
|
+
site_models
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def display
|
|
74
|
+
# print out pages and inbound / outbound links
|
|
75
|
+
site_model.pages.each do |page|
|
|
76
|
+
log "Page: #{page.url} - #{page.title} - #{page.response.code}"
|
|
77
|
+
page.outbound_edges.each do |edge|
|
|
78
|
+
log "\tout: #{edge.to.url} - #{edge.to.title} - #{edge.to.route}"
|
|
79
|
+
end
|
|
80
|
+
page.inbound_edges.each do |edge|
|
|
81
|
+
log "\tin: #{edge.from.url} - #{edge.from.title}"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def crawl
|
|
87
|
+
# grab all pages independently
|
|
88
|
+
@model.routes.invalid.each {|route_container|
|
|
89
|
+
puts "skipping invalid route #{route_container.route.to_s}"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
authentication_controller = nil
|
|
93
|
+
if (@current_phase == :authenticated)
|
|
94
|
+
route = @model.routes.recognize(authentication.login_page)
|
|
95
|
+
authentication_controller = route.controller
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
@model.routes.each {|route_container|
|
|
99
|
+
if authentication_controller && route_container.controller && (route_container.controller.klass == authentication_controller.klass)
|
|
100
|
+
log "Skipping authentication routes #{route_container.route.to_s}"
|
|
101
|
+
else
|
|
102
|
+
visit(route_container) unless @model.routes.invalid.include?(route_container)
|
|
103
|
+
end
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# establish edges on site graph
|
|
107
|
+
site_model.pages.each do |page|
|
|
108
|
+
site_model.add_edge(page, page.redirected_to, :redirect) if page.redirected?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
site_model
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def visit(route_container)
|
|
117
|
+
puts "visiting #{route_container.route}"
|
|
118
|
+
build_urls(route_container).each do |route_info|
|
|
119
|
+
call_route(route_container, route_info)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def call_route(route, route_info)
|
|
124
|
+
puts 'CALLING: .'+route_info[:verb].downcase+". - "+route_info[:path]
|
|
125
|
+
verb = route_info[:verb].downcase
|
|
126
|
+
verb = 'get' if verb == '' || verb == 'any'
|
|
127
|
+
case verb
|
|
128
|
+
when 'get'
|
|
129
|
+
do_get(route, route_info[:path])
|
|
130
|
+
puts response.code if response
|
|
131
|
+
when 'post'
|
|
132
|
+
when 'put'
|
|
133
|
+
when 'delete'
|
|
134
|
+
else
|
|
135
|
+
raise "Unsupported verb: #{route[:verb]}"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Builds a list of string URLs for a given route. This is done
|
|
140
|
+
# by replacing :xyz_id segments in the route with known values
|
|
141
|
+
# from the well
|
|
142
|
+
def build_urls(route_container)
|
|
143
|
+
urls = []
|
|
144
|
+
route = route_container.route
|
|
145
|
+
parameter_types = (@parameter_types[route_container] ||= resolve_parameter_types(route_container))
|
|
146
|
+
route_infos, params = build_url(route_container, parameter_types)
|
|
147
|
+
puts "route: #{route_container.route.to_s} permutations:"
|
|
148
|
+
route_infos.each do |info|
|
|
149
|
+
puts "\t#{info[:path]}"
|
|
150
|
+
end
|
|
151
|
+
route_infos
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def url_permutations(meta_segments, segment_stack=[], &block)
|
|
155
|
+
if (segment_stack.size == meta_segments.size)
|
|
156
|
+
yield segment_stack
|
|
157
|
+
else
|
|
158
|
+
cursor = segment_stack.size
|
|
159
|
+
options = meta_segments[cursor]
|
|
160
|
+
if (options.kind_of?(Array))
|
|
161
|
+
options.each do |option|
|
|
162
|
+
url_permutations(meta_segments, segment_stack + [option], &block)
|
|
163
|
+
end
|
|
164
|
+
else
|
|
165
|
+
url_permutations(meta_segments, segment_stack + [options], &block)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def build_url(route_container, parameter_types={})
|
|
171
|
+
params = {}
|
|
172
|
+
route = route_container.route
|
|
173
|
+
name = route.to_s
|
|
174
|
+
verb = route.conditions[:method].to_s
|
|
175
|
+
|
|
176
|
+
parameter_types ||= {}
|
|
177
|
+
|
|
178
|
+
i = 12345
|
|
179
|
+
segs = route.segments.inject([]) {|accumulated,segment|
|
|
180
|
+
s = segment.to_s
|
|
181
|
+
if (s =~ /^:/)
|
|
182
|
+
model = parameter_types[s]
|
|
183
|
+
value = nil
|
|
184
|
+
if (model)
|
|
185
|
+
value = (Stratagem::AutoMock::Aquifer.instance.instances_of model).map {|inst|
|
|
186
|
+
attr_name = s.gsub(/^:/, '').to_sym
|
|
187
|
+
if inst.methods_include?(attr_name)
|
|
188
|
+
puts "#{attr_name} is a method on the object"
|
|
189
|
+
inst.send(attr_name)
|
|
190
|
+
else
|
|
191
|
+
puts "#{attr_name} is being mapped to the id on the object"
|
|
192
|
+
inst.id
|
|
193
|
+
end
|
|
194
|
+
}
|
|
195
|
+
else
|
|
196
|
+
i += 1
|
|
197
|
+
value = [i]
|
|
198
|
+
end
|
|
199
|
+
accumulated << value
|
|
200
|
+
params[s] = value
|
|
201
|
+
else
|
|
202
|
+
accumulated << s
|
|
203
|
+
end
|
|
204
|
+
accumulated
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
routes = []
|
|
208
|
+
reqs = route.requirements.empty? ? "" : route.requirements.inspect
|
|
209
|
+
url_permutations(segs) do |segments|
|
|
210
|
+
path = segments.join('').gsub('(.:format)', '').gsub(/\/$/, '')
|
|
211
|
+
permutation = {:name => name, :verb => verb, :segs => segs, :reqs => reqs, :path => path}
|
|
212
|
+
routes << permutation
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
[routes, params]
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def handle_redirect(redirect_url)
|
|
219
|
+
existing_pages = site_model.pages_for(response.redirect_url)
|
|
220
|
+
if (existing_pages.size > 0)
|
|
221
|
+
existing_pages.first
|
|
222
|
+
else
|
|
223
|
+
invocation_delta = model_invocations_for_request do
|
|
224
|
+
get redirect_url
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
site_model.add(nil, response) {|redirect_url|
|
|
228
|
+
# TODO - record as bug!
|
|
229
|
+
puts "recursive redirect #{redirect_url}"
|
|
230
|
+
}
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def do_get(route,path)
|
|
235
|
+
begin
|
|
236
|
+
get path
|
|
237
|
+
site_model.add(route, response) {|redirect_url| @redirect_proc.call(redirect_url) }
|
|
238
|
+
rescue
|
|
239
|
+
puts $!.message
|
|
240
|
+
puts path
|
|
241
|
+
#puts $!.backtrace
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def resolve_parameter_types(route_container)
|
|
246
|
+
puts "resolving parameter types for #{route_container.route}"
|
|
247
|
+
resolved_parameters = {}
|
|
248
|
+
route_infos, params = build_url(route_container, resolved_parameters)
|
|
249
|
+
route_info = route_infos.first
|
|
250
|
+
unknown_params = params.keys
|
|
251
|
+
puts "unknown params: #{unknown_params}"
|
|
252
|
+
progress = nil
|
|
253
|
+
while ((unknown_params.size > 0) && (progress.nil? || (progress > 0)))
|
|
254
|
+
progress = 0
|
|
255
|
+
|
|
256
|
+
# p route_infos
|
|
257
|
+
# puts "---"
|
|
258
|
+
delta = model_invocations_for_request do
|
|
259
|
+
call_route(route_container, route_info)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
puts "delta - #{delta.size}"
|
|
263
|
+
#p delta
|
|
264
|
+
|
|
265
|
+
unknown_params.clone.each do |key|
|
|
266
|
+
value = params[key]
|
|
267
|
+
value_s = params[key].map {|v| v.to_s }
|
|
268
|
+
delta.each do |invocation|
|
|
269
|
+
puts "#{route_info[:path]} - #{invocation.model_class.name} - #{invocation.method} - #{invocation.args.inspect} - #{value_s}"
|
|
270
|
+
|
|
271
|
+
# TODO inspect is a hack, refactor
|
|
272
|
+
if (invocation.args.include?(value.first)) || (invocation.args.inspect.include?('"'+value.first.to_s+'"'))
|
|
273
|
+
# found match
|
|
274
|
+
|
|
275
|
+
puts "\tresolved #{key} to #{invocation.model_class}"
|
|
276
|
+
unknown_params.delete(key)
|
|
277
|
+
resolved_parameters[key] = invocation.model_class
|
|
278
|
+
progress += 1
|
|
279
|
+
|
|
280
|
+
break
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
route_infos, params = build_url(route_container, resolved_parameters)
|
|
286
|
+
route_info = route_infos.first
|
|
287
|
+
end
|
|
288
|
+
if (resolved_parameters.size > 0)
|
|
289
|
+
resolved_parameters
|
|
290
|
+
else
|
|
291
|
+
nil
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
module Stratagem::Crawler
|
|
2
|
+
class SiteModel
|
|
3
|
+
include Stratagem::Crawler::HtmlUtils
|
|
4
|
+
|
|
5
|
+
attr_reader :pages, :edges
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@pages = []
|
|
9
|
+
@edges = []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def export
|
|
13
|
+
{
|
|
14
|
+
:pages => @pages.map {|page| page.export },
|
|
15
|
+
:edges => @edges.map {|edge| edge.export }
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def add_edge(from,to,type)
|
|
20
|
+
self.edges << Edge.new(from,to,type)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add(route, response, &block)
|
|
24
|
+
page = Page.new(self, response, &block)
|
|
25
|
+
self.pages << page
|
|
26
|
+
page
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def pages_for(id)
|
|
30
|
+
if (id.kind_of?(String))
|
|
31
|
+
pages.select {|page| page.url == id }
|
|
32
|
+
else
|
|
33
|
+
pages.select {|page| page.route == id }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class Edge
|
|
40
|
+
# type -> :link, :redirect,
|
|
41
|
+
attr_accessor :from, :to, :type
|
|
42
|
+
|
|
43
|
+
def initialize(from, to, type)
|
|
44
|
+
@from = from
|
|
45
|
+
@to = to
|
|
46
|
+
@type = type
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def export
|
|
50
|
+
{
|
|
51
|
+
:from => from.object_id,
|
|
52
|
+
:to => to.object_id,
|
|
53
|
+
:type => type
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class Page
|
|
59
|
+
include Stratagem::Crawler::HtmlUtils
|
|
60
|
+
|
|
61
|
+
attr_reader :response
|
|
62
|
+
|
|
63
|
+
attr_accessor :url
|
|
64
|
+
attr_accessor :path
|
|
65
|
+
attr_accessor :method
|
|
66
|
+
attr_accessor :redirected_to
|
|
67
|
+
attr_accessor :document
|
|
68
|
+
|
|
69
|
+
def initialize(site_model, response, &block)
|
|
70
|
+
@site_model = site_model
|
|
71
|
+
init(response, &block)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def route
|
|
75
|
+
Stratagem::Model::Application.instance.routes.recognize(self)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def export
|
|
79
|
+
{
|
|
80
|
+
:external_id => self.object_id,
|
|
81
|
+
:url => url,
|
|
82
|
+
:path => path,
|
|
83
|
+
:method => method,
|
|
84
|
+
:redirected_to_page_external_id => redirected_to ? redirected_to.object_id : nil,
|
|
85
|
+
:route_external_id => route.object_id
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def init(response, &block)
|
|
90
|
+
@response = response.clone
|
|
91
|
+
@url = response.request.url
|
|
92
|
+
@path = response.request.path
|
|
93
|
+
@method = response.request.method
|
|
94
|
+
@document = Nokogiri::HTML(response.body)
|
|
95
|
+
self.redirected_to = block.call(response.redirect_url) if response.redirect?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def reload(&block)
|
|
99
|
+
# TODO - should support all the verbs and params, but
|
|
100
|
+
# hack together for now to reload the authenticity token
|
|
101
|
+
response = yield url
|
|
102
|
+
init(response) {|redirected_to| }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def redirected?
|
|
106
|
+
!self.redirected_to.nil?
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def forms
|
|
110
|
+
self.parse_forms(@document)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def login_form
|
|
114
|
+
self.find_login_form(@document)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def to_html
|
|
118
|
+
@document.to_html
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def outbound_edges(type=nil)
|
|
122
|
+
@site_model.edges.select {|edge| edge.from == self && (type.nil? || (type == edge.type)) }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def inbound_edges(type=nil)
|
|
126
|
+
@site_model.edges.select {|edge| (edge.to == self) && (type.nil? || (type == edge.type)) }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def title
|
|
130
|
+
unless @title
|
|
131
|
+
title = (@document/'head title').first
|
|
132
|
+
@title = title.inner_html if title
|
|
133
|
+
end
|
|
134
|
+
@title
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module Stratagem::Crawler
|
|
2
|
+
module TraceUtils
|
|
3
|
+
def model_invocations_for_request()
|
|
4
|
+
prior_invocations = ActiveRecord::Base.stratagem.invocations_audit.clone
|
|
5
|
+
yield
|
|
6
|
+
post_invocations = ActiveRecord::Base.stratagem.invocations_audit.clone
|
|
7
|
+
delta = post_invocations - prior_invocations
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module Stratagem::Crawler
|
|
2
|
+
end
|
|
3
|
+
|
|
4
|
+
require 'stratagem/crawler/form'
|
|
5
|
+
require 'stratagem/crawler/trace_utils'
|
|
6
|
+
require 'stratagem/crawler/html_utils'
|
|
7
|
+
require 'stratagem/crawler/authentication'
|
|
8
|
+
require 'stratagem/crawler/session'
|
|
9
|
+
require 'stratagem/crawler/site_model'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class Hash
|
|
2
|
+
alias_method :ruby_get, :[]
|
|
3
|
+
attr_reader :hash_reads, :hash_writes
|
|
4
|
+
|
|
5
|
+
def [](name)
|
|
6
|
+
(@hash_reads ||= []) << name if (@auditing)
|
|
7
|
+
ruby_get name
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def enable_auditing
|
|
11
|
+
@auditing = true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# def []=
|
|
15
|
+
# end
|
|
16
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class Object
|
|
2
|
+
def methods_include?(name)
|
|
3
|
+
methods.include?(name.to_sym) || methods.include?(name.to_s)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def self.subclasses
|
|
7
|
+
classes = []
|
|
8
|
+
ObjectSpace.each_object(Class) do |c|
|
|
9
|
+
next unless c.superclass == self || c.ancestors.include?(self)
|
|
10
|
+
next if c == self
|
|
11
|
+
classes << c
|
|
12
|
+
end
|
|
13
|
+
classes
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
class RedParse::Node
|
|
2
|
+
def talking_about?(model)
|
|
3
|
+
talking_about = false
|
|
4
|
+
|
|
5
|
+
if self.receiver.methods_include?(:name)
|
|
6
|
+
if ((self.receiver) && self.receiver.name == model.klass.name)
|
|
7
|
+
# puts "\treceiver name #{self.receiver.name} is equal to #{model.klass.name}"
|
|
8
|
+
talking_about = true
|
|
9
|
+
elsif (self.receiver)
|
|
10
|
+
name = self.receiver.name
|
|
11
|
+
# if it's lowercase then it's possibly referencing an association
|
|
12
|
+
if (name.downcase == name)
|
|
13
|
+
# look through the models to see if one has an association with this name and class
|
|
14
|
+
Stratagem::Model::Application.instance.models.each do |app_model|
|
|
15
|
+
talking_about = app_model.association_match?(name, model)
|
|
16
|
+
break if talking_about
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
talking_about
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# iterates the node and figures out the class of the leaf that a call is talking about
|
|
26
|
+
def dereference
|
|
27
|
+
# recursion is evil
|
|
28
|
+
path = []
|
|
29
|
+
current_node = self
|
|
30
|
+
# puts "current:#{current_node.inspect}"
|
|
31
|
+
if (current_node.kind_of?(RedParse::VarNode))
|
|
32
|
+
path << current_node.name if current_node.methods_include?(:name)
|
|
33
|
+
else
|
|
34
|
+
while (current_node.methods_include?(:receiver) && (current_node = current_node.receiver) != nil)
|
|
35
|
+
path << current_node.name if current_node.methods_include?(:name)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
# p path
|
|
39
|
+
previous = nil
|
|
40
|
+
path.reverse!
|
|
41
|
+
path.map! {|element|
|
|
42
|
+
new_val = element
|
|
43
|
+
if (element.downcase != element)
|
|
44
|
+
# class?
|
|
45
|
+
begin
|
|
46
|
+
new_val = Class.class_eval(element)
|
|
47
|
+
rescue
|
|
48
|
+
logger.error "Unable to load class #{element} in #{path.inspect}"
|
|
49
|
+
end
|
|
50
|
+
elsif (element.pluralize == element)
|
|
51
|
+
# collection?
|
|
52
|
+
if (previous)
|
|
53
|
+
if (previous.kind_of?(Class))
|
|
54
|
+
# look up the loaded model of the class
|
|
55
|
+
model = Stratagem::Model::Application.instance.models.find {|m| m.klass == previous }
|
|
56
|
+
if (model)
|
|
57
|
+
match = previous.reflect_on_all_associations.find {|assoc| assoc.name.to_sym == element.to_sym }
|
|
58
|
+
if (match)
|
|
59
|
+
new_val = Class.class_eval(match.class_name)
|
|
60
|
+
else
|
|
61
|
+
logger.error "I give up, cannot determine what #{element} is in #{path.inspect}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
logger.error "Unknown previous type when trying to dereference a collection #{element} in #{path.inspect}"
|
|
66
|
+
end
|
|
67
|
+
else
|
|
68
|
+
logger.error "Unable to determine prior element for #{element} in #{path.inspect}"
|
|
69
|
+
end
|
|
70
|
+
elsif previous
|
|
71
|
+
# call to the class / object in the previous statement
|
|
72
|
+
if (['find','all','first','last'].include?(element))
|
|
73
|
+
# then expect to receive an instance of the previous type
|
|
74
|
+
new_val = previous
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
previous = new_val
|
|
78
|
+
new_val
|
|
79
|
+
}
|
|
80
|
+
path.last
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def logger
|
|
84
|
+
Rails.logger
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class String
|
|
2
|
+
def stratagem_strip_erb
|
|
3
|
+
self.gsub(/^[<%=]+/, '').gsub(/[-%>]+$/,'').strip
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def stratagem_contains_token?(token)
|
|
7
|
+
match = false
|
|
8
|
+
[
|
|
9
|
+
Regexp.compile("^#{token}$", true),
|
|
10
|
+
Regexp.compile("^#{token}[^A-Za-z0-9]", true),
|
|
11
|
+
Regexp.compile("[^A-Za-z0-9]#{token}$", true),
|
|
12
|
+
].each do |regex|
|
|
13
|
+
if (regex.match(self))
|
|
14
|
+
match = true
|
|
15
|
+
break
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
match
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class ActiveRecord::Base
|
|
2
|
+
class << self
|
|
3
|
+
def removed_methods=(methods)
|
|
4
|
+
@@removed_methods = methods
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def removed_methods
|
|
8
|
+
@@removed_methods
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def removed_validators=(validators)
|
|
12
|
+
@@removed_validators = validators
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Stratagem::ApplicationExtensions::Models::Annotations.configure(self)
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module ActiveRecord::Validations::ClassMethods
|
|
21
|
+
@@removed_validators = []
|
|
22
|
+
|
|
23
|
+
instance_methods.each do |m|
|
|
24
|
+
if (m =~ /^validates_/) && (m !~ /validates_each/)
|
|
25
|
+
alias_method "old_#{m}", m
|
|
26
|
+
undef_method m
|
|
27
|
+
puts "removing validator #{m}"
|
|
28
|
+
@@removed_validators << m.to_sym
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def removed_validators
|
|
33
|
+
@@removed_validators
|
|
34
|
+
end
|
|
35
|
+
end
|