gophish-ruby 0.1.0 → 0.3.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.
@@ -7,6 +7,8 @@ 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)
11
+ - [Page Class](#page-class)
10
12
  - [Error Handling](#error-handling)
11
13
  - [Examples](#examples)
12
14
 
@@ -125,7 +127,7 @@ Create or update the resource on the server.
125
127
  **Side Effects:**
126
128
 
127
129
  - Sets `@persisted` to true on success
128
- - Clears `@changed_attributes` on success
130
+ - Clears change tracking information on success
129
131
  - Adds errors to `#errors` on failure
130
132
 
131
133
  **Example:**
@@ -207,7 +209,7 @@ group = Gophish::Group.new
207
209
  puts group.new_record? # => true
208
210
  ```
209
211
 
210
- ##### `#changed_attributes`
212
+ ##### `#changed`
211
213
 
212
214
  Get array of attribute names that have changed.
213
215
 
@@ -218,7 +220,23 @@ Get array of attribute names that have changed.
218
220
  ```ruby
219
221
  group = Gophish::Group.find(1)
220
222
  group.name = "New Name"
221
- puts group.changed_attributes # => ["name"]
223
+ puts group.changed # => ["name"]
224
+ ```
225
+
226
+ ##### `#changed?`
227
+
228
+ Check if any attributes have changed.
229
+
230
+ **Returns:** Boolean
231
+
232
+ **Example:**
233
+
234
+ ```ruby
235
+ group = Gophish::Group.find(1)
236
+ puts group.changed? # => false
237
+
238
+ group.name = "New Name"
239
+ puts group.changed? # => true
222
240
  ```
223
241
 
224
242
  ##### `#attribute_changed?(attr)`
@@ -236,8 +254,8 @@ Check if a specific attribute has changed.
236
254
  ```ruby
237
255
  group = Gophish::Group.find(1)
238
256
  group.name = "New Name"
257
+ puts group.name_changed? # => true (using dynamic method)
239
258
  puts group.attribute_changed?(:name) # => true
240
- puts group.attribute_changed?("targets") # => false
241
259
  ```
242
260
 
243
261
  ##### `#attribute_was(attr)`
@@ -256,6 +274,7 @@ Get the previous value of a changed attribute.
256
274
  group = Gophish::Group.find(1)
257
275
  original_name = group.name
258
276
  group.name = "New Name"
277
+ puts group.name_was # => original_name (using dynamic method)
259
278
  puts group.attribute_was(:name) # => original_name
260
279
  ```
261
280
 
@@ -376,6 +395,555 @@ group.import_csv(csv_content)
376
395
  group.save
377
396
  ```
378
397
 
398
+ ## Template Class
399
+
400
+ The `Gophish::Template` class represents an email template in Gophish.
401
+
402
+ ### Class: `Gophish::Template < Gophish::Base`
403
+
404
+ #### Attributes
405
+
406
+ | Attribute | Type | Required | Description |
407
+ |-----------|------|----------|-------------|
408
+ | `id` | Integer | No | Unique template identifier (set by server) |
409
+ | `name` | String | Yes | Template name |
410
+ | `subject` | String | No | Email subject line |
411
+ | `text` | String | No | Plain text email content |
412
+ | `html` | String | No | HTML email content |
413
+ | `modified_date` | String | No | Last modification timestamp (set by server) |
414
+ | `attachments` | Array | No | Array of attachment hashes |
415
+
416
+ #### Attachment Structure
417
+
418
+ Each attachment in the `attachments` array must have:
419
+
420
+ | Field | Type | Required | Description |
421
+ |-------|------|----------|-------------|
422
+ | `content` | String | Yes | Base64 encoded file content |
423
+ | `type` | String | Yes | MIME type (e.g., "application/pdf") |
424
+ | `name` | String | Yes | Filename |
425
+
426
+ #### Validations
427
+
428
+ - `name` must be present
429
+ - Must have either `text` or `html` content (or both)
430
+ - Each attachment must be a Hash with required fields (`content`, `type`, `name`)
431
+
432
+ #### Class Methods
433
+
434
+ ##### `.import_email(content, convert_links: false)`
435
+
436
+ Import email content and return template data.
437
+
438
+ **Parameters:**
439
+
440
+ - `content` (String) - Raw email content (.eml format)
441
+ - `convert_links` (Boolean) - Whether to convert links for Gophish tracking (default: false)
442
+
443
+ **Returns:** Hash of template attributes
444
+
445
+ **Raises:**
446
+
447
+ - `StandardError` if import fails
448
+
449
+ **Example:**
450
+
451
+ ```ruby
452
+ email_content = File.read("sample.eml")
453
+ template_data = Gophish::Template.import_email(email_content, convert_links: true)
454
+
455
+ template = Gophish::Template.new(template_data)
456
+ template.name = "Imported Template"
457
+ template.save
458
+ ```
459
+
460
+ #### Instance Methods
461
+
462
+ ##### `#add_attachment(content, type, name)`
463
+
464
+ Add an attachment to the template.
465
+
466
+ **Parameters:**
467
+
468
+ - `content` (String) - File content (will be Base64 encoded automatically)
469
+ - `type` (String) - MIME type
470
+ - `name` (String) - Filename
471
+
472
+ **Returns:** Void
473
+
474
+ **Side Effects:**
475
+
476
+ - Adds attachment to `attachments` array
477
+ - Marks `attachments` attribute as changed
478
+
479
+ **Example:**
480
+
481
+ ```ruby
482
+ template = Gophish::Template.new(name: "Test", html: "<p>Test</p>")
483
+ file_content = File.read("document.pdf")
484
+ template.add_attachment(file_content, "application/pdf", "document.pdf")
485
+ ```
486
+
487
+ ##### `#remove_attachment(name)`
488
+
489
+ Remove an attachment by filename.
490
+
491
+ **Parameters:**
492
+
493
+ - `name` (String) - Filename of attachment to remove
494
+
495
+ **Returns:** Void
496
+
497
+ **Side Effects:**
498
+
499
+ - Removes matching attachment(s) from `attachments` array
500
+ - Marks `attachments` attribute as changed if any were removed
501
+
502
+ **Example:**
503
+
504
+ ```ruby
505
+ template.remove_attachment("document.pdf")
506
+ ```
507
+
508
+ ##### `#has_attachments?`
509
+
510
+ Check if template has any attachments.
511
+
512
+ **Returns:** Boolean
513
+
514
+ **Example:**
515
+
516
+ ```ruby
517
+ if template.has_attachments?
518
+ puts "Template has #{template.attachment_count} attachments"
519
+ end
520
+ ```
521
+
522
+ ##### `#attachment_count`
523
+
524
+ Get the number of attachments.
525
+
526
+ **Returns:** Integer
527
+
528
+ **Example:**
529
+
530
+ ```ruby
531
+ puts "Attachments: #{template.attachment_count}"
532
+ ```
533
+
534
+ #### Usage Examples
535
+
536
+ ##### Create a Template
537
+
538
+ ```ruby
539
+ template = Gophish::Template.new(
540
+ name: "Phishing Test Template",
541
+ subject: "Important Security Update",
542
+ html: "<h1>Security Update Required</h1><p>Please click <a href='{{.URL}}'>here</a> to update.</p>",
543
+ text: "Security Update Required\n\nPlease visit {{.URL}} to update your credentials."
544
+ )
545
+
546
+ if template.save
547
+ puts "Template created with ID: #{template.id}"
548
+ end
549
+ ```
550
+
551
+ ##### Template with Attachments
552
+
553
+ ```ruby
554
+ template = Gophish::Template.new(
555
+ name: "Invoice Template",
556
+ subject: "Invoice #{{.RId}}",
557
+ html: "<p>Please find your invoice attached.</p>"
558
+ )
559
+
560
+ # Add PDF attachment
561
+ pdf_content = File.read("invoice.pdf")
562
+ template.add_attachment(pdf_content, "application/pdf", "invoice.pdf")
563
+
564
+ # Add image attachment
565
+ image_content = File.read("logo.png")
566
+ template.add_attachment(image_content, "image/png", "logo.png")
567
+
568
+ template.save
569
+ ```
570
+
571
+ ##### Import from Email
572
+
573
+ ```ruby
574
+ # Import existing email
575
+ email_content = File.read("phishing_template.eml")
576
+ imported_data = Gophish::Template.import_email(
577
+ email_content,
578
+ convert_links: true # Convert links for tracking
579
+ )
580
+
581
+ template = Gophish::Template.new(imported_data)
582
+ template.name = "Imported Phishing Template"
583
+ template.save
584
+ ```
585
+
586
+ ##### Update Template
587
+
588
+ ```ruby
589
+ template = Gophish::Template.find(1)
590
+ template.subject = "Updated Subject"
591
+ template.html = "<h1>Updated Content</h1>"
592
+
593
+ # Add new attachment
594
+ template.add_attachment(File.read("new_doc.pdf"), "application/pdf", "new_doc.pdf")
595
+
596
+ # Remove old attachment
597
+ template.remove_attachment("old_doc.pdf")
598
+
599
+ template.save
600
+ ```
601
+
602
+ ##### Template Validation
603
+
604
+ ```ruby
605
+ # Invalid template (no content)
606
+ template = Gophish::Template.new(name: "Test Template")
607
+
608
+ unless template.valid?
609
+ puts "Validation errors:"
610
+ template.errors.full_messages.each { |msg| puts " - #{msg}" }
611
+ # => ["Need to specify at least plaintext or HTML content"]
612
+ end
613
+
614
+ # Invalid attachment
615
+ template = Gophish::Template.new(
616
+ name: "Test",
617
+ html: "<p>Test</p>",
618
+ attachments: [{ name: "file.pdf" }] # Missing content and type
619
+ )
620
+
621
+ unless template.valid?
622
+ puts template.errors.full_messages
623
+ # => ["Attachments item at index 0 must have a content",
624
+ # "Attachments item at index 0 must have a type"]
625
+ end
626
+ ```
627
+
628
+ ## Page Class
629
+
630
+ The `Gophish::Page` class represents a landing page in Gophish campaigns.
631
+
632
+ ### Class: `Gophish::Page < Gophish::Base`
633
+
634
+ #### Attributes
635
+
636
+ | Attribute | Type | Required | Description |
637
+ |-----------|------|----------|-------------|
638
+ | `id` | Integer | No | Unique page identifier (set by server) |
639
+ | `name` | String | Yes | Page name |
640
+ | `html` | String | Yes | HTML content of the page |
641
+ | `capture_credentials` | Boolean | No | Whether to capture credentials (default: false) |
642
+ | `capture_passwords` | Boolean | No | Whether to capture passwords (default: false) |
643
+ | `redirect_url` | String | No | URL to redirect users after form submission |
644
+ | `modified_date` | String | No | Last modification timestamp (set by server) |
645
+
646
+ #### Validations
647
+
648
+ - `name` must be present
649
+ - `html` must be present
650
+
651
+ #### Class Methods
652
+
653
+ ##### `.import_site(url, include_resources: false)`
654
+
655
+ Import a website as a landing page template.
656
+
657
+ **Parameters:**
658
+
659
+ - `url` (String) - URL of the website to import
660
+ - `include_resources` (Boolean) - Whether to include CSS, JS, and images (default: false)
661
+
662
+ **Returns:** Hash of page attributes
663
+
664
+ **Raises:**
665
+
666
+ - `StandardError` if import fails
667
+
668
+ **Example:**
669
+
670
+ ```ruby
671
+ begin
672
+ page_data = Gophish::Page.import_site(
673
+ "https://login.microsoft.com",
674
+ include_resources: true
675
+ )
676
+
677
+ page = Gophish::Page.new(page_data)
678
+ page.name = "Imported Microsoft Login"
679
+ page.capture_credentials = true
680
+ page.save
681
+ rescue StandardError => e
682
+ puts "Import failed: #{e.message}"
683
+ end
684
+ ```
685
+
686
+ #### Instance Methods
687
+
688
+ ##### `#captures_credentials?`
689
+
690
+ Check if page is configured to capture credentials.
691
+
692
+ **Returns:** Boolean
693
+
694
+ **Example:**
695
+
696
+ ```ruby
697
+ page = Gophish::Page.new(capture_credentials: true)
698
+ puts page.captures_credentials? # => true
699
+ ```
700
+
701
+ ##### `#captures_passwords?`
702
+
703
+ Check if page is configured to capture passwords.
704
+
705
+ **Returns:** Boolean
706
+
707
+ **Example:**
708
+
709
+ ```ruby
710
+ page = Gophish::Page.new(capture_passwords: true)
711
+ puts page.captures_passwords? # => true
712
+ ```
713
+
714
+ ##### `#has_redirect?`
715
+
716
+ Check if page has a redirect URL configured.
717
+
718
+ **Returns:** Boolean
719
+
720
+ **Example:**
721
+
722
+ ```ruby
723
+ page = Gophish::Page.new(redirect_url: "https://example.com")
724
+ puts page.has_redirect? # => true
725
+
726
+ page = Gophish::Page.new
727
+ puts page.has_redirect? # => false
728
+ ```
729
+
730
+ #### Usage Examples
731
+
732
+ ##### Create a Basic Landing Page
733
+
734
+ ```ruby
735
+ page = Gophish::Page.new(
736
+ name: "Microsoft Login Clone",
737
+ html: <<~HTML
738
+ <!DOCTYPE html>
739
+ <html>
740
+ <head>
741
+ <title>Microsoft Account</title>
742
+ <style>
743
+ body {
744
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
745
+ background-color: #f5f5f5;
746
+ margin: 0;
747
+ padding: 40px;
748
+ }
749
+ .login-form {
750
+ max-width: 400px;
751
+ margin: 0 auto;
752
+ background: white;
753
+ padding: 40px;
754
+ border-radius: 8px;
755
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
756
+ }
757
+ .form-group { margin-bottom: 20px; }
758
+ input {
759
+ width: 100%;
760
+ padding: 12px;
761
+ border: 1px solid #ddd;
762
+ border-radius: 4px;
763
+ font-size: 14px;
764
+ }
765
+ button {
766
+ width: 100%;
767
+ padding: 12px;
768
+ background: #0078d4;
769
+ color: white;
770
+ border: none;
771
+ border-radius: 4px;
772
+ font-size: 14px;
773
+ cursor: pointer;
774
+ }
775
+ button:hover { background: #106ebe; }
776
+ </style>
777
+ </head>
778
+ <body>
779
+ <div class="login-form">
780
+ <h2>Sign in</h2>
781
+ <form method="post">
782
+ <div class="form-group">
783
+ <input type="email" name="username" placeholder="Email" required>
784
+ </div>
785
+ <div class="form-group">
786
+ <input type="password" name="password" placeholder="Password" required>
787
+ </div>
788
+ <button type="submit">Sign in</button>
789
+ </form>
790
+ </div>
791
+ </body>
792
+ </html>
793
+ HTML
794
+ )
795
+
796
+ if page.save
797
+ puts "Landing page created with ID: #{page.id}"
798
+ end
799
+ ```
800
+
801
+ ##### Create Page with Credential Capture
802
+
803
+ ```ruby
804
+ page = Gophish::Page.new(
805
+ name: "Banking Portal - Credential Capture",
806
+ html: <<~HTML
807
+ <html>
808
+ <head>
809
+ <title>Secure Banking Portal</title>
810
+ <style>
811
+ body { font-family: Arial, sans-serif; background: #1e3d59; color: white; }
812
+ .container { max-width: 400px; margin: 100px auto; padding: 40px; background: white; color: black; border-radius: 10px; }
813
+ input { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ccc; }
814
+ button { width: 100%; padding: 12px; background: #1e3d59; color: white; border: none; border-radius: 5px; }
815
+ </style>
816
+ </head>
817
+ <body>
818
+ <div class="container">
819
+ <h2>Secure Login</h2>
820
+ <form method="post">
821
+ <input type="text" name="username" placeholder="Username" required>
822
+ <input type="password" name="password" placeholder="Password" required>
823
+ <button type="submit">Access Account</button>
824
+ </form>
825
+ </div>
826
+ </body>
827
+ </html>
828
+ HTML,
829
+ capture_credentials: true,
830
+ capture_passwords: true,
831
+ redirect_url: "https://www.realbank.com/login"
832
+ )
833
+
834
+ puts "Page captures credentials: #{page.captures_credentials?}"
835
+ puts "Page captures passwords: #{page.captures_passwords?}"
836
+ puts "Page has redirect: #{page.has_redirect?}"
837
+
838
+ page.save
839
+ ```
840
+
841
+ ##### Import Website as Landing Page
842
+
843
+ ```ruby
844
+ # Import a real website
845
+ begin
846
+ imported_data = Gophish::Page.import_site(
847
+ "https://accounts.google.com/signin",
848
+ include_resources: true # Include CSS, JS, images
849
+ )
850
+
851
+ page = Gophish::Page.new(imported_data)
852
+ page.name = "Google Login Clone"
853
+ page.capture_credentials = true
854
+
855
+ if page.save
856
+ puts "Successfully imported Google login page"
857
+ puts "Page ID: #{page.id}"
858
+ end
859
+
860
+ rescue StandardError => e
861
+ puts "Failed to import site: #{e.message}"
862
+
863
+ # Fallback to manual creation
864
+ page = Gophish::Page.new(
865
+ name: "Manual Google Login Clone",
866
+ html: "<html><body><h1>Google</h1><form method='post'><input name='email' type='email' placeholder='Email'><input name='password' type='password' placeholder='Password'><button type='submit'>Sign in</button></form></body></html>",
867
+ capture_credentials: true
868
+ )
869
+ page.save
870
+ end
871
+ ```
872
+
873
+ ##### Update Existing Page
874
+
875
+ ```ruby
876
+ page = Gophish::Page.find(1)
877
+
878
+ # Update content
879
+ page.html = page.html.gsub("Sign in", "Login")
880
+
881
+ # Enable credential capture
882
+ page.capture_credentials = true
883
+ page.capture_passwords = true
884
+
885
+ # Set redirect URL
886
+ page.redirect_url = "https://legitimate-site.com"
887
+
888
+ if page.save
889
+ puts "Page updated successfully"
890
+ puts "Now captures credentials: #{page.captures_credentials?}"
891
+ end
892
+ ```
893
+
894
+ ##### Page Validation
895
+
896
+ ```ruby
897
+ # Invalid page (missing required fields)
898
+ page = Gophish::Page.new
899
+
900
+ unless page.valid?
901
+ puts "Validation errors:"
902
+ page.errors.full_messages.each { |msg| puts " - #{msg}" }
903
+ # => ["Name can't be blank", "Html can't be blank"]
904
+ end
905
+
906
+ # Valid page
907
+ page = Gophish::Page.new(
908
+ name: "Valid Page",
909
+ html: "<html><body>Content</body></html>"
910
+ )
911
+
912
+ puts page.valid? # => true
913
+ ```
914
+
915
+ ##### Checking Page Configuration
916
+
917
+ ```ruby
918
+ page = Gophish::Page.find(1)
919
+
920
+ # Check capabilities
921
+ if page.captures_credentials?
922
+ puts "⚠️ This page will capture user credentials"
923
+ end
924
+
925
+ if page.captures_passwords?
926
+ puts "🔐 This page will capture passwords in plain text"
927
+ end
928
+
929
+ if page.has_redirect?
930
+ puts "🔄 Users will be redirected to: #{page.redirect_url}"
931
+ else
932
+ puts "🛑 Users will see a generic success message"
933
+ end
934
+ ```
935
+
936
+ ##### Delete Page
937
+
938
+ ```ruby
939
+ page = Gophish::Page.find(1)
940
+
941
+ if page.destroy
942
+ puts "Page deleted successfully"
943
+ puts "Page frozen: #{page.frozen?}" # => true
944
+ end
945
+ ```
946
+
379
947
  ## Error Handling
380
948
 
381
949
  ### Validation Errors