forked from intrigueio/intrigue-core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
api.rb
369 lines (302 loc) · 11.9 KB
/
api.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
class IntrigueApp < Sinatra::Base
namespace '/v1' do
### ###
### System Config ###
### ###
post '/:project/config/system' do
global_config = Intrigue::Config::GlobalConfig.new
global_config.config["credentials"]["username"] = "#{params["username"]}"
global_config.config["credentials"]["password"] = "#{params["password"]}"
global_config.save
redirect "/v1/#{@project_name}" # handy if we're in a browser
end
# save the config
post '/:project/config/module' do
# Update our config if one of the fields have been changed. Note that we use ***
# as a way to mask out the full details in the view. If we have one that doesn't lead with ***
# go ahead and update it
global_config = Intrigue::Config::GlobalConfig.new
params.each do |k,v|
# skip unless we already know about this config setting, helps us avoid
# other parameters sent to this page (splat, project, etc)
next unless global_config.config["intrigue_global_module_config"][k]
global_config.config["intrigue_global_module_config"][k]["value"] = v unless v =~ /^\*\*\*/
end
global_config.save
redirect "/v1/#{@project_name}" # handy if we're in a browser
end
### ###
### Project Mgmt ###
### ###
# Create a project!
post '/project' do
# When we create the project, we want to make sure no HTML is
# stored, as we'll use this for display later on...
new_project_name = CGI::escapeHTML(params[:project])
# create the project unless it exists
unless Intrigue::Model::Project.first(:name => new_project_name)
Intrigue::Model::Project.create(:name => new_project_name)
end
redirect "/v1/#{new_project_name}/scan" # handy if we're in a browser
end
# save the config
post '/project/delete' do
# we have to collect the name bc we skip the before block
@project_name = params[:project]
project = Intrigue::Model::Project.first(:name => @project_name)
# create the project unless it exists
if project
project.destroy!
# recreate the default project if we've removed
if @project_name == "Default"
Intrigue::Model::Project.create(:name => "Default")
end
end
redirect '/v1/' # handy if we're in a browser
end
# Project Graph
get '/:project/graph.json/?' do
content_type 'application/json'
project = Intrigue::Model::Project.first(:name => @project_name)
project.export_graph_json
end
### ###
### System-Level Informational Calls ###
### ###
# Return a JSON array of all entity type
get '/entity_types.json' do
content_type 'application/json'
Intrigue::Model::Entity.descendants.map {|x| x.new.type_string }.to_json
end
# Export All Scan Type Info
get '/scans.json/?' do
content_type 'application/json'
scans = []
Intrigue::ScanFactory.list.each do |s|
scans << s.send(:new).metadata
end
scans.to_json
end
# Export All Tasks
get '/tasks.json/?' do
content_type 'application/json'
tasks = []
Intrigue::TaskFactory.list.each do |t|
tasks << t.send(:new).metadata
end
tasks.to_json
end
# Export a single task
get '/tasks/:task_name.json/?' do
content_type 'application/json'
task_name = params[:task_name]
Intrigue::TaskFactory.create_by_name(task_name).metadata.to_json
end
### ###
### Per-Project Entities ###
### ###
get '/:project/entities/:id.csv' do
content_type 'text/plain'
@entity = Intrigue::Model::Entity.scope_by_project(@project_name).first(:id => params[:id])
@entity.export_csv
end
get '/:project/entities/:id.json' do
content_type 'application/json'
@entity = Intrigue::Model::Entity.scope_by_project(@project_name).first(:id => params[:id])
@entity.export_json
end
### ###
### Per-Project Task Results ###
### ###
# Create a task result from a json request
# What we receive should look like this:
#
#payload = {
# "project_name" => project_name,
# "handlers" => []
# "task" => task_name,
# "entity" => entity_hash,
# "options" => options_list,
#}.to_json
post '/:project/task_results/?' do
project_name = params[:project]
# Parse the incoming request
payload = JSON.parse(request.body.read) if request.content_type == "application/json"
### don't take any shit
return nil unless payload
# Construct an entity from the entity_hash provided
type = payload["entity"]["type"]
attributes = payload["entity"].merge("type" => "Intrigue::Entity::#{type}")
# get the details from the payload
task_name = payload["task"]
options = payload["options"]
handlers = payload["handlers"]
# Try to find our project
project = Intrigue::Model::Project.first(:name => project_name)
unless project # If the project didn't exist, create it
project = Intrigue::Model::Project.create(:name => project_name)
end
# Try to find our entity
entity = Intrigue::Model::Entity.scope_by_project(project_name).first(
:name => payload["entity"]["name"],
:type => payload["entity"]["type"]
)
unless entity # If the entity didn't exist, create it
entity = Intrigue::Model::Entity.create(attributes.merge(:project => project))
entity.save
end
# Start the task_run
task_id = start_task_run(project.id, nil, task_name, entity, options, handlers)
status 200 if task_id
# must be a string otherwise it can be interpreted as a status code
task_id.to_s
end
# Accept the results of a task run
post '/:project/task_results/:id/?' do
raise "Broken?"
# Retrieve the request's body and parse it as JSON
result = JSON.parse(request.body.read)
# Do something with event_json
job_id = result["id"]
# Return status
status 200 if result
end
# Export All task results
get '/:project/task_results.json/?' do
raise "Not implemented"
end
# Show the results in a CSV format
get '/:project/task_results/:id.csv/?' do
content_type 'text/plain'
@task_result = Intrigue::Model::TaskResult.scope_by_project(@project_name).first(:id => params[:id])
@task_result.export_csv
end
# Show the results in a CSV format
get '/:project/task_results/:id.tsv/?' do
content_type 'text/plain'
@task_result = Intrigue::Model::TaskResult.scope_by_project(@project_name).first(:id => params[:id])
@task_result.export_tsv
end
# Show the results in a JSON format
get '/:project/task_results/:id.json/?' do
content_type 'application/json'
@result = Intrigue::Model::TaskResult.scope_by_project(@project_name).first(:id => params[:id])
@result.export_json if @result
end
# Determine if the task run is complete
get '/:project/task_results/:id/complete/?' do
# Get the task result and return unless it's false
x = Intrigue::Model::TaskResult.scope_by_project(@project_name).first(:id => params[:id])
return false unless x
# if we got it, and it's complete, return true
return "true" if x.complete
# Otherwise, not ready yet, return false
false
end
# Get the task log
get '/:project/task_results/:id/log/?' do
content_type 'application/json'
@result = Intrigue::Model::TaskResult.scope_by_project(@project_name).first(:id => params[:id])
return unless @result
{:data => @result.log}.to_json
end
### ###
### Per-Project Scan Results ###
### ###
# Show the results in a JSON
get '/:project/scan_results/:id.json/?' do
content_type 'application/json'
@result = Intrigue::Model::ScanResult.scope_by_project(@project_name).first(:id => params[:id])
@result.export_json if @result
end
# Show the results in a CSV
get '/:project/scan_results/:id.csv/?' do
content_type 'text/plain'
@result = Intrigue::Model::ScanResult.scope_by_project(@project_name).first(:id => params[:id])
@result.export_csv if @result
end
# Show the results in a graph format
get '/:project/scan_results/:id/graph.csv/?' do
content_type 'text/plain'
@result = Intrigue::Model::ScanResult.scope_by_project(@project_name).first(:id => params[:id])
@result.export_graph_csv if @result
end
# Show the results in a graph format
get '/:project/scan_results/:id/graph.json/?' do
content_type 'application/json'
@result = Intrigue::Model::ScanResult.scope_by_project(@project_name).first(:id => params[:id])
@result.export_graph_json if @result
end
# Show the results in a graph format
get '/:project/scan_results/:id/graph.gexf/?' do
content_type 'text/plain'
result = Intrigue::Model::ScanResult.scope_by_project(@project_name).first(:id => params[:id])
return unless result
# Generate a list of entities and task runs to work through
@entity_pairs = []
result.task_results.each do |task_result|
task_result.entities.each do |entity|
@entity_pairs << {:task_result => task_result, :entity => entity}
end
end
erb :'scans/gexf', :layout => false
end
# Determine if the scan run is complete
get '/:project/scan_results/:id/complete' do
result = Intrigue::Model::ScanResult.scope_by_project(@project_name).first(:id => params[:id])
# immediately return false unless we find the scan result
return false unless result
# check for completion
return "true" if result.complete
# default to false
false
end
# Endpoint to start a task run programmatically
post '/:project/scan_results/?' do
scan_result_info = JSON.parse(request.body.read) if request.content_type == "application/json"
project_name = scan_result_info["project_name"]
scan_type = scan_result_info["scan_type"]
entity_hash = scan_result_info["entity"]
depth = scan_result_info["depth"].to_i
options = scan_result_info["options"]
handlers = scan_result_info["handlers"]
# Get the project
project = Intrigue::Model::Project.first(:name => project_name)
unless project # If the project didn't exist, create it
project = Intrigue::Model::Project.create(:name => project_name)
end
# Try to find the entity
entity = Intrigue::Model::Entity.scope_by_project(project_name).first(
:name => entity_hash['name'],
:type => entity_hash['type']
) # If it doesn't exist, create it
unless entity
entity = Intrigue::Model::Entity.create({
:type => "Intrigue::Entity::#{entity_hash['type']}",
:name => entity_hash['name'],
:details => entity_hash['details'],
:project => project
})
end
# Set up the ScanResult object
scan_result = Intrigue::Model::ScanResult.create({
:scan_type => scan_type,
:name => "#{scan_type}",
:base_entity => entity,
:depth => depth,
:filter_strings => "",
:handlers => handlers,
:logger => Intrigue::Model::Logger.create(:project => project),
:project => project
})
id = scan_result.start
end
# Get the scan log
get '/:project/scan_results/:id/log' do
content_type 'application/json'
@result = Intrigue::Model::ScanResult.scope_by_project(@project_name).first(:id => params[:id])
return unless @result
{:data => @result.log}.to_json
end
end
end