gophish-ruby 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59b79d410ed7e2467c187c89992d394ee71d911da58524ecc3cc4b4eb5fb2a39
4
- data.tar.gz: 62ca19b3ecdfb8cc88bfedc2bd4f53fca272db9a9f5de8846398573413daf4ae
3
+ metadata.gz: ba7f348fc87e10ad752fe1b59ce329357ca0948dc180b5e5912210fc55a82a44
4
+ data.tar.gz: bbfd2bad8612b3814f04820c9bcb3e13cf21352b2712e13c85a2cce37c3e4cf5
5
5
  SHA512:
6
- metadata.gz: 78d1964ebcb0d3caba10c53d0f78e819b356b88961af7dfe48dde8611469e4c1919991cf7dec2aae991ccccea30e66a2f0bfea56f51420ebec4e8e0bbb07807e
7
- data.tar.gz: 321297b58bdd280855faf6d988bfc9f4d2c062a5569a41316371c501bafabc7c8175e9312f5cb22f10fd0df1452ee439b65fe5c0475f01bd5243125868ab2c28
6
+ metadata.gz: 697688a9381c1f53c4174e0d3922b4effc5d582dbd56dd0c71d40370126fb287e143ea7ee18fab7856cff52fc19d16ab6514baa04ca8759a3a8e6997cece2b97
7
+ data.tar.gz: 48965fe82e77390d55df53cbc9c763edc411e055dadcd51fd7852dba49f17ac519b48a7986a021be2b7eb7ca40153a3effee4d5b999887fa400052f31a437d95
data/CHANGELOG.md CHANGED
@@ -8,16 +8,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  ## [Unreleased]
9
9
 
10
10
  ### Added
11
+ - **Template Management System**
12
+ - `Gophish::Template` class for managing email templates
13
+ - Full CRUD operations for templates (create, read, update, delete)
14
+ - Support for HTML and plain text email content
15
+ - Comprehensive validations requiring template name and content
16
+ - Email import functionality with `Template.import_email` class method
17
+ - Option to convert links during email import for tracking
18
+
19
+ - **Attachment Management**
20
+ - Add attachments to templates with `#add_attachment` method
21
+ - Remove attachments by name with `#remove_attachment` method
22
+ - Query attachment status with `#has_attachments?` and `#attachment_count`
23
+ - Automatic Base64 encoding of attachment content
24
+ - Validation of attachment structure (content, type, name required)
25
+
26
+ - **Enhanced Change Tracking**
27
+ - Migrated from custom change tracking to ActiveModel::Dirty
28
+ - Improved change detection with `#changed?` and `#clear_changes_information`
29
+ - Better integration with Rails-style attribute tracking
30
+
11
31
  - Comprehensive documentation with getting started guide, API reference, and examples
12
32
  - Enhanced README with detailed usage instructions and configuration examples
13
33
 
14
34
  ### Changed
35
+ - **Breaking Change**: Replaced custom `@changed_attributes` system with ActiveModel::Dirty
36
+ - Updated Base class to use `define_attribute_methods` for proper dirty tracking
37
+ - Modified `#update_record` to include `id` in update payload for proper API calls
15
38
  - Improved error messages for validation failures
16
39
  - Enhanced CSV import validation with better error reporting
17
40
 
18
41
  ### Fixed
19
42
  - Console script requiring wrong file name (`gophish_ruby` → `gophish-ruby`)
20
43
  - Spec helper requiring wrong file name for consistent naming
44
+ - Corrected require statement in main library file for Template class
21
45
 
22
46
  ## [0.1.0] - 2025-08-29
23
47
 
data/README.md CHANGED
@@ -11,9 +11,11 @@ A Ruby SDK for the [Gophish](https://getgophish.com/) phishing simulation platfo
11
11
  - **ActiveModel Integration**: Familiar Rails-like attributes, validations, and callbacks
12
12
  - **Automatic Authentication**: Built-in API key authentication for all requests
13
13
  - **CSV Import Support**: Easy bulk import of targets from CSV files
14
+ - **Email Template Management**: Create, modify, and manage email templates with attachment support
15
+ - **Email Import**: Import existing emails and convert them to templates
14
16
  - **SSL Configuration**: Configurable SSL verification for development environments
15
17
  - **Debug Support**: Built-in debugging capabilities for API interactions
16
- - **Change Tracking**: Automatic tracking of attribute changes
18
+ - **Change Tracking**: Automatic tracking of attribute changes with ActiveModel::Dirty
17
19
  - **Comprehensive Validation**: Built-in validations for all data models
18
20
 
19
21
  ## Installation
@@ -184,9 +186,110 @@ unless group.valid?
184
186
  end
185
187
  ```
186
188
 
189
+ ### Templates Management
190
+
191
+ Templates define the email content for your phishing campaigns, including HTML/text content and attachments.
192
+
193
+ #### Creating a Template
194
+
195
+ ```ruby
196
+ # Create a new email template
197
+ template = Gophish::Template.new(
198
+ name: "Phishing Awareness Test",
199
+ subject: "Security Update Required",
200
+ html: "<h1>Important Security Update</h1><p>Please click <a href='{{.URL}}'>here</a> to update your password.</p>",
201
+ text: "Important Security Update\n\nPlease visit {{.URL}} to update your password."
202
+ )
203
+
204
+ if template.save
205
+ puts "Template created successfully with ID: #{template.id}"
206
+ else
207
+ puts "Failed to create template: #{template.errors.full_messages}"
208
+ end
209
+ ```
210
+
211
+ #### Adding Attachments
212
+
213
+ ```ruby
214
+ template = Gophish::Template.new(
215
+ name: "Invoice Template",
216
+ subject: "Invoice #12345",
217
+ html: "<p>Please find your invoice attached.</p>"
218
+ )
219
+
220
+ # Add an attachment
221
+ file_content = File.read("path/to/invoice.pdf")
222
+ template.add_attachment(file_content, "application/pdf", "invoice.pdf")
223
+
224
+ puts "Template has #{template.attachment_count} attachments"
225
+ ```
226
+
227
+ #### Managing Attachments
228
+
229
+ ```ruby
230
+ # Check if template has attachments
231
+ if template.has_attachments?
232
+ puts "Template has attachments"
233
+ end
234
+
235
+ # Remove an attachment by name
236
+ template.remove_attachment("invoice.pdf")
237
+ puts "Attachments remaining: #{template.attachment_count}"
238
+ ```
239
+
240
+ #### Importing Email Content
241
+
242
+ ```ruby
243
+ # Import an existing email (.eml file content)
244
+ email_content = File.read("path/to/email.eml")
245
+
246
+ imported_data = Gophish::Template.import_email(
247
+ email_content,
248
+ convert_links: true # Convert links to Gophish tracking format
249
+ )
250
+
251
+ # Create template from imported data
252
+ template = Gophish::Template.new(imported_data)
253
+ template.name = "Imported Email Template"
254
+ template.save
255
+ ```
256
+
257
+ #### Retrieving Templates
258
+
259
+ ```ruby
260
+ # Get all templates
261
+ templates = Gophish::Template.all
262
+ puts "Found #{templates.length} templates"
263
+
264
+ # Find a specific template by ID
265
+ template = Gophish::Template.find(1)
266
+ puts "Template: #{template.name}"
267
+ ```
268
+
269
+ #### Updating a Template
270
+
271
+ ```ruby
272
+ template = Gophish::Template.find(1)
273
+ template.subject = "Updated Subject Line"
274
+ template.html = "<h1>Updated Content</h1>"
275
+
276
+ if template.save
277
+ puts "Template updated successfully"
278
+ end
279
+ ```
280
+
281
+ #### Deleting a Template
282
+
283
+ ```ruby
284
+ template = Gophish::Template.find(1)
285
+ if template.destroy
286
+ puts "Template deleted successfully"
287
+ end
288
+ ```
289
+
187
290
  ### Change Tracking
188
291
 
189
- The SDK automatically tracks changes to attributes:
292
+ The SDK automatically tracks changes to attributes using ActiveModel::Dirty:
190
293
 
191
294
  ```ruby
192
295
  group = Gophish::Group.find(1)
@@ -258,6 +361,44 @@ Each target in the `targets` array should have:
258
361
 
259
362
  - `#import_csv(csv_data)` - Import targets from CSV data
260
363
 
364
+ #### `Gophish::Template`
365
+
366
+ Represents a Gophish email template.
367
+
368
+ **Attributes:**
369
+
370
+ - `id` (Integer) - Unique template identifier
371
+ - `name` (String) - Template name (required)
372
+ - `subject` (String) - Email subject line
373
+ - `text` (String) - Plain text email content
374
+ - `html` (String) - HTML email content
375
+ - `modified_date` (String) - Last modification timestamp
376
+ - `attachments` (Array) - Array of attachment hashes
377
+
378
+ **Attachment Structure:**
379
+ Each attachment in the `attachments` array should have:
380
+
381
+ - `content` (String) - Base64 encoded file content (required)
382
+ - `type` (String) - MIME type of the attachment (required)
383
+ - `name` (String) - Filename of the attachment (required)
384
+
385
+ **Class Methods:**
386
+
387
+ - `.import_email(content, convert_links: false)` - Import email content and return template data
388
+
389
+ **Instance Methods:**
390
+
391
+ - `#add_attachment(content, type, name)` - Add an attachment to the template
392
+ - `#remove_attachment(name)` - Remove an attachment by filename
393
+ - `#has_attachments?` - Check if template has any attachments
394
+ - `#attachment_count` - Get the number of attachments
395
+
396
+ **Validations:**
397
+
398
+ - Template must have a name
399
+ - Template must have either text or HTML content (or both)
400
+ - All attachments must have content, type, and name
401
+
261
402
  ## Development
262
403
 
263
404
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -7,6 +7,7 @@ This document provides detailed API reference for the Gophish Ruby SDK.
7
7
  - [Configuration](#configuration)
8
8
  - [Base Class](#base-class)
9
9
  - [Group Class](#group-class)
10
+ - [Template Class](#template-class)
10
11
  - [Error Handling](#error-handling)
11
12
  - [Examples](#examples)
12
13
 
@@ -125,7 +126,7 @@ Create or update the resource on the server.
125
126
  **Side Effects:**
126
127
 
127
128
  - Sets `@persisted` to true on success
128
- - Clears `@changed_attributes` on success
129
+ - Clears change tracking information on success
129
130
  - Adds errors to `#errors` on failure
130
131
 
131
132
  **Example:**
@@ -207,7 +208,7 @@ group = Gophish::Group.new
207
208
  puts group.new_record? # => true
208
209
  ```
209
210
 
210
- ##### `#changed_attributes`
211
+ ##### `#changed`
211
212
 
212
213
  Get array of attribute names that have changed.
213
214
 
@@ -218,7 +219,23 @@ Get array of attribute names that have changed.
218
219
  ```ruby
219
220
  group = Gophish::Group.find(1)
220
221
  group.name = "New Name"
221
- puts group.changed_attributes # => ["name"]
222
+ puts group.changed # => ["name"]
223
+ ```
224
+
225
+ ##### `#changed?`
226
+
227
+ Check if any attributes have changed.
228
+
229
+ **Returns:** Boolean
230
+
231
+ **Example:**
232
+
233
+ ```ruby
234
+ group = Gophish::Group.find(1)
235
+ puts group.changed? # => false
236
+
237
+ group.name = "New Name"
238
+ puts group.changed? # => true
222
239
  ```
223
240
 
224
241
  ##### `#attribute_changed?(attr)`
@@ -236,8 +253,8 @@ Check if a specific attribute has changed.
236
253
  ```ruby
237
254
  group = Gophish::Group.find(1)
238
255
  group.name = "New Name"
256
+ puts group.name_changed? # => true (using dynamic method)
239
257
  puts group.attribute_changed?(:name) # => true
240
- puts group.attribute_changed?("targets") # => false
241
258
  ```
242
259
 
243
260
  ##### `#attribute_was(attr)`
@@ -256,6 +273,7 @@ Get the previous value of a changed attribute.
256
273
  group = Gophish::Group.find(1)
257
274
  original_name = group.name
258
275
  group.name = "New Name"
276
+ puts group.name_was # => original_name (using dynamic method)
259
277
  puts group.attribute_was(:name) # => original_name
260
278
  ```
261
279
 
@@ -376,6 +394,236 @@ group.import_csv(csv_content)
376
394
  group.save
377
395
  ```
378
396
 
397
+ ## Template Class
398
+
399
+ The `Gophish::Template` class represents an email template in Gophish.
400
+
401
+ ### Class: `Gophish::Template < Gophish::Base`
402
+
403
+ #### Attributes
404
+
405
+ | Attribute | Type | Required | Description |
406
+ |-----------|------|----------|-------------|
407
+ | `id` | Integer | No | Unique template identifier (set by server) |
408
+ | `name` | String | Yes | Template name |
409
+ | `subject` | String | No | Email subject line |
410
+ | `text` | String | No | Plain text email content |
411
+ | `html` | String | No | HTML email content |
412
+ | `modified_date` | String | No | Last modification timestamp (set by server) |
413
+ | `attachments` | Array | No | Array of attachment hashes |
414
+
415
+ #### Attachment Structure
416
+
417
+ Each attachment in the `attachments` array must have:
418
+
419
+ | Field | Type | Required | Description |
420
+ |-------|------|----------|-------------|
421
+ | `content` | String | Yes | Base64 encoded file content |
422
+ | `type` | String | Yes | MIME type (e.g., "application/pdf") |
423
+ | `name` | String | Yes | Filename |
424
+
425
+ #### Validations
426
+
427
+ - `name` must be present
428
+ - Must have either `text` or `html` content (or both)
429
+ - Each attachment must be a Hash with required fields (`content`, `type`, `name`)
430
+
431
+ #### Class Methods
432
+
433
+ ##### `.import_email(content, convert_links: false)`
434
+
435
+ Import email content and return template data.
436
+
437
+ **Parameters:**
438
+
439
+ - `content` (String) - Raw email content (.eml format)
440
+ - `convert_links` (Boolean) - Whether to convert links for Gophish tracking (default: false)
441
+
442
+ **Returns:** Hash of template attributes
443
+
444
+ **Raises:**
445
+
446
+ - `StandardError` if import fails
447
+
448
+ **Example:**
449
+
450
+ ```ruby
451
+ email_content = File.read("sample.eml")
452
+ template_data = Gophish::Template.import_email(email_content, convert_links: true)
453
+
454
+ template = Gophish::Template.new(template_data)
455
+ template.name = "Imported Template"
456
+ template.save
457
+ ```
458
+
459
+ #### Instance Methods
460
+
461
+ ##### `#add_attachment(content, type, name)`
462
+
463
+ Add an attachment to the template.
464
+
465
+ **Parameters:**
466
+
467
+ - `content` (String) - File content (will be Base64 encoded automatically)
468
+ - `type` (String) - MIME type
469
+ - `name` (String) - Filename
470
+
471
+ **Returns:** Void
472
+
473
+ **Side Effects:**
474
+
475
+ - Adds attachment to `attachments` array
476
+ - Marks `attachments` attribute as changed
477
+
478
+ **Example:**
479
+
480
+ ```ruby
481
+ template = Gophish::Template.new(name: "Test", html: "<p>Test</p>")
482
+ file_content = File.read("document.pdf")
483
+ template.add_attachment(file_content, "application/pdf", "document.pdf")
484
+ ```
485
+
486
+ ##### `#remove_attachment(name)`
487
+
488
+ Remove an attachment by filename.
489
+
490
+ **Parameters:**
491
+
492
+ - `name` (String) - Filename of attachment to remove
493
+
494
+ **Returns:** Void
495
+
496
+ **Side Effects:**
497
+
498
+ - Removes matching attachment(s) from `attachments` array
499
+ - Marks `attachments` attribute as changed if any were removed
500
+
501
+ **Example:**
502
+
503
+ ```ruby
504
+ template.remove_attachment("document.pdf")
505
+ ```
506
+
507
+ ##### `#has_attachments?`
508
+
509
+ Check if template has any attachments.
510
+
511
+ **Returns:** Boolean
512
+
513
+ **Example:**
514
+
515
+ ```ruby
516
+ if template.has_attachments?
517
+ puts "Template has #{template.attachment_count} attachments"
518
+ end
519
+ ```
520
+
521
+ ##### `#attachment_count`
522
+
523
+ Get the number of attachments.
524
+
525
+ **Returns:** Integer
526
+
527
+ **Example:**
528
+
529
+ ```ruby
530
+ puts "Attachments: #{template.attachment_count}"
531
+ ```
532
+
533
+ #### Usage Examples
534
+
535
+ ##### Create a Template
536
+
537
+ ```ruby
538
+ template = Gophish::Template.new(
539
+ name: "Phishing Test Template",
540
+ subject: "Important Security Update",
541
+ html: "<h1>Security Update Required</h1><p>Please click <a href='{{.URL}}'>here</a> to update.</p>",
542
+ text: "Security Update Required\n\nPlease visit {{.URL}} to update your credentials."
543
+ )
544
+
545
+ if template.save
546
+ puts "Template created with ID: #{template.id}"
547
+ end
548
+ ```
549
+
550
+ ##### Template with Attachments
551
+
552
+ ```ruby
553
+ template = Gophish::Template.new(
554
+ name: "Invoice Template",
555
+ subject: "Invoice #{{.RId}}",
556
+ html: "<p>Please find your invoice attached.</p>"
557
+ )
558
+
559
+ # Add PDF attachment
560
+ pdf_content = File.read("invoice.pdf")
561
+ template.add_attachment(pdf_content, "application/pdf", "invoice.pdf")
562
+
563
+ # Add image attachment
564
+ image_content = File.read("logo.png")
565
+ template.add_attachment(image_content, "image/png", "logo.png")
566
+
567
+ template.save
568
+ ```
569
+
570
+ ##### Import from Email
571
+
572
+ ```ruby
573
+ # Import existing email
574
+ email_content = File.read("phishing_template.eml")
575
+ imported_data = Gophish::Template.import_email(
576
+ email_content,
577
+ convert_links: true # Convert links for tracking
578
+ )
579
+
580
+ template = Gophish::Template.new(imported_data)
581
+ template.name = "Imported Phishing Template"
582
+ template.save
583
+ ```
584
+
585
+ ##### Update Template
586
+
587
+ ```ruby
588
+ template = Gophish::Template.find(1)
589
+ template.subject = "Updated Subject"
590
+ template.html = "<h1>Updated Content</h1>"
591
+
592
+ # Add new attachment
593
+ template.add_attachment(File.read("new_doc.pdf"), "application/pdf", "new_doc.pdf")
594
+
595
+ # Remove old attachment
596
+ template.remove_attachment("old_doc.pdf")
597
+
598
+ template.save
599
+ ```
600
+
601
+ ##### Template Validation
602
+
603
+ ```ruby
604
+ # Invalid template (no content)
605
+ template = Gophish::Template.new(name: "Test Template")
606
+
607
+ unless template.valid?
608
+ puts "Validation errors:"
609
+ template.errors.full_messages.each { |msg| puts " - #{msg}" }
610
+ # => ["Need to specify at least plaintext or HTML content"]
611
+ end
612
+
613
+ # Invalid attachment
614
+ template = Gophish::Template.new(
615
+ name: "Test",
616
+ html: "<p>Test</p>",
617
+ attachments: [{ name: "file.pdf" }] # Missing content and type
618
+ )
619
+
620
+ unless template.valid?
621
+ puts template.errors.full_messages
622
+ # => ["Attachments item at index 0 must have a content",
623
+ # "Attachments item at index 0 must have a type"]
624
+ end
625
+ ```
626
+
379
627
  ## Error Handling
380
628
 
381
629
  ### Validation Errors
data/docs/EXAMPLES.md CHANGED
@@ -6,6 +6,7 @@ This document contains practical examples for common use cases with the Gophish
6
6
 
7
7
  - [Basic Operations](#basic-operations)
8
8
  - [CSV Operations](#csv-operations)
9
+ - [Template Operations](#template-operations)
9
10
  - [Error Handling](#error-handling)
10
11
  - [Advanced Scenarios](#advanced-scenarios)
11
12
  - [Production Examples](#production-examples)
@@ -252,6 +253,347 @@ end
252
253
  group = import_large_csv("large_employee_list.csv", "All Company Employees")
253
254
  ```
254
255
 
256
+ ## Template Operations
257
+
258
+ ### Basic Template Creation
259
+
260
+ ```ruby
261
+ # Simple text and HTML template
262
+ template = Gophish::Template.new(
263
+ name: "Security Awareness Training",
264
+ subject: "Important Security Update - Action Required",
265
+ html: <<~HTML,
266
+ <h1>Security Update Required</h1>
267
+ <p>Dear {{.FirstName}},</p>
268
+ <p>We have detected suspicious activity on your account. Please click <a href="{{.URL}}">here</a> to verify your account immediately.</p>
269
+ <p>This link will expire in 24 hours.</p>
270
+ <p>Best regards,<br>IT Security Team</p>
271
+ HTML
272
+ text: <<~TEXT
273
+ Security Update Required
274
+
275
+ Dear {{.FirstName}},
276
+
277
+ We have detected suspicious activity on your account. Please visit {{.URL}} to verify your account immediately.
278
+
279
+ This link will expire in 24 hours.
280
+
281
+ Best regards,
282
+ IT Security Team
283
+ TEXT
284
+ )
285
+
286
+ if template.save
287
+ puts "✓ Template '#{template.name}' created with ID: #{template.id}"
288
+ else
289
+ puts "✗ Failed to create template: #{template.errors.full_messages}"
290
+ end
291
+ ```
292
+
293
+ ### Template with Attachments
294
+
295
+ ```ruby
296
+ # Create template with multiple attachments
297
+ template = Gophish::Template.new(
298
+ name: "Invoice Phishing Template",
299
+ subject: "Invoice #{{.RId}} - Payment Due",
300
+ html: "<h1>Invoice Attached</h1><p>Dear {{.FirstName}},</p><p>Please find your invoice attached for immediate payment.</p>"
301
+ )
302
+
303
+ # Add PDF invoice attachment
304
+ pdf_content = File.read("sample_invoice.pdf")
305
+ template.add_attachment(pdf_content, "application/pdf", "invoice_#{Time.now.strftime('%Y%m%d')}.pdf")
306
+
307
+ # Add company logo
308
+ logo_content = File.read("company_logo.png")
309
+ template.add_attachment(logo_content, "image/png", "logo.png")
310
+
311
+ # Add fake Excel spreadsheet
312
+ excel_content = File.read("expense_report.xlsx")
313
+ template.add_attachment(excel_content, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "Q4_expenses.xlsx")
314
+
315
+ puts "Template has #{template.attachment_count} attachments"
316
+
317
+ if template.save
318
+ puts "✓ Template with attachments created successfully"
319
+ end
320
+ ```
321
+
322
+ ### Email Import from .EML Files
323
+
324
+ ```ruby
325
+ # Import existing phishing email
326
+ def import_phishing_template(eml_file_path, template_name)
327
+ unless File.exist?(eml_file_path)
328
+ puts "✗ EML file not found: #{eml_file_path}"
329
+ return nil
330
+ end
331
+
332
+ email_content = File.read(eml_file_path)
333
+
334
+ # Import with link conversion for tracking
335
+ begin
336
+ imported_data = Gophish::Template.import_email(email_content, convert_links: true)
337
+ rescue StandardError => e
338
+ puts "✗ Email import failed: #{e.message}"
339
+ return nil
340
+ end
341
+
342
+ # Create template from imported data
343
+ template = Gophish::Template.new(imported_data)
344
+ template.name = template_name
345
+
346
+ if template.valid?
347
+ if template.save
348
+ puts "✓ Successfully imported template '#{template.name}'"
349
+ puts " Subject: #{template.subject}"
350
+ puts " Has HTML: #{!template.html.nil?}"
351
+ puts " Has Text: #{!template.text.nil?}"
352
+ puts " Attachments: #{template.attachment_count}"
353
+ return template
354
+ else
355
+ puts "✗ Save failed: #{template.errors.full_messages}"
356
+ end
357
+ else
358
+ puts "✗ Validation failed: #{template.errors.full_messages}"
359
+ end
360
+
361
+ nil
362
+ end
363
+
364
+ # Usage
365
+ template = import_phishing_template("phishing_email.eml", "Imported Phishing Template")
366
+ ```
367
+
368
+ ### Template Management Operations
369
+
370
+ ```ruby
371
+ # List all existing templates
372
+ def list_templates
373
+ templates = Gophish::Template.all
374
+ puts "Found #{templates.length} templates:"
375
+
376
+ templates.each do |template|
377
+ attachment_info = template.has_attachments? ? " (#{template.attachment_count} attachments)" : ""
378
+ puts " #{template.id}: #{template.name}#{attachment_info}"
379
+ puts " Subject: #{template.subject}" if template.subject
380
+ puts " Modified: #{template.modified_date}" if template.modified_date
381
+ end
382
+ end
383
+
384
+ # Update existing template
385
+ def update_template(template_id, new_subject = nil, new_html = nil)
386
+ begin
387
+ template = Gophish::Template.find(template_id)
388
+ rescue StandardError
389
+ puts "✗ Template #{template_id} not found"
390
+ return false
391
+ end
392
+
393
+ puts "Updating template '#{template.name}'"
394
+
395
+ template.subject = new_subject if new_subject
396
+ template.html = new_html if new_html
397
+
398
+ if template.save
399
+ puts "✓ Template updated successfully"
400
+ true
401
+ else
402
+ puts "✗ Update failed: #{template.errors.full_messages}"
403
+ false
404
+ end
405
+ end
406
+
407
+ # Clone template with modifications
408
+ def clone_template(original_id, new_name, modifications = {})
409
+ begin
410
+ original = Gophish::Template.find(original_id)
411
+ rescue StandardError
412
+ puts "✗ Original template #{original_id} not found"
413
+ return nil
414
+ end
415
+
416
+ # Create new template with same content
417
+ new_template = Gophish::Template.new(
418
+ name: new_name,
419
+ subject: original.subject,
420
+ html: original.html,
421
+ text: original.text
422
+ )
423
+
424
+ # Apply modifications
425
+ modifications.each do |field, value|
426
+ new_template.send("#{field}=", value) if new_template.respond_to?("#{field}=")
427
+ end
428
+
429
+ # Copy attachments
430
+ if original.has_attachments?
431
+ original.attachments.each do |attachment|
432
+ new_template.attachments << attachment.dup
433
+ end
434
+ end
435
+
436
+ if new_template.save
437
+ puts "✓ Template cloned as '#{new_name}' (ID: #{new_template.id})"
438
+ new_template
439
+ else
440
+ puts "✗ Clone failed: #{new_template.errors.full_messages}"
441
+ nil
442
+ end
443
+ end
444
+
445
+ # Usage examples
446
+ list_templates
447
+ update_template(1, "Updated Subject Line", "<h1>Updated HTML content</h1>")
448
+ clone_template(1, "Modified Version", { subject: "Modified Subject" })
449
+ ```
450
+
451
+ ### Template Validation and Error Handling
452
+
453
+ ```ruby
454
+ # Comprehensive template validation
455
+ def validate_template_thoroughly(template)
456
+ puts "Validating template '#{template.name}'"
457
+
458
+ # Basic validation
459
+ unless template.valid?
460
+ puts "✗ Basic validation failed:"
461
+ template.errors.full_messages.each { |error| puts " - #{error}" }
462
+ return false
463
+ end
464
+
465
+ # Content validation
466
+ has_html = !template.html.nil? && !template.html.strip.empty?
467
+ has_text = !template.text.nil? && !template.text.strip.empty?
468
+
469
+ unless has_html || has_text
470
+ puts "✗ Template has no content (neither HTML nor text)"
471
+ return false
472
+ end
473
+
474
+ # Gophish template variable validation
475
+ content = "#{template.html} #{template.text} #{template.subject}"
476
+
477
+ # Check for common Gophish template variables
478
+ variables_found = content.scan(/\{\{\.(\w+)\}\}/).flatten.uniq
479
+ puts " Found template variables: #{variables_found.join(', ')}" if variables_found.any?
480
+
481
+ # Warn about missing tracking URL
482
+ unless content.include?('{{.URL}}')
483
+ puts " ⚠ Warning: No {{.URL}} tracking variable found"
484
+ end
485
+
486
+ # Attachment validation
487
+ if template.has_attachments?
488
+ puts " Validating #{template.attachment_count} attachments:"
489
+ template.attachments.each_with_index do |attachment, index|
490
+ name = attachment[:name] || attachment['name']
491
+ type = attachment[:type] || attachment['type']
492
+ content = attachment[:content] || attachment['content']
493
+
494
+ puts " #{index + 1}. #{name} (#{type})"
495
+
496
+ if content.nil? || content.empty?
497
+ puts " ✗ Missing content"
498
+ return false
499
+ end
500
+
501
+ # Validate Base64 encoding
502
+ begin
503
+ Base64.strict_decode64(content)
504
+ puts " ✓ Valid Base64 encoding"
505
+ rescue ArgumentError
506
+ puts " ✗ Invalid Base64 encoding"
507
+ return false
508
+ end
509
+ end
510
+ end
511
+
512
+ puts "✓ Template validation passed"
513
+ true
514
+ end
515
+
516
+ # Test various template scenarios
517
+ def test_template_scenarios
518
+ # Valid template
519
+ valid_template = Gophish::Template.new(
520
+ name: "Valid Template",
521
+ subject: "Test {{.FirstName}}",
522
+ html: "<p>Click {{.URL}} to continue</p>",
523
+ text: "Visit {{.URL}} to continue"
524
+ )
525
+ validate_template_thoroughly(valid_template)
526
+
527
+ # Invalid template (no content)
528
+ invalid_template = Gophish::Template.new(
529
+ name: "Invalid Template",
530
+ subject: "Test"
531
+ )
532
+ validate_template_thoroughly(invalid_template)
533
+
534
+ # Template with attachment
535
+ template_with_attachment = Gophish::Template.new(
536
+ name: "Attachment Template",
537
+ html: "<p>See attachment</p>"
538
+ )
539
+ template_with_attachment.add_attachment("Hello World", "text/plain", "test.txt")
540
+ validate_template_thoroughly(template_with_attachment)
541
+ end
542
+
543
+ test_template_scenarios
544
+ ```
545
+
546
+ ### Bulk Template Operations
547
+
548
+ ```ruby
549
+ # Create multiple templates from a configuration
550
+ def create_template_suite(campaign_name)
551
+ templates = [
552
+ {
553
+ name: "#{campaign_name} - Initial Email",
554
+ subject: "Important Account Update Required",
555
+ html: "<h1>Account Update</h1><p>Dear {{.FirstName}}, please update your account by clicking <a href='{{.URL}}'>here</a>.</p>",
556
+ text: "Dear {{.FirstName}}, please update your account by visiting {{.URL}}"
557
+ },
558
+ {
559
+ name: "#{campaign_name} - Follow-up",
560
+ subject: "URGENT: Account Suspension Notice",
561
+ html: "<h1 style='color: red;'>URGENT</h1><p>Your account will be suspended in 24 hours. Verify immediately: {{.URL}}</p>",
562
+ text: "URGENT: Your account will be suspended in 24 hours. Verify at {{.URL}}"
563
+ },
564
+ {
565
+ name: "#{campaign_name} - Final Warning",
566
+ subject: "Final Warning - Account Closure",
567
+ html: "<h1>Final Warning</h1><p>This is your last chance to save your account: {{.URL}}</p>",
568
+ text: "Final Warning: Last chance to save your account at {{.URL}}"
569
+ }
570
+ ]
571
+
572
+ created_templates = []
573
+
574
+ templates.each_with_index do |template_data, index|
575
+ puts "Creating template #{index + 1}/#{templates.length}: #{template_data[:name]}"
576
+
577
+ template = Gophish::Template.new(template_data)
578
+
579
+ if template.save
580
+ puts " ✓ Created with ID: #{template.id}"
581
+ created_templates << template
582
+ else
583
+ puts " ✗ Failed: #{template.errors.full_messages}"
584
+ end
585
+ end
586
+
587
+ puts "\nTemplate suite '#{campaign_name}' creation completed"
588
+ puts "Successfully created: #{created_templates.length}/#{templates.length} templates"
589
+
590
+ created_templates
591
+ end
592
+
593
+ # Usage
594
+ suite = create_template_suite("Q4 Security Training")
595
+ ```
596
+
255
597
  ## Error Handling
256
598
 
257
599
  ### Comprehensive Error Handling
@@ -84,6 +84,27 @@ else
84
84
  end
85
85
  ```
86
86
 
87
+ ### 4. Create Your First Template
88
+
89
+ Templates define the email content for your phishing campaigns:
90
+
91
+ ```ruby
92
+ # Create a basic email template
93
+ template = Gophish::Template.new(
94
+ name: "Security Awareness Test",
95
+ subject: "Important Security Update Required",
96
+ html: "<h1>Security Update</h1><p>Please click <a href='{{.URL}}'>here</a> to update your password.</p>",
97
+ text: "Security Update\n\nPlease visit {{.URL}} to update your password."
98
+ )
99
+
100
+ if template.save
101
+ puts "✓ Template created successfully with ID: #{template.id}"
102
+ else
103
+ puts "✗ Failed to create template:"
104
+ template.errors.full_messages.each { |error| puts " - #{error}" }
105
+ end
106
+ ```
107
+
87
108
  ## Common Workflows
88
109
 
89
110
  ### Importing Targets from CSV
@@ -125,6 +146,68 @@ else
125
146
  end
126
147
  ```
127
148
 
149
+ ### Working with Templates
150
+
151
+ #### Creating Templates with Attachments
152
+
153
+ ```ruby
154
+ # Create template with file attachments
155
+ template = Gophish::Template.new(
156
+ name: "Invoice Template",
157
+ subject: "Your Invoice #{{.RId}}",
158
+ html: "<p>Dear {{.FirstName}},</p><p>Please find your invoice attached.</p>"
159
+ )
160
+
161
+ # Add PDF attachment
162
+ pdf_content = File.read("sample_invoice.pdf")
163
+ template.add_attachment(pdf_content, "application/pdf", "invoice.pdf")
164
+
165
+ # Check attachments
166
+ puts "Template has #{template.attachment_count} attachments" if template.has_attachments?
167
+
168
+ template.save
169
+ ```
170
+
171
+ #### Importing Email Templates
172
+
173
+ ```ruby
174
+ # Import an existing email (.eml file)
175
+ email_content = File.read("phishing_template.eml")
176
+
177
+ imported_data = Gophish::Template.import_email(
178
+ email_content,
179
+ convert_links: true # Convert links for Gophish tracking
180
+ )
181
+
182
+ template = Gophish::Template.new(imported_data)
183
+ template.name = "Imported Email Template"
184
+ template.save
185
+
186
+ puts "Imported template: #{template.name}"
187
+ ```
188
+
189
+ #### Managing Existing Templates
190
+
191
+ ```ruby
192
+ # List all templates
193
+ puts "Existing templates:"
194
+ Gophish::Template.all.each do |template|
195
+ attachment_info = template.has_attachments? ? " (#{template.attachment_count} attachments)" : ""
196
+ puts " #{template.id}: #{template.name}#{attachment_info}"
197
+ end
198
+
199
+ # Update a template
200
+ template = Gophish::Template.find(1)
201
+ template.subject = "Updated Subject Line"
202
+ template.html = "<h1>Updated Content</h1><p>New message content here.</p>"
203
+
204
+ # Add or remove attachments
205
+ template.add_attachment(File.read("new_file.pdf"), "application/pdf", "new_file.pdf")
206
+ template.remove_attachment("old_file.pdf")
207
+
208
+ template.save
209
+ ```
210
+
128
211
  ### Managing Existing Groups
129
212
 
130
213
  ```ruby
data/lib/gophish/base.rb CHANGED
@@ -13,12 +13,13 @@ module Gophish
13
13
  include ActiveModel::Model
14
14
  include ActiveModel::Attributes
15
15
  include ActiveModel::Validations
16
+ include ActiveModel::Dirty
16
17
  include ActiveRecord::Callbacks
17
18
 
18
19
  def initialize(attributes = {})
19
20
  @persisted = false
20
- @changed_attributes = {}
21
21
  super(attributes)
22
+ clear_changes_information
22
23
  end
23
24
 
24
25
  def self.configuration
@@ -125,7 +126,7 @@ module Gophish
125
126
  send "#{key}=", value if respond_to? "#{key}="
126
127
  end
127
128
  @persisted = true
128
- @changed_attributes.clear
129
+ clear_changes_information
129
130
  end
130
131
 
131
132
  def handle_error_response(response)
@@ -145,9 +146,9 @@ module Gophish
145
146
 
146
147
  def update_record
147
148
  return false if id.nil?
148
- return true if @changed_attributes.empty?
149
+ return true unless changed?
149
150
 
150
- response = self.class.put "#{self.class.resource_path}/#{id}/", request_options(body_for_update)
151
+ response = self.class.put "#{self.class.resource_path}/#{id}", request_options(body_for_update)
151
152
  return handle_error_response response unless response.success?
152
153
 
153
154
  update_attributes_from_response response.parsed_response
@@ -177,29 +178,10 @@ module Gophish
177
178
  end
178
179
 
179
180
  def body_for_update
180
- body_for_create
181
- end
182
-
183
- def attribute_changed?(attribute)
184
- @changed_attributes.key? attribute.to_s
185
- end
186
-
187
- def changed_attributes
188
- @changed_attributes.keys
189
- end
190
-
191
- def attribute_was(attribute)
192
- @changed_attributes[attribute.to_s]
181
+ body_for_create.merge id:
193
182
  end
194
183
 
195
184
  def []=(attribute, value)
196
- attribute_str = attribute.to_s
197
- current_value = send attribute if respond_to? attribute
198
-
199
- unless current_value == value
200
- @changed_attributes[attribute_str] = current_value
201
- end
202
-
203
185
  send "#{attribute}=", value if respond_to? "#{attribute}="
204
186
  end
205
187
  end
data/lib/gophish/group.rb CHANGED
@@ -9,6 +9,8 @@ module Gophish
9
9
  attribute :modified_date, :string
10
10
  attribute :targets
11
11
 
12
+ define_attribute_methods :id, :name, :modified_date, :targets
13
+
12
14
  validates :name, presence: true
13
15
  validates :targets, presence: true
14
16
  validate :validate_targets_structure
@@ -0,0 +1,94 @@
1
+ require_relative 'base'
2
+ require 'active_support/core_ext/object/blank'
3
+ require 'base64'
4
+
5
+ module Gophish
6
+ class Template < Base
7
+ attribute :id, :integer
8
+ attribute :name, :string
9
+ attribute :subject, :string
10
+ attribute :text, :string
11
+ attribute :html, :string
12
+ attribute :modified_date, :string
13
+ attribute :attachments, default: -> { [] }
14
+
15
+ define_attribute_methods :id, :name, :subject, :text, :html, :modified_date, :attachments
16
+
17
+ validates :name, presence: true
18
+ validate :validate_content_presence
19
+ validate :validate_attachments_structure
20
+
21
+ def body_for_create
22
+ { name:, subject:, text:, html:, attachments: }
23
+ end
24
+
25
+ def self.import_email(content, convert_links: false)
26
+ options = build_import_options content, convert_links
27
+ response = post '/import/email', options
28
+ raise StandardError, 'Failed to import email' unless response.success?
29
+
30
+ response.parsed_response
31
+ end
32
+
33
+ def self.build_import_options(content, convert_links)
34
+ {
35
+ body: { content:, convert_links: }.to_json,
36
+ headers: { 'Content-Type' => 'application/json' }
37
+ }
38
+ end
39
+
40
+ def add_attachment(content, type, name)
41
+ encoded_content = encode_content content
42
+ attachments << { content: encoded_content, type:, name: }
43
+ attachments_will_change!
44
+ end
45
+
46
+ def remove_attachment(name)
47
+ original_size = attachments.size
48
+ attachments.reject! { |attachment| attachment[:name] == name || attachment['name'] == name }
49
+ attachments_will_change! if attachments.size != original_size
50
+ end
51
+
52
+ def has_attachments?
53
+ !attachments.empty?
54
+ end
55
+
56
+ def attachment_count
57
+ attachments.length
58
+ end
59
+
60
+ private
61
+
62
+ def encode_content(content)
63
+ content.is_a?(String) ? Base64.strict_encode64(content) : content
64
+ end
65
+
66
+ def validate_content_presence
67
+ return unless text.blank? && html.blank?
68
+
69
+ errors.add :base, 'Need to specify at least plaintext or HTML content'
70
+ end
71
+
72
+ def validate_attachments_structure
73
+ return if attachments.blank?
74
+ return errors.add :attachments, 'must be an array' unless attachments.is_a? Array
75
+
76
+ attachments.each_with_index { |attachment, index| validate_attachment attachment, index }
77
+ end
78
+
79
+ def validate_attachment(attachment, index)
80
+ return errors.add :attachments, "item at index #{index} must be a hash" unless attachment.is_a? Hash
81
+
82
+ validate_attachment_field attachment, index, :content
83
+ validate_attachment_field attachment, index, :type
84
+ validate_attachment_field attachment, index, :name
85
+ end
86
+
87
+ def validate_attachment_field(attachment, index, field)
88
+ value = attachment[field] || attachment[field.to_s]
89
+ return unless value.blank?
90
+
91
+ errors.add :attachments, "item at index #{index} must have a #{field}"
92
+ end
93
+ end
94
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gophish
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/gophish-ruby.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require_relative 'gophish/version'
2
2
  require_relative 'gophish/configuration'
3
- require_relative 'gophish/base'
4
3
  require_relative 'gophish/group'
4
+ require_relative 'gophish/template'
5
5
 
6
6
  module Gophish
7
7
  class << self
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gophish-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eli Sebastian Herrera Aguilar
@@ -105,6 +105,7 @@ files:
105
105
  - lib/gophish/base.rb
106
106
  - lib/gophish/configuration.rb
107
107
  - lib/gophish/group.rb
108
+ - lib/gophish/template.rb
108
109
  - lib/gophish/version.rb
109
110
  - sig/gophish/ruby.rbs
110
111
  homepage: https://github.com/EliSebastian/gopish-ruby