gophish-ruby 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +295 -0
- data/CHANGELOG.md +83 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +340 -0
- data/Rakefile +12 -0
- data/docs/API_REFERENCE.md +512 -0
- data/docs/EXAMPLES.md +665 -0
- data/docs/GETTING_STARTED.md +340 -0
- data/lib/gophish/base.rb +206 -0
- data/lib/gophish/configuration.rb +5 -0
- data/lib/gophish/group.rb +72 -0
- data/lib/gophish/version.rb +5 -0
- data/lib/gophish-ruby.rb +18 -0
- data/sig/gophish/ruby.rbs +6 -0
- metadata +137 -0
data/docs/EXAMPLES.md
ADDED
@@ -0,0 +1,665 @@
|
|
1
|
+
# Examples
|
2
|
+
|
3
|
+
This document contains practical examples for common use cases with the Gophish Ruby SDK.
|
4
|
+
|
5
|
+
## Table of Contents
|
6
|
+
|
7
|
+
- [Basic Operations](#basic-operations)
|
8
|
+
- [CSV Operations](#csv-operations)
|
9
|
+
- [Error Handling](#error-handling)
|
10
|
+
- [Advanced Scenarios](#advanced-scenarios)
|
11
|
+
- [Production Examples](#production-examples)
|
12
|
+
|
13
|
+
## Basic Operations
|
14
|
+
|
15
|
+
### Configuration Setup
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'gophish-ruby'
|
19
|
+
|
20
|
+
# Standard configuration
|
21
|
+
Gophish.configure do |config|
|
22
|
+
config.url = "https://gophish.company.com"
|
23
|
+
config.api_key = "your-api-key"
|
24
|
+
config.verify_ssl = true
|
25
|
+
config.debug_output = false
|
26
|
+
end
|
27
|
+
|
28
|
+
# Development configuration with debugging
|
29
|
+
Gophish.configure do |config|
|
30
|
+
config.url = "https://localhost:3333"
|
31
|
+
config.api_key = "dev-api-key"
|
32
|
+
config.verify_ssl = false
|
33
|
+
config.debug_output = true
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
### Creating Groups
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
# Simple group creation
|
41
|
+
group = Gophish::Group.new(
|
42
|
+
name: "Engineering Team",
|
43
|
+
targets: [
|
44
|
+
{
|
45
|
+
first_name: "Alice",
|
46
|
+
last_name: "Developer",
|
47
|
+
email: "alice@company.com",
|
48
|
+
position: "Senior Developer"
|
49
|
+
},
|
50
|
+
{
|
51
|
+
first_name: "Bob",
|
52
|
+
last_name: "Engineer",
|
53
|
+
email: "bob@company.com",
|
54
|
+
position: "Software Engineer"
|
55
|
+
}
|
56
|
+
]
|
57
|
+
)
|
58
|
+
|
59
|
+
puts group.save ? "✓ Group created" : "✗ Failed: #{group.errors.full_messages}"
|
60
|
+
```
|
61
|
+
|
62
|
+
### Retrieving Groups
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
# Get all groups
|
66
|
+
all_groups = Gophish::Group.all
|
67
|
+
puts "Total groups: #{all_groups.length}"
|
68
|
+
|
69
|
+
all_groups.each do |group|
|
70
|
+
puts "#{group.id}: #{group.name} (#{group.targets.length} targets)"
|
71
|
+
end
|
72
|
+
|
73
|
+
# Find specific group
|
74
|
+
begin
|
75
|
+
group = Gophish::Group.find(1)
|
76
|
+
puts "Found: #{group.name}"
|
77
|
+
rescue StandardError => e
|
78
|
+
puts "Group not found: #{e.message}"
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
### Updating Groups
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
# Load and update existing group
|
86
|
+
group = Gophish::Group.find(1)
|
87
|
+
original_name = group.name
|
88
|
+
|
89
|
+
group.name = "Updated Engineering Team"
|
90
|
+
group.targets << {
|
91
|
+
first_name: "Charlie",
|
92
|
+
last_name: "New",
|
93
|
+
email: "charlie@company.com",
|
94
|
+
position: "Junior Developer"
|
95
|
+
}
|
96
|
+
|
97
|
+
if group.save
|
98
|
+
puts "✓ Updated group from '#{original_name}' to '#{group.name}'"
|
99
|
+
puts " Now has #{group.targets.length} targets"
|
100
|
+
else
|
101
|
+
puts "✗ Update failed: #{group.errors.full_messages}"
|
102
|
+
end
|
103
|
+
```
|
104
|
+
|
105
|
+
### Deleting Groups
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
# Safe deletion with confirmation
|
109
|
+
group = Gophish::Group.find(1)
|
110
|
+
puts "About to delete group: #{group.name} (#{group.targets.length} targets)"
|
111
|
+
|
112
|
+
if group.destroy
|
113
|
+
puts "✓ Group deleted successfully"
|
114
|
+
else
|
115
|
+
puts "✗ Failed to delete group"
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
## CSV Operations
|
120
|
+
|
121
|
+
### Basic CSV Import
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
# CSV data with proper headers
|
125
|
+
csv_data = <<~CSV
|
126
|
+
First Name,Last Name,Email,Position
|
127
|
+
John,Smith,john.smith@company.com,Manager
|
128
|
+
Sarah,Johnson,sarah.johnson@company.com,Developer
|
129
|
+
Mike,Brown,mike.brown@company.com,Analyst
|
130
|
+
Lisa,Wilson,lisa.wilson@company.com,Designer
|
131
|
+
CSV
|
132
|
+
|
133
|
+
group = Gophish::Group.new(name: "Marketing Department")
|
134
|
+
group.import_csv(csv_data)
|
135
|
+
|
136
|
+
puts "Imported #{group.targets.length} targets"
|
137
|
+
puts "First target: #{group.targets.first[:first_name]} #{group.targets.first[:last_name]}"
|
138
|
+
|
139
|
+
group.save
|
140
|
+
```
|
141
|
+
|
142
|
+
### Reading CSV from File
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
# Read from external CSV file
|
146
|
+
def import_from_file(file_path, group_name)
|
147
|
+
unless File.exist?(file_path)
|
148
|
+
puts "Error: File not found - #{file_path}"
|
149
|
+
return nil
|
150
|
+
end
|
151
|
+
|
152
|
+
csv_content = File.read(file_path)
|
153
|
+
|
154
|
+
group = Gophish::Group.new(name: group_name)
|
155
|
+
group.import_csv(csv_content)
|
156
|
+
|
157
|
+
if group.valid?
|
158
|
+
if group.save
|
159
|
+
puts "✓ Imported #{group.targets.length} targets from #{file_path}"
|
160
|
+
return group
|
161
|
+
else
|
162
|
+
puts "✗ Save failed: #{group.errors.full_messages}"
|
163
|
+
end
|
164
|
+
else
|
165
|
+
puts "✗ Validation failed: #{group.errors.full_messages}"
|
166
|
+
end
|
167
|
+
|
168
|
+
nil
|
169
|
+
end
|
170
|
+
|
171
|
+
# Usage
|
172
|
+
group = import_from_file("employees.csv", "All Employees")
|
173
|
+
```
|
174
|
+
|
175
|
+
### CSV with Different Encodings
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
# Handle different file encodings
|
179
|
+
def import_csv_with_encoding(file_path, group_name, encoding = 'UTF-8')
|
180
|
+
begin
|
181
|
+
csv_content = File.read(file_path, encoding: encoding)
|
182
|
+
|
183
|
+
# Convert to UTF-8 if needed
|
184
|
+
csv_content = csv_content.encode('UTF-8') unless encoding == 'UTF-8'
|
185
|
+
|
186
|
+
group = Gophish::Group.new(name: group_name)
|
187
|
+
group.import_csv(csv_content)
|
188
|
+
group.save
|
189
|
+
|
190
|
+
puts "✓ Imported #{group.targets.length} targets with #{encoding} encoding"
|
191
|
+
rescue Encoding::UndefinedConversionError => e
|
192
|
+
puts "✗ Encoding error: #{e.message}"
|
193
|
+
puts "Try a different encoding (e.g., 'ISO-8859-1', 'Windows-1252')"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Usage for different encodings
|
198
|
+
import_csv_with_encoding("employees_utf8.csv", "UTF-8 Group", 'UTF-8')
|
199
|
+
import_csv_with_encoding("employees_latin1.csv", "Latin-1 Group", 'ISO-8859-1')
|
200
|
+
```
|
201
|
+
|
202
|
+
### Large CSV Processing
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
require 'csv'
|
206
|
+
|
207
|
+
# Process large CSV files in chunks
|
208
|
+
def import_large_csv(file_path, group_name, chunk_size = 1000)
|
209
|
+
puts "Processing large CSV file: #{file_path}"
|
210
|
+
|
211
|
+
all_targets = []
|
212
|
+
row_count = 0
|
213
|
+
|
214
|
+
CSV.foreach(file_path, headers: true) do |row|
|
215
|
+
target = {
|
216
|
+
first_name: row['First Name'],
|
217
|
+
last_name: row['Last Name'],
|
218
|
+
email: row['Email'],
|
219
|
+
position: row['Position']
|
220
|
+
}
|
221
|
+
|
222
|
+
all_targets << target
|
223
|
+
row_count += 1
|
224
|
+
|
225
|
+
# Process in chunks
|
226
|
+
if all_targets.length >= chunk_size
|
227
|
+
puts " Processed #{row_count} rows..."
|
228
|
+
# Could save intermediate groups here if needed
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
puts "✓ Read #{row_count} rows total"
|
233
|
+
|
234
|
+
# Create single group with all targets
|
235
|
+
group = Gophish::Group.new(name: group_name, targets: all_targets)
|
236
|
+
|
237
|
+
if group.valid?
|
238
|
+
if group.save
|
239
|
+
puts "✓ Successfully imported #{group.targets.length} targets"
|
240
|
+
return group
|
241
|
+
else
|
242
|
+
puts "✗ Save failed: #{group.errors.full_messages}"
|
243
|
+
end
|
244
|
+
else
|
245
|
+
puts "✗ Validation failed: #{group.errors.full_messages}"
|
246
|
+
end
|
247
|
+
|
248
|
+
nil
|
249
|
+
end
|
250
|
+
|
251
|
+
# Usage
|
252
|
+
group = import_large_csv("large_employee_list.csv", "All Company Employees")
|
253
|
+
```
|
254
|
+
|
255
|
+
## Error Handling
|
256
|
+
|
257
|
+
### Comprehensive Error Handling
|
258
|
+
|
259
|
+
```ruby
|
260
|
+
def robust_group_creation(name, csv_file_path)
|
261
|
+
puts "Creating group '#{name}' from #{csv_file_path}"
|
262
|
+
|
263
|
+
# Step 1: Verify file exists
|
264
|
+
unless File.exist?(csv_file_path)
|
265
|
+
puts "✗ Error: CSV file not found"
|
266
|
+
return false
|
267
|
+
end
|
268
|
+
|
269
|
+
# Step 2: Read file safely
|
270
|
+
begin
|
271
|
+
csv_content = File.read(csv_file_path)
|
272
|
+
rescue Errno::EACCES
|
273
|
+
puts "✗ Error: Permission denied reading file"
|
274
|
+
return false
|
275
|
+
rescue StandardError => e
|
276
|
+
puts "✗ Error reading file: #{e.message}"
|
277
|
+
return false
|
278
|
+
end
|
279
|
+
|
280
|
+
# Step 3: Create group and import
|
281
|
+
group = Gophish::Group.new(name: name)
|
282
|
+
|
283
|
+
begin
|
284
|
+
group.import_csv(csv_content)
|
285
|
+
rescue CSV::MalformedCSVError => e
|
286
|
+
puts "✗ CSV format error: #{e.message}"
|
287
|
+
return false
|
288
|
+
rescue StandardError => e
|
289
|
+
puts "✗ Import error: #{e.message}"
|
290
|
+
return false
|
291
|
+
end
|
292
|
+
|
293
|
+
# Step 4: Validate
|
294
|
+
unless group.valid?
|
295
|
+
puts "✗ Validation errors:"
|
296
|
+
group.errors.full_messages.each { |error| puts " - #{error}" }
|
297
|
+
return false
|
298
|
+
end
|
299
|
+
|
300
|
+
# Step 5: Save with API error handling
|
301
|
+
begin
|
302
|
+
unless group.save
|
303
|
+
puts "✗ Save failed (API errors):"
|
304
|
+
group.errors.full_messages.each { |error| puts " - #{error}" }
|
305
|
+
return false
|
306
|
+
end
|
307
|
+
rescue StandardError => e
|
308
|
+
puts "✗ Network/API error: #{e.message}"
|
309
|
+
return false
|
310
|
+
end
|
311
|
+
|
312
|
+
puts "✓ Successfully created group '#{name}' with #{group.targets.length} targets"
|
313
|
+
puts " Group ID: #{group.id}"
|
314
|
+
true
|
315
|
+
end
|
316
|
+
|
317
|
+
# Usage
|
318
|
+
success = robust_group_creation("Sales Team", "sales_team.csv")
|
319
|
+
puts success ? "Operation completed" : "Operation failed"
|
320
|
+
```
|
321
|
+
|
322
|
+
### Validation Error Details
|
323
|
+
|
324
|
+
```ruby
|
325
|
+
def diagnose_validation_errors(group)
|
326
|
+
return true if group.valid?
|
327
|
+
|
328
|
+
puts "Validation failed. Details:"
|
329
|
+
|
330
|
+
# Check each error type
|
331
|
+
group.errors.each do |attribute, messages|
|
332
|
+
puts " #{attribute.to_s.humanize}:"
|
333
|
+
Array(messages).each { |msg| puts " - #{msg}" }
|
334
|
+
end
|
335
|
+
|
336
|
+
# Special handling for targets errors
|
337
|
+
if group.errors[:targets].any?
|
338
|
+
puts "\nTarget validation details:"
|
339
|
+
group.targets.each_with_index do |target, index|
|
340
|
+
# Check each required field
|
341
|
+
%i[first_name last_name email position].each do |field|
|
342
|
+
value = target[field] || target[field.to_s]
|
343
|
+
if value.nil? || value.to_s.strip.empty?
|
344
|
+
puts " Target #{index}: missing #{field}"
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
# Check email format
|
349
|
+
email = target[:email] || target['email']
|
350
|
+
if email && !email.match?(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/)
|
351
|
+
puts " Target #{index}: invalid email format '#{email}'"
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
false
|
357
|
+
end
|
358
|
+
|
359
|
+
# Usage
|
360
|
+
group = Gophish::Group.new(name: "", targets: [
|
361
|
+
{ first_name: "John", last_name: "", email: "invalid-email", position: "Manager" }
|
362
|
+
])
|
363
|
+
|
364
|
+
diagnose_validation_errors(group)
|
365
|
+
```
|
366
|
+
|
367
|
+
## Advanced Scenarios
|
368
|
+
|
369
|
+
### Batch Operations
|
370
|
+
|
371
|
+
```ruby
|
372
|
+
# Create multiple groups from directory of CSV files
|
373
|
+
def create_groups_from_directory(csv_directory)
|
374
|
+
Dir.glob("#{csv_directory}/*.csv").each do |csv_file|
|
375
|
+
file_name = File.basename(csv_file, '.csv')
|
376
|
+
group_name = file_name.gsub('_', ' ').split.map(&:capitalize).join(' ')
|
377
|
+
|
378
|
+
puts "Processing #{csv_file} -> '#{group_name}'"
|
379
|
+
|
380
|
+
csv_content = File.read(csv_file)
|
381
|
+
group = Gophish::Group.new(name: group_name)
|
382
|
+
group.import_csv(csv_content)
|
383
|
+
|
384
|
+
if group.valid? && group.save
|
385
|
+
puts " ✓ Created group with #{group.targets.length} targets"
|
386
|
+
else
|
387
|
+
puts " ✗ Failed: #{group.errors.full_messages.join(', ')}"
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# Usage
|
393
|
+
create_groups_from_directory("./csv_files")
|
394
|
+
```
|
395
|
+
|
396
|
+
### Group Synchronization
|
397
|
+
|
398
|
+
```ruby
|
399
|
+
# Sync local CSV with existing Gophish group
|
400
|
+
def sync_group_with_csv(group_id, csv_file_path)
|
401
|
+
# Load existing group
|
402
|
+
begin
|
403
|
+
group = Gophish::Group.find(group_id)
|
404
|
+
rescue StandardError
|
405
|
+
puts "Group #{group_id} not found"
|
406
|
+
return false
|
407
|
+
end
|
408
|
+
|
409
|
+
puts "Syncing group '#{group.name}' with #{csv_file_path}"
|
410
|
+
puts " Current targets: #{group.targets.length}"
|
411
|
+
|
412
|
+
# Import new targets from CSV
|
413
|
+
csv_content = File.read(csv_file_path)
|
414
|
+
temp_group = Gophish::Group.new(name: "temp")
|
415
|
+
temp_group.import_csv(csv_content)
|
416
|
+
|
417
|
+
# Compare and update
|
418
|
+
old_emails = group.targets.map { |t| t[:email] || t['email'] }
|
419
|
+
new_emails = temp_group.targets.map { |t| t[:email] || t['email'] }
|
420
|
+
|
421
|
+
added = new_emails - old_emails
|
422
|
+
removed = old_emails - new_emails
|
423
|
+
|
424
|
+
puts " Changes detected:"
|
425
|
+
puts " Adding: #{added.length} targets"
|
426
|
+
puts " Removing: #{removed.length} targets"
|
427
|
+
|
428
|
+
# Update the group
|
429
|
+
group.targets = temp_group.targets
|
430
|
+
|
431
|
+
if group.save
|
432
|
+
puts " ✓ Sync completed"
|
433
|
+
puts " New target count: #{group.targets.length}"
|
434
|
+
else
|
435
|
+
puts " ✗ Sync failed: #{group.errors.full_messages}"
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
# Usage
|
440
|
+
sync_group_with_csv(1, "updated_employees.csv")
|
441
|
+
```
|
442
|
+
|
443
|
+
### Change Tracking Example
|
444
|
+
|
445
|
+
```ruby
|
446
|
+
# Monitor and log changes to groups
|
447
|
+
class GroupChangeTracker
|
448
|
+
def self.track_changes(group)
|
449
|
+
return unless group.persisted?
|
450
|
+
|
451
|
+
changes = {}
|
452
|
+
|
453
|
+
if group.attribute_changed?(:name)
|
454
|
+
changes[:name] = {
|
455
|
+
from: group.attribute_was(:name),
|
456
|
+
to: group.name
|
457
|
+
}
|
458
|
+
end
|
459
|
+
|
460
|
+
if group.attribute_changed?(:targets)
|
461
|
+
old_targets = group.attribute_was(:targets) || []
|
462
|
+
new_targets = group.targets || []
|
463
|
+
|
464
|
+
changes[:targets] = {
|
465
|
+
count_change: new_targets.length - old_targets.length,
|
466
|
+
old_count: old_targets.length,
|
467
|
+
new_count: new_targets.length
|
468
|
+
}
|
469
|
+
end
|
470
|
+
|
471
|
+
changes
|
472
|
+
end
|
473
|
+
|
474
|
+
def self.log_and_save(group)
|
475
|
+
changes = track_changes(group)
|
476
|
+
|
477
|
+
if changes.any?
|
478
|
+
puts "Saving changes to group '#{group.name}':"
|
479
|
+
changes.each do |field, change|
|
480
|
+
case field
|
481
|
+
when :name
|
482
|
+
puts " Name: '#{change[:from]}' → '#{change[:to]}'"
|
483
|
+
when :targets
|
484
|
+
puts " Targets: #{change[:old_count]} → #{change[:new_count]} (#{change[:count_change]:+d})"
|
485
|
+
end
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
result = group.save
|
490
|
+
puts result ? " ✓ Changes saved" : " ✗ Save failed"
|
491
|
+
result
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
# Usage
|
496
|
+
group = Gophish::Group.find(1)
|
497
|
+
group.name = "Updated Team Name"
|
498
|
+
group.targets << { first_name: "New", last_name: "Person", email: "new@company.com", position: "Intern" }
|
499
|
+
|
500
|
+
GroupChangeTracker.log_and_save(group)
|
501
|
+
```
|
502
|
+
|
503
|
+
## Production Examples
|
504
|
+
|
505
|
+
### Configuration with Environment Variables
|
506
|
+
|
507
|
+
```ruby
|
508
|
+
# config/gophish.rb
|
509
|
+
class GophishConfig
|
510
|
+
def self.setup
|
511
|
+
Gophish.configure do |config|
|
512
|
+
config.url = ENV.fetch('GOPHISH_URL') { raise "GOPHISH_URL environment variable required" }
|
513
|
+
config.api_key = ENV.fetch('GOPHISH_API_KEY') { raise "GOPHISH_API_KEY environment variable required" }
|
514
|
+
config.verify_ssl = ENV.fetch('GOPHISH_VERIFY_SSL', 'true') == 'true'
|
515
|
+
config.debug_output = ENV.fetch('GOPHISH_DEBUG', 'false') == 'true'
|
516
|
+
end
|
517
|
+
|
518
|
+
# Test connection
|
519
|
+
begin
|
520
|
+
Gophish::Group.all
|
521
|
+
puts "✓ Gophish connection configured successfully"
|
522
|
+
rescue StandardError => e
|
523
|
+
puts "✗ Gophish connection failed: #{e.message}"
|
524
|
+
raise
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
# Initialize in your application
|
530
|
+
GophishConfig.setup
|
531
|
+
```
|
532
|
+
|
533
|
+
### Logging and Monitoring
|
534
|
+
|
535
|
+
```ruby
|
536
|
+
require 'logger'
|
537
|
+
|
538
|
+
class GophishManager
|
539
|
+
def initialize(logger = Logger.new(STDOUT))
|
540
|
+
@logger = logger
|
541
|
+
end
|
542
|
+
|
543
|
+
def create_group_with_logging(name, csv_data)
|
544
|
+
@logger.info "Starting group creation: '#{name}'"
|
545
|
+
|
546
|
+
group = Gophish::Group.new(name: name)
|
547
|
+
|
548
|
+
# Parse CSV with logging
|
549
|
+
begin
|
550
|
+
group.import_csv(csv_data)
|
551
|
+
@logger.info "CSV parsed successfully: #{group.targets.length} targets"
|
552
|
+
rescue CSV::MalformedCSVError => e
|
553
|
+
@logger.error "CSV parsing failed: #{e.message}"
|
554
|
+
return false
|
555
|
+
end
|
556
|
+
|
557
|
+
# Validate with detailed logging
|
558
|
+
unless group.valid?
|
559
|
+
@logger.error "Validation failed for group '#{name}'"
|
560
|
+
group.errors.full_messages.each { |error| @logger.error " - #{error}" }
|
561
|
+
return false
|
562
|
+
end
|
563
|
+
|
564
|
+
# Save with timing
|
565
|
+
start_time = Time.now
|
566
|
+
success = group.save
|
567
|
+
duration = Time.now - start_time
|
568
|
+
|
569
|
+
if success
|
570
|
+
@logger.info "Group '#{name}' created successfully in #{duration.round(2)}s (ID: #{group.id})"
|
571
|
+
else
|
572
|
+
@logger.error "Failed to save group '#{name}' after #{duration.round(2)}s"
|
573
|
+
group.errors.full_messages.each { |error| @logger.error " - #{error}" }
|
574
|
+
end
|
575
|
+
|
576
|
+
success
|
577
|
+
end
|
578
|
+
|
579
|
+
def bulk_import(csv_directory)
|
580
|
+
@logger.info "Starting bulk import from #{csv_directory}"
|
581
|
+
|
582
|
+
csv_files = Dir.glob("#{csv_directory}/*.csv")
|
583
|
+
@logger.info "Found #{csv_files.length} CSV files to process"
|
584
|
+
|
585
|
+
results = { success: 0, failed: 0 }
|
586
|
+
|
587
|
+
csv_files.each_with_index do |csv_file, index|
|
588
|
+
file_name = File.basename(csv_file, '.csv')
|
589
|
+
group_name = file_name.gsub(/[_-]/, ' ').split.map(&:capitalize).join(' ')
|
590
|
+
|
591
|
+
@logger.info "[#{index + 1}/#{csv_files.length}] Processing '#{group_name}'"
|
592
|
+
|
593
|
+
csv_content = File.read(csv_file)
|
594
|
+
if create_group_with_logging(group_name, csv_content)
|
595
|
+
results[:success] += 1
|
596
|
+
else
|
597
|
+
results[:failed] += 1
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
@logger.info "Bulk import completed: #{results[:success]} succeeded, #{results[:failed]} failed"
|
602
|
+
results
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
# Usage
|
607
|
+
logger = Logger.new('gophish_import.log')
|
608
|
+
manager = GophishManager.new(logger)
|
609
|
+
results = manager.bulk_import('./employee_csvs')
|
610
|
+
```
|
611
|
+
|
612
|
+
### Retry Logic for API Calls
|
613
|
+
|
614
|
+
```ruby
|
615
|
+
class RetryableGophishOperation
|
616
|
+
MAX_RETRIES = 3
|
617
|
+
RETRY_DELAY = 2
|
618
|
+
|
619
|
+
def self.with_retry(operation_name)
|
620
|
+
retries = 0
|
621
|
+
|
622
|
+
begin
|
623
|
+
yield
|
624
|
+
rescue StandardError => e
|
625
|
+
retries += 1
|
626
|
+
|
627
|
+
if retries <= MAX_RETRIES
|
628
|
+
puts "#{operation_name} failed (attempt #{retries}): #{e.message}"
|
629
|
+
puts "Retrying in #{RETRY_DELAY} seconds..."
|
630
|
+
sleep(RETRY_DELAY)
|
631
|
+
retry
|
632
|
+
else
|
633
|
+
puts "#{operation_name} failed after #{MAX_RETRIES} attempts"
|
634
|
+
raise
|
635
|
+
end
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
def self.create_group_with_retry(name, targets)
|
640
|
+
with_retry("Group creation for '#{name}'") do
|
641
|
+
group = Gophish::Group.new(name: name, targets: targets)
|
642
|
+
|
643
|
+
unless group.valid?
|
644
|
+
raise StandardError, "Validation failed: #{group.errors.full_messages.join(', ')}"
|
645
|
+
end
|
646
|
+
|
647
|
+
unless group.save
|
648
|
+
raise StandardError, "Save failed: #{group.errors.full_messages.join(', ')}"
|
649
|
+
end
|
650
|
+
|
651
|
+
puts "✓ Group '#{name}' created successfully"
|
652
|
+
group
|
653
|
+
end
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
# Usage
|
658
|
+
targets = [
|
659
|
+
{ first_name: "John", last_name: "Doe", email: "john@example.com", position: "Manager" }
|
660
|
+
]
|
661
|
+
|
662
|
+
group = RetryableGophishOperation.create_group_with_retry("Test Group", targets)
|
663
|
+
```
|
664
|
+
|
665
|
+
These examples demonstrate real-world usage patterns and robust error handling for production environments.
|