sift 4.5.0 → 4.6.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: 71be696ff04ffb1503d22a724241e5087809d2ffa2ab29f08666939ecd6bb98b
4
- data.tar.gz: e2e841d8259fbea8c247b1b2bd3cdf208bd53ded613a765a75f595714a2e1e8d
3
+ metadata.gz: f52238626726ed4acc2b903b57bc393b46b2e8df162a021abd0b94d084631acc
4
+ data.tar.gz: 4576e4a7bb68d34b9687a8cea83d7e767cb9d10aacbf354d6dcf6167cd0de89b
5
5
  SHA512:
6
- metadata.gz: 9b578f6bccf6323d4ef53947cea5a517678920d882439da1d1df262cf5eb39e45064aab3a1dee702da01f41f29a522df0c5e7b850c68e1ac302fa0f7d9830e29
7
- data.tar.gz: 3c4565ffc5f98626ac67a07b378adfd80aaef787e47ffc50d84780da4b62788bdfb43e1b2e4ed90ca59cb4ee0fe23ec727dad8d1c0dd9413c137d7ec1de95510
6
+ metadata.gz: 3b7c07547207731c7592c0a08f229adde8400511855ad54ed7ae86de0b891acbb7b4fa91b7666df2e4927a7f9483898554daa8658f28ed9d5e0a49875139a011
7
+ data.tar.gz: 3d3fd96f0ed512504cdd4f52f353ef9264ee74395b1c1af58c6d583c17114e3cdd971ec577397d28339ceea68e0d69c495e817bdb42dc16aac61a7b6e348f7a5
@@ -0,0 +1,49 @@
1
+ name: Ruby-ci
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+ branches:
9
+ - master
10
+
11
+ env:
12
+ ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }}
13
+ API_KEY: ${{ secrets.API_KEY }}
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ build:
20
+ runs-on: ubuntu-latest
21
+ strategy:
22
+ matrix:
23
+ ruby-version: ['2.7', '3.1', '3.2', '3.3']
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ - name: Set up Ruby
27
+ uses: ruby/setup-ruby@v1
28
+ with:
29
+ ruby-version: ${{ matrix.ruby-version }}
30
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
31
+ - name: Run tests
32
+ run: bundle exec rspec --format RspecJunitFormatter --out test_results/rspec.xml --format progress
33
+
34
+ run_integration_tests:
35
+ runs-on: ubuntu-latest
36
+ if: ${{ github.ref == 'refs/heads/master' }}
37
+ strategy:
38
+ matrix:
39
+ ruby-version: ['2.7', '3.1', '3.2', '3.3']
40
+ steps:
41
+ - uses: actions/checkout@v4
42
+ - name: Set up Ruby
43
+ uses: ruby/setup-ruby@v1
44
+ with:
45
+ ruby-version: ${{ matrix.ruby-version }}
46
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
47
+ - name: Run tests
48
+ run: |
49
+ bundle exec ruby test_integration_app/main.rb
data/HISTORY CHANGED
@@ -1,3 +1,12 @@
1
+ === 4.6.0 2026-02-10
2
+ - Bump the minimum version of httparty to 0.23.3 to ensure protection against CVE-2025-68696
3
+ - Refactor Client to use dedicated internal HTTP clients for different API endpoints
4
+ - Fix Verification API methods to use correct version parameter
5
+ - Increase minimum required version for Ruby to 2.7.0
6
+
7
+ === 4.5.1 2025-04-07
8
+ - Fix Verification URLs
9
+
1
10
  === 4.5.0 2024-05-16
2
11
  - Support for warnings in Events API
3
12
 
data/README.md CHANGED
@@ -4,7 +4,7 @@ The official Ruby bindings for the latest version (v205) of the [Sift API](https
4
4
 
5
5
  ## Requirements
6
6
 
7
- * Ruby 2.0.0 or above.
7
+ * Ruby 2.7.0 or above.
8
8
 
9
9
 
10
10
  ## Installation
@@ -21,7 +21,7 @@ module Sift
21
21
  time
22
22
  }
23
23
 
24
- attr_reader :decision_id, :configs, :getter, :api_key
24
+ attr_reader :decision_id, :configs, :getter, :api_key, :client_class
25
25
 
26
26
  PROPERTIES.each do |attribute|
27
27
  class_eval %{
@@ -31,11 +31,12 @@ module Sift
31
31
  }
32
32
  end
33
33
 
34
- def initialize(api_key, decision_id, configs)
34
+ def initialize(api_key, decision_id, configs, client_class = Sift::Client)
35
35
  @api_key = api_key
36
36
  @decision_id = decision_id
37
37
  @configs = configs
38
38
  @getter = Utils::HashGetter.new(configs)
39
+ @client_class = client_class
39
40
  end
40
41
 
41
42
  def run
@@ -58,7 +59,8 @@ module Sift
58
59
  def send_request
59
60
  Router.post(path, {
60
61
  body: request_body,
61
- headers: headers
62
+ headers: headers,
63
+ client_class: client_class
62
64
  })
63
65
  end
64
66
 
@@ -79,6 +81,8 @@ module Sift
79
81
  validator.valid_order?
80
82
  elsif applying_to_session?
81
83
  validator.valid_session?
84
+ elsif applying_to_content?
85
+ validator.valid_content?
82
86
  else
83
87
  validator.valid_user?
84
88
  end
@@ -10,11 +10,12 @@ module Sift
10
10
  class Decision
11
11
  FILTER_PARAMS = %w{ limit entity_type abuse_types from }
12
12
 
13
- attr_reader :account_id, :api_key
13
+ attr_reader :account_id, :api_key, :client_class
14
14
 
15
- def initialize(api_key, account_id)
15
+ def initialize(api_key, account_id, client_class = Sift::Client)
16
16
  @account_id = account_id
17
17
  @api_key = api_key
18
+ @client_class = client_class
18
19
  end
19
20
 
20
21
  def list(options = {})
@@ -25,7 +26,8 @@ module Sift
25
26
  else
26
27
  Router.get(index_path, {
27
28
  query: build_query(getter),
28
- headers: auth_header
29
+ headers: auth_header,
30
+ client_class: client_class
29
31
  })
30
32
  end
31
33
  end
@@ -44,7 +46,7 @@ module Sift
44
46
  getter = Utils::HashGetter.new(configs)
45
47
  configs[:account_id] = account_id
46
48
 
47
- ApplyTo.new(api_key, getter.get(:decision_id), configs).run
49
+ ApplyTo.new(api_key, getter.get(:decision_id), configs, client_class).run
48
50
  end
49
51
 
50
52
  def index_path
@@ -54,7 +56,7 @@ module Sift
54
56
  private
55
57
 
56
58
  def request_next_page(path)
57
- Router.get(path, headers: auth_header)
59
+ Router.get(path, headers: auth_header, client_class: client_class)
58
60
  end
59
61
 
60
62
  def auth_header
@@ -63,4 +65,3 @@ module Sift
63
65
  end
64
66
  end
65
67
  end
66
-
data/lib/sift/client.rb CHANGED
@@ -2,9 +2,6 @@ require "httparty"
2
2
  require "multi_json"
3
3
  require "base64"
4
4
 
5
- require_relative "./client/decision"
6
- require_relative "./error"
7
-
8
5
  module Sift
9
6
 
10
7
  # Represents the payload returned from a call through the track API
@@ -94,17 +91,36 @@ module Sift
94
91
  API_ENDPOINT = ENV["SIFT_RUBY_API_URL"] || 'https://api.siftscience.com'
95
92
  API3_ENDPOINT = ENV["SIFT_RUBY_API3_URL"] || 'https://api3.siftscience.com'
96
93
 
94
+ # Maintain backward compatibility for users who may rely on HTTParty methods
97
95
  include HTTParty
98
96
  base_uri API_ENDPOINT
99
97
 
100
98
  attr_reader :api_key, :account_id
101
99
 
102
- def self.build_auth_header(api_key)
103
- { "Authorization" => "Basic #{Base64.encode64(api_key)}" }
104
- end
100
+ class << self
101
+ def build_auth_header(api_key)
102
+ { "Authorization" => "Basic #{Base64.strict_encode64(api_key + ":")}" }
103
+ end
104
+
105
+ def user_agent
106
+ "sift-ruby/#{VERSION}"
107
+ end
108
+
109
+ # Factory methods for internal API executors that inherit from the current class context.
110
+ # This ensures that subclasses of Client propagate their HTTParty configuration
111
+ # to these internal clients.
105
112
 
106
- def self.user_agent
107
- "sift-ruby/#{VERSION}"
113
+ def api_client
114
+ @api_client ||= Class.new(self) do
115
+ base_uri API_ENDPOINT
116
+ end
117
+ end
118
+
119
+ def api3_client
120
+ @api3_client ||= Class.new(self) do
121
+ base_uri API3_ENDPOINT
122
+ end
123
+ end
108
124
  end
109
125
 
110
126
  # Constructor
@@ -139,6 +155,7 @@ module Sift
139
155
  @api_key = opts[:api_key] || Sift.api_key
140
156
  @account_id = opts[:account_id] || Sift.account_id
141
157
  @version = opts[:version] || API_VERSION
158
+ @verification_version = opts[:verification_version] || VERIFICATION_API_VERSION
142
159
  @timeout = opts[:timeout] || 2 # 2-second timeout by default
143
160
  @path = opts[:path] || Sift.rest_api_path(@version)
144
161
 
@@ -201,7 +218,7 @@ module Sift
201
218
  #
202
219
  # :path::
203
220
  # Overrides the URI path for this API call.
204
- #
221
+ #
205
222
  # :include_score_percentiles::
206
223
  # include_score_percentiles(optional) : Whether to add new parameter in the query parameter.
207
224
  #
@@ -255,7 +272,7 @@ module Sift
255
272
  }
256
273
  options.merge!(:timeout => timeout) unless timeout.nil?
257
274
 
258
- response = self.class.post(path, options)
275
+ response = self.class.api_client.post(path, options)
259
276
  Response.new(response.body, response.code, response.response)
260
277
  end
261
278
 
@@ -286,7 +303,7 @@ module Sift
286
303
  #
287
304
  # :version::
288
305
  # Overrides the version of the Events API to call.
289
- #
306
+ #
290
307
  # :include_score_percentiles::
291
308
  # include_score_percentiles(optional) : Whether to add new parameter in the query parameter.
292
309
  #
@@ -318,7 +335,7 @@ module Sift
318
335
  }
319
336
  options.merge!(:timeout => timeout) unless timeout.nil?
320
337
 
321
- response = self.class.get(Sift.score_api_path(user_id, version), options)
338
+ response = self.class.api_client.get(Sift.score_api_path(user_id, version), options)
322
339
  Response.new(response.body, response.code, response.response)
323
340
  end
324
341
 
@@ -350,7 +367,7 @@ module Sift
350
367
  #
351
368
  # :timeout::
352
369
  # Overrides the timeout (in seconds) for this call.
353
- #
370
+ #
354
371
  # :include_score_percentiles::
355
372
  # include_score_percentiles(optional) : Whether to add new parameter in the query parameter.
356
373
  #
@@ -363,6 +380,7 @@ module Sift
363
380
  abuse_types = opts[:abuse_types]
364
381
  api_key = opts[:api_key] || @api_key
365
382
  timeout = opts[:timeout] || @timeout
383
+ version = opts[:version] || @version
366
384
  include_score_percentiles = opts[:include_score_percentiles]
367
385
 
368
386
  raise("user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty?
@@ -381,7 +399,7 @@ module Sift
381
399
  }
382
400
  options.merge!(:timeout => timeout) unless timeout.nil?
383
401
 
384
- response = self.class.get(Sift.user_score_api_path(user_id, @version), options)
402
+ response = self.class.api_client.get(Sift.user_score_api_path(user_id, version), options)
385
403
  Response.new(response.body, response.code, response.response)
386
404
  end
387
405
 
@@ -419,6 +437,7 @@ module Sift
419
437
  abuse_types = opts[:abuse_types]
420
438
  api_key = opts[:api_key] || @api_key
421
439
  timeout = opts[:timeout] || @timeout
440
+ version = opts[:version] || @version
422
441
 
423
442
  raise("user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty?
424
443
  raise("Bad api_key parameter") if api_key.empty?
@@ -433,7 +452,7 @@ module Sift
433
452
  }
434
453
  options.merge!(:timeout => timeout) unless timeout.nil?
435
454
 
436
- response = self.class.post(Sift.user_score_api_path(user_id, @version), options)
455
+ response = self.class.api_client.post(Sift.user_score_api_path(user_id, version), options)
437
456
  Response.new(response.body, response.code, response.response)
438
457
  end
439
458
 
@@ -531,7 +550,7 @@ module Sift
531
550
  }
532
551
  options.merge!(:timeout => timeout) unless timeout.nil?
533
552
 
534
- response = self.class.delete(Sift.users_label_api_path(user_id, version), options)
553
+ response = self.class.api_client.delete(Sift.users_label_api_path(user_id, version), options)
535
554
  Response.new(response.body, response.code, response.response)
536
555
  end
537
556
 
@@ -568,8 +587,7 @@ module Sift
568
587
  }
569
588
  options.merge!(:timeout => timeout) unless timeout.nil?
570
589
 
571
- uri = API3_ENDPOINT + Sift.workflow_status_path(account_id, run_id)
572
- response = self.class.get(uri, options)
590
+ response = self.class.api3_client.get(Sift.workflow_status_path(account_id, run_id), options)
573
591
  Response.new(response.body, response.code, response.response)
574
592
  end
575
593
 
@@ -606,8 +624,7 @@ module Sift
606
624
  }
607
625
  options.merge!(:timeout => timeout) unless timeout.nil?
608
626
 
609
- uri = API3_ENDPOINT + Sift.user_decisions_api_path(account_id, user_id)
610
- response = self.class.get(uri, options)
627
+ response = self.class.api3_client.get(Sift.user_decisions_api_path(account_id, user_id), options)
611
628
  Response.new(response.body, response.code, response.response)
612
629
  end
613
630
 
@@ -644,8 +661,7 @@ module Sift
644
661
  }
645
662
  options.merge!(:timeout => timeout) unless timeout.nil?
646
663
 
647
- uri = API3_ENDPOINT + Sift.order_decisions_api_path(account_id, order_id)
648
- response = self.class.get(uri, options)
664
+ response = self.class.api3_client.get(Sift.order_decisions_api_path(account_id, order_id), options)
649
665
  Response.new(response.body, response.code, response.response)
650
666
  end
651
667
 
@@ -684,8 +700,7 @@ module Sift
684
700
  }
685
701
  options.merge!(:timeout => timeout) unless timeout.nil?
686
702
 
687
- uri = API3_ENDPOINT + Sift.session_decisions_api_path(account_id, user_id, session_id)
688
- response = self.class.get(uri, options)
703
+ response = self.class.api3_client.get(Sift.session_decisions_api_path(account_id, user_id, session_id), options)
689
704
  Response.new(response.body, response.code, response.response)
690
705
  end
691
706
 
@@ -724,8 +739,7 @@ module Sift
724
739
  }
725
740
  options.merge!(:timeout => timeout) unless timeout.nil?
726
741
 
727
- uri = API3_ENDPOINT + Sift.content_decisions_api_path(account_id, user_id, content_id)
728
- response = self.class.get(uri, options)
742
+ response = self.class.api3_client.get(Sift.content_decisions_api_path(account_id, user_id, content_id), options)
729
743
  Response.new(response.body, response.code, response.response)
730
744
  end
731
745
 
@@ -747,7 +761,7 @@ module Sift
747
761
 
748
762
  def build_default_headers_post(api_key)
749
763
  {
750
- "Authorization" => "Basic #{Base64.encode64(api_key+":")}",
764
+ "Authorization" => "Basic #{Base64.strict_encode64(api_key+":")}",
751
765
  "User-Agent" => "SiftScience/v#{@version} sift-ruby/#{VERSION}",
752
766
  "Content-Type" => "application/json"
753
767
  }
@@ -755,7 +769,7 @@ module Sift
755
769
 
756
770
  def verification_send(properties = {}, opts = {})
757
771
  api_key = opts[:api_key] || @api_key
758
- version = opts[:version] || @version
772
+ version = opts[:verification_version] || @verification_version
759
773
  timeout = opts[:timeout] || @timeout
760
774
 
761
775
  raise("properties cannot be empty") if properties.empty?
@@ -767,14 +781,13 @@ module Sift
767
781
  :headers => build_default_headers_post(api_key)
768
782
  }
769
783
  options.merge!(:timeout => timeout) unless timeout.nil?
770
-
771
- response = self.class.post(Sift.verification_api_send_path(@version), options)
784
+ response = self.class.api_client.post(Sift.verification_api_send_path(version), options)
772
785
  Response.new(response.body, response.code, response.response)
773
786
  end
774
787
 
775
788
  def verification_resend(properties = {}, opts = {})
776
789
  api_key = opts[:api_key] || @api_key
777
- version = opts[:version] || @version
790
+ version = opts[:verification_version] || @verification_version
778
791
  timeout = opts[:timeout] || @timeout
779
792
 
780
793
  raise("properties cannot be empty") if properties.empty?
@@ -787,13 +800,13 @@ module Sift
787
800
  }
788
801
  options.merge!(:timeout => timeout) unless timeout.nil?
789
802
 
790
- response = self.class.post(Sift.verification_api_resend_path(@version), options)
803
+ response = self.class.api_client.post(Sift.verification_api_resend_path(version), options)
791
804
  Response.new(response.body, response.code, response.response)
792
805
  end
793
806
 
794
807
  def verification_check(properties = {}, opts = {})
795
808
  api_key = opts[:api_key] || @api_key
796
- version = opts[:version] || @version
809
+ version = opts[:verification_version] || @verification_version
797
810
  timeout = opts[:timeout] || @timeout
798
811
 
799
812
  raise("properties cannot be empty") if properties.empty?
@@ -806,7 +819,7 @@ module Sift
806
819
  }
807
820
  options.merge!(:timeout => timeout) unless timeout.nil?
808
821
 
809
- response = self.class.post(Sift.verification_api_check_path(@version), options)
822
+ response = self.class.api_client.post(Sift.verification_api_check_path(version), options)
810
823
  Response.new(response.body, response.code, response.response)
811
824
  end
812
825
 
@@ -831,7 +844,7 @@ module Sift
831
844
  :basic_auth => { :username => api_key, :password => "" }
832
845
  }
833
846
  options.merge!(:timeout => timeout) unless timeout.nil?
834
- response = self.class.post(API_ENDPOINT + Sift.psp_merchant_api_path(account_id), options)
847
+ response = self.class.api_client.post(Sift.psp_merchant_api_path(account_id), options)
835
848
  Response.new(response.body, response.code, response.response)
836
849
  end
837
850
 
@@ -858,7 +871,7 @@ module Sift
858
871
  :basic_auth => { :username => api_key, :password => "" }
859
872
  }
860
873
  options.merge!(:timeout => timeout) unless timeout.nil?
861
- response = self.class.put(API_ENDPOINT + Sift.psp_merchant_id_api_path(account_id, merchant_id), options)
874
+ response = self.class.api_client.put(Sift.psp_merchant_id_api_path(account_id, merchant_id), options)
862
875
  Response.new(response.body, response.code, response.response)
863
876
  end
864
877
 
@@ -882,7 +895,7 @@ module Sift
882
895
  :basic_auth => { :username => api_key, :password => "" }
883
896
  }
884
897
  options.merge!(:timeout => timeout) unless timeout.nil?
885
- response = self.class.get(API_ENDPOINT + Sift.psp_merchant_id_api_path(account_id, merchant_id), options)
898
+ response = self.class.api_client.get(Sift.psp_merchant_id_api_path(account_id, merchant_id), options)
886
899
  Response.new(response.body, response.code, response.response)
887
900
  end
888
901
 
@@ -911,7 +924,7 @@ module Sift
911
924
  :query => query
912
925
  }
913
926
  options.merge!(:timeout => timeout) unless timeout.nil?
914
- response = self.class.get(API_ENDPOINT + Sift.psp_merchant_api_path(account_id), options)
927
+ response = self.class.api_client.get(Sift.psp_merchant_api_path(account_id), options)
915
928
  Response.new(response.body, response.code, response.response)
916
929
  end
917
930
 
@@ -926,7 +939,7 @@ module Sift
926
939
  end
927
940
 
928
941
  def decision_instance
929
- @decision_instance ||= Decision.new(api_key, account_id)
942
+ @decision_instance ||= Decision.new(api_key, account_id, self.class)
930
943
  end
931
944
 
932
945
  def delete_nils(properties)
@@ -943,4 +956,8 @@ module Sift
943
956
  end
944
957
  end
945
958
  end
959
+
960
+ require_relative "./client/decision"
961
+ require_relative "./error"
962
+
946
963
  end
data/lib/sift/router.rb CHANGED
@@ -2,20 +2,23 @@ require_relative "./version"
2
2
  require_relative "./client"
3
3
 
4
4
  module Sift
5
- class Router
6
- include HTTParty
5
+ class Router < Client
7
6
 
8
7
  class << self
9
8
  def get(path, options = {})
9
+ client_class = options.delete(:client_class) || Sift::Client
10
+ options[:base_uri] = nil
10
11
  serialize_body(options)
11
12
  add_default_headers(options)
12
- wrap_response(super(path, options))
13
+ wrap_response(client_class.api3_client.get(path, options))
13
14
  end
14
15
 
15
16
  def post(path, options = {})
17
+ client_class = options.delete(:client_class) || Sift::Client
18
+ options[:base_uri] = nil
16
19
  serialize_body(options)
17
20
  add_default_headers(options)
18
- wrap_response(super(path, options))
21
+ wrap_response(client_class.api3_client.post(path, options))
19
22
  end
20
23
 
21
24
  def serialize_body(options)
data/lib/sift/version.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  module Sift
2
- VERSION = "4.5.0"
2
+ VERSION = "4.6.0"
3
3
  API_VERSION = "205"
4
+ VERIFICATION_API_VERSION = "1.1"
4
5
  end
data/lib/sift.rb CHANGED
@@ -10,17 +10,17 @@ module Sift
10
10
  end
11
11
 
12
12
  # Returns the path for the specified API version
13
- def self.verification_api_send_path(version=API_VERSION)
13
+ def self.verification_api_send_path(version=VERIFICATION_API_VERSION)
14
14
  "/v#{version}/verification/send"
15
15
  end
16
16
 
17
17
  # Returns the path for the specified API version
18
- def self.verification_api_resend_path(version=API_VERSION)
18
+ def self.verification_api_resend_path(version=VERIFICATION_API_VERSION)
19
19
  "/v#{version}/verification/resend"
20
20
  end
21
21
 
22
22
  # Returns the path for the specified API version
23
- def self.verification_api_check_path(version=API_VERSION)
23
+ def self.verification_api_check_path(version=VERIFICATION_API_VERSION)
24
24
  "/v#{version}/verification/check"
25
25
  end
26
26
 
data/sift.gemspec CHANGED
@@ -7,10 +7,10 @@ Gem::Specification.new do |s|
7
7
  s.version = Sift::VERSION
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.authors = ["Fred Sadaghiani", "Yoav Schatzberg", "Jacob Burnim"]
10
- s.email = ["support@siftscience.com"]
11
- s.homepage = "http://siftscience.com"
12
- s.summary = %q{Sift Science Ruby API Gem}
13
- s.description = %q{Sift Science Ruby API. Please see http://siftscience.com for more details.}
10
+ s.email = ["support@sift.com"]
11
+ s.homepage = "http://sift.com"
12
+ s.summary = %q{Sift Ruby API Gem}
13
+ s.description = %q{Sift Ruby API. Please see http://sift.com for more details.}
14
14
 
15
15
  s.rubyforge_project = "sift"
16
16
 
@@ -19,6 +19,8 @@ Gem::Specification.new do |s|
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
21
 
22
+ s.required_ruby_version = '>= 2.7.0'
23
+
22
24
  # Gems that must be intalled for sift to compile and build
23
25
  s.add_development_dependency "rspec", "~> 3.5"
24
26
  s.add_development_dependency "rspec_junit_formatter"
@@ -26,7 +28,7 @@ Gem::Specification.new do |s|
26
28
  s.add_development_dependency "webmock", ">= 1.16.0", "< 2"
27
29
 
28
30
  # Gems that must be intalled for sift to work
29
- s.add_dependency "httparty", ">= 0.11.0"
31
+ s.add_dependency "httparty", ">= 0.23.3"
30
32
  s.add_dependency "multi_json", ">= 1.0"
31
33
 
32
34
  s.add_development_dependency("rake")
@@ -254,7 +254,7 @@ module Sift
254
254
  def put_auth_in_url(api_key, url)
255
255
  protocal, uri = url.split(/(?<=https\:\/\/)/)
256
256
 
257
- protocal + api_key + "@" + uri
257
+ protocal + api_key + ":@" + uri
258
258
  end
259
259
  end
260
260
  end
@@ -20,7 +20,7 @@ module Sift
20
20
  #
21
21
  protocal, uri = decision.index_path.split(/(?<=https\:\/\/)/)
22
22
 
23
- protocal + api_key + "@" + uri
23
+ protocal + api_key + ":@" + uri
24
24
  }
25
25
 
26
26
  describe "#list" do
@@ -88,4 +88,50 @@ describe Sift::Client do
88
88
 
89
89
  end
90
90
 
91
+ # Regression tests for verification API version bug fix
92
+ # These tests ensure verification methods use verification_version (1.1)
93
+ # instead of the events API version (205)
94
+
95
+ it "Uses verification API version (1.1) not events API version (205) for verification_send" do
96
+ api_key = "test_key"
97
+ response_json = { :status => 0, :error_message => "OK"}
98
+
99
+ # Should call v1.1, NOT v205
100
+ stub_request(:post, "https://test_key:@api.siftscience.com/v1.1/verification/send")
101
+ .to_return(:status => 200, :body => MultiJson.dump(response_json))
102
+
103
+ # Client defaults: version=205 (events), verification_version=1.1
104
+ response = Sift::Client.new(:api_key => api_key).verification_send(valid_send_properties)
105
+
106
+ expect(response.ok?).to eq(true)
107
+ end
108
+
109
+ it "Uses verification API version (1.1) not events API version (205) for verification_resend" do
110
+ api_key = "test_key"
111
+ response_json = { :status => 0, :error_message => "OK"}
112
+
113
+ # Should call v1.1, NOT v205
114
+ stub_request(:post, "https://test_key:@api.siftscience.com/v1.1/verification/resend")
115
+ .to_return(:status => 200, :body => MultiJson.dump(response_json))
116
+
117
+ # Client defaults: version=205 (events), verification_version=1.1
118
+ response = Sift::Client.new(:api_key => api_key).verification_resend(valid_resend_properties)
119
+
120
+ expect(response.ok?).to eq(true)
121
+ end
122
+
123
+ it "Uses verification API version (1.1) not events API version (205) for verification_check" do
124
+ api_key = "test_key"
125
+ response_json = { :status => 0, :error_message => "OK"}
126
+
127
+ # Should call v1.1, NOT v205
128
+ stub_request(:post, "https://test_key:@api.siftscience.com/v1.1/verification/check")
129
+ .to_return(:status => 200, :body => MultiJson.dump(response_json))
130
+
131
+ # Client defaults: version=205 (events), verification_version=1.1
132
+ response = Sift::Client.new(:api_key => api_key).verification_check(valid_check_properties)
133
+
134
+ expect(response.ok?).to eq(true)
135
+ end
136
+
91
137
  end
@@ -0,0 +1,67 @@
1
+ require_relative "../spec_helper"
2
+ require "sift"
3
+ require "logger"
4
+
5
+ describe "Sift::Client Configuration Patterns" do
6
+ let(:api_key) { "test_api_key" }
7
+
8
+ it "propagates global Sift::Client configuration to internal clients" do
9
+ Sift::Client.default_timeout 5
10
+
11
+ # Internal executors should inherit this
12
+ expect(Sift::Client.api_client.default_options[:timeout]).to eq(5)
13
+ expect(Sift::Client.api3_client.default_options[:timeout]).to eq(5)
14
+
15
+ # Reset
16
+ Sift::Client.default_timeout 2
17
+ end
18
+
19
+ it "allows independent subclass configurations" do
20
+ class SubclassA < Sift::Client; end
21
+ class SubclassB < Sift::Client; end
22
+
23
+ SubclassA.default_timeout 10
24
+ SubclassB.default_timeout 20
25
+
26
+ expect(SubclassA.api_client.default_options[:timeout]).to eq(10)
27
+ expect(SubclassB.api_client.default_options[:timeout]).to eq(20)
28
+
29
+ # Ensure they didn't leak to parent
30
+ expect(Sift::Client.api_client.default_options[:timeout]).to be <= 5
31
+ end
32
+
33
+ it "propagates complex settings like loggers to subclasses" do
34
+ class LoggingClient < Sift::Client; end
35
+
36
+ logger = Logger.new(nil)
37
+ LoggingClient.logger logger, :debug, :curl
38
+
39
+ expect(LoggingClient.api_client.default_options[:logger]).to eq(logger)
40
+ expect(LoggingClient.api_client.default_options[:log_level]).to eq(:debug)
41
+ expect(LoggingClient.api_client.default_options[:log_format]).to eq(:curl)
42
+
43
+ # Ensure Sift::Client remains untouched
44
+ expect(Sift::Client.api_client.default_options[:logger]).to be_nil
45
+ end
46
+
47
+ it "respects inheritance chain for Decisions and Router" do
48
+ class DecisionClient < Sift::Client; end
49
+ DecisionClient.default_timeout 15
50
+
51
+ client = DecisionClient.new(api_key: api_key, account_id: "acc")
52
+
53
+ # Verify the executor used by the Router is the one from DecisionClient
54
+ expect(DecisionClient.api3_client.default_options[:timeout]).to eq(15)
55
+
56
+ # We want to ensure that when Router.get is called, it uses DecisionClient.api3_client
57
+ # In lib/sift/router.rb, we have:
58
+ # wrap_response(client_class.api3_client.get(path, options))
59
+
60
+ expect(DecisionClient.api3_client).to receive(:get).and_call_original
61
+
62
+ # Mock the request to avoid network calls
63
+ stub_request(:get, /api3.siftscience.com/)
64
+
65
+ client.get_user_decisions("user_1")
66
+ end
67
+ end
@@ -2,13 +2,13 @@ require "sift"
2
2
 
3
3
  class VerificationAPI
4
4
 
5
- @@client = Sift::Client.new(:api_key => ENV["API_KEY"], :version=>1.1)
5
+ @@client = Sift::Client.new(:api_key => ENV["API_KEY"], :version => 1.1)
6
6
 
7
7
  def send()
8
8
  properties = {
9
9
  "$user_id" => $user_id,
10
10
  "$send_to" => $user_email,
11
- "$verification_type" => "$email",
11
+ "$verification_type" => "$email",
12
12
  "$brand_name" => "MyTopBrand",
13
13
  "$language" => "en",
14
14
  "$site_country" => "IN",
@@ -28,5 +28,5 @@ class VerificationAPI
28
28
 
29
29
  return @@client.verification_send(properties)
30
30
  end
31
-
31
+
32
32
  end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sift
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.5.0
4
+ version: 4.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Sadaghiani
8
8
  - Yoav Schatzberg
9
9
  - Jacob Burnim
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2024-05-17 00:00:00.000000000 Z
13
+ date: 2026-03-02 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rspec
@@ -80,14 +80,14 @@ dependencies:
80
80
  requirements:
81
81
  - - ">="
82
82
  - !ruby/object:Gem::Version
83
- version: 0.11.0
83
+ version: 0.23.3
84
84
  type: :runtime
85
85
  prerelease: false
86
86
  version_requirements: !ruby/object:Gem::Requirement
87
87
  requirements:
88
88
  - - ">="
89
89
  - !ruby/object:Gem::Version
90
- version: 0.11.0
90
+ version: 0.23.3
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: multi_json
93
93
  requirement: !ruby/object:Gem::Requirement
@@ -116,18 +116,17 @@ dependencies:
116
116
  - - ">="
117
117
  - !ruby/object:Gem::Version
118
118
  version: '0'
119
- description: Sift Science Ruby API. Please see http://siftscience.com for more details.
119
+ description: Sift Ruby API. Please see http://sift.com for more details.
120
120
  email:
121
- - support@siftscience.com
121
+ - support@sift.com
122
122
  executables: []
123
123
  extensions: []
124
124
  extra_rdoc_files: []
125
125
  files:
126
- - ".circleci/config.yml"
127
126
  - ".github/pull_request_template.md"
127
+ - ".github/workflows/ci.yml"
128
128
  - ".github/workflows/publishing_sift_ruby.yml"
129
129
  - ".gitignore"
130
- - ".jenkins/Jenkinsfile"
131
130
  - Gemfile
132
131
  - HISTORY
133
132
  - LICENSE
@@ -158,6 +157,7 @@ files:
158
157
  - spec/unit/client_psp_merchant_spec.rb
159
158
  - spec/unit/client_spec.rb
160
159
  - spec/unit/client_validationapi_spec.rb
160
+ - spec/unit/configuration_spec.rb
161
161
  - spec/unit/router_spec.rb
162
162
  - spec/unit/validate/decision_spec.rb
163
163
  - spec/unit/validate/primitive_spec.rb
@@ -168,10 +168,10 @@ files:
168
168
  - test_integration_app/psp_merchants_api/test_psp_merchant_api.rb
169
169
  - test_integration_app/score_api/test_score_api.rb
170
170
  - test_integration_app/verification_api/test_verification_api.rb
171
- homepage: http://siftscience.com
171
+ homepage: http://sift.com
172
172
  licenses: []
173
173
  metadata: {}
174
- post_install_message:
174
+ post_install_message:
175
175
  rdoc_options: []
176
176
  require_paths:
177
177
  - lib
@@ -179,7 +179,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
179
179
  requirements:
180
180
  - - ">="
181
181
  - !ruby/object:Gem::Version
182
- version: '0'
182
+ version: 2.7.0
183
183
  required_rubygems_version: !ruby/object:Gem::Requirement
184
184
  requirements:
185
185
  - - ">="
@@ -187,7 +187,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
187
187
  version: '0'
188
188
  requirements: []
189
189
  rubygems_version: 3.1.6
190
- signing_key:
190
+ signing_key:
191
191
  specification_version: 4
192
- summary: Sift Science Ruby API Gem
192
+ summary: Sift Ruby API Gem
193
193
  test_files: []
data/.circleci/config.yml DELETED
@@ -1,105 +0,0 @@
1
- version: 2.1
2
-
3
- orbs:
4
- slack: circleci/slack@4.4.2
5
-
6
- commands:
7
- export_slack_id:
8
- steps:
9
- - run:
10
- name : Exporting circleci username as slack id.
11
- command: echo 'export SLACK_PARAM_MENTIONS="$CIRCLE_USERNAME"' >> "$BASH_ENV"
12
- - run:
13
- name : CircleCi To Slack user mapping.
14
- command: |
15
- echo $GITHUB_SLACK_USERMAPPING | base64 --decode > github_slack
16
- while read -r line || [[ -n $line ]];
17
- do
18
- [[ ${line//[[:space:]]/} =~ ^#.* || -z "$line" ]] && continue
19
- echo "$line" | tr "=" "\n" | while read -r key; do
20
- read -r value
21
- if [ "$CIRCLE_USERNAME" = "${key}" ]; then
22
- echo "export SLACK_PARAM_MENTIONS='<@${value}>'" >> $BASH_ENV
23
- fi
24
- done
25
- done < github_slack
26
- rm github_slack
27
-
28
- slack/notify: &slack_notify
29
- branch_pattern: master
30
- event: fail
31
- channel: ci-build-status
32
- template: SLACK_TAG_CI_FAILURE_TEMPLATE
33
-
34
- context: &context
35
- - slack-templates
36
- - slack_Oauth
37
- - Github_Slack_UserMapping
38
-
39
- jobs:
40
- build:
41
- parallelism: 3 # run three instances of this job in parallel
42
- docker:
43
- - image: circleci/ruby:2.4.2-jessie-node
44
- environment:
45
- BUNDLE_JOBS: 3
46
- BUNDLE_RETRY: 3
47
- BUNDLE_PATH: vendor/bundle
48
- steps:
49
- - checkout
50
- - export_slack_id
51
-
52
- - run:
53
- name: Which bundler?
54
- command: bundle -v
55
-
56
- - restore_cache:
57
- keys:
58
- - sift-bundle-{{ checksum "sift.gemspec" }}
59
- - sift-bundle-
60
-
61
- - run:
62
- name: Bundle Install
63
- command: bundle check || bundle install
64
-
65
- - save_cache:
66
- key: sift-bundle-{{ checksum "sift.gemspec" }}
67
- paths:
68
- - vendor/bundle
69
-
70
- - run:
71
- name: Run rspec in parallel
72
- command: |
73
- bundle exec rspec --profile 10 \
74
- --format RspecJunitFormatter \
75
- --out test_results/rspec.xml \
76
- --format progress \
77
- $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
78
-
79
- - store_test_results:
80
- path: test_results
81
- - slack/notify:
82
- <<: *slack_notify
83
-
84
- run_integration_tests:
85
- docker:
86
- - image: circleci/ruby:2.4.2-jessie-node
87
- steps:
88
- - checkout
89
- - run:
90
- name: Install bundle and run the tests
91
- command: |
92
- bundle check || bundle install
93
- bundle exec ruby test_integration_app/main.rb
94
-
95
- workflows:
96
- ruby-test:
97
- jobs:
98
- - build:
99
- context: *context
100
- ruby-integration-tests:
101
- jobs:
102
- - run_integration_tests:
103
- filters:
104
- branches:
105
- only: master
data/.jenkins/Jenkinsfile DELETED
@@ -1,103 +0,0 @@
1
- // Load Jenkins shared library
2
- jenkinsBranch = 'v0.37.0'
3
- sharedLib = library("shared-lib@${jenkinsBranch}")
4
-
5
- def siftRubyWorkflow = sharedLib.com.sift.ci.SiftRubyWorkflow.new()
6
- def ciUtil = sharedLib.com.sift.ci.CIUtil.new()
7
- def stackdriver = sharedLib.com.sift.ci.StackDriverMetrics.new()
8
-
9
- // Default GitHub status context for automatically triggered builds
10
- def defaultStatusContext = 'Jenkins:auto'
11
-
12
- // Pod template file for Jenkins agent pod
13
- // Pod template yaml file is defined in https://github.com/SiftScience/jenkins/tree/master/resources/jenkins-k8s-pod-templates
14
- def ruby2PodTemplateFile = 'ruby-2-4-2-pod-template.yaml'
15
- def ruby2PodLabel = "ruby2-${BUILD_TAG}"
16
-
17
-
18
- // GitHub repo name
19
- def repoName = 'sift-ruby'
20
-
21
- pipeline {
22
- agent none
23
- options {
24
- timestamps()
25
- skipDefaultCheckout()
26
- disableConcurrentBuilds()
27
- disableRestartFromStage()
28
- parallelsAlwaysFailFast()
29
- buildDiscarder logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '30', numToKeepStr: '')
30
- timeout(time: 1, unit: 'HOURS')
31
- }
32
- environment {
33
- GIT_BRANCH = "${env.CHANGE_BRANCH != null? env.CHANGE_BRANCH : env.BRANCH_NAME}"
34
- }
35
- stages {
36
- stage('Initialize') {
37
- steps {
38
- script {
39
- statusContext = defaultStatusContext
40
- // Get the commit sha for the build
41
- commitSha = ciUtil.commitHashForBuild()
42
- ciUtil.updateGithubCommitStatus(repoName, statusContext, 'Started', 'pending', commitSha)
43
- }
44
- }
45
- }
46
- stage ('Build and Test Workflows') {
47
- steps {
48
- script {
49
- def workflows = [:]
50
- def stage1 = 'Run Integration Tests - ruby'
51
- workflows[stage1] = {
52
- stage(stage1) {
53
- if (env.GIT_BRANCH.equals('master')) {
54
- ciUtil.updateGithubCommitStatus(repoName, stage1, 'Started', 'pending', commitSha)
55
- try {
56
- siftRubyWorkflow.runSiftRubyIntegration(ruby2PodTemplateFile, ruby2PodLabel)
57
- ciUtil.updateGithubCommitStatus(repoName, stage1, 'SUCCESS', 'success', commitSha)
58
- } catch (Exception e) {
59
- ciUtil.updateGithubCommitStatus(repoName, stage1, 'FAILURE', 'failure', commitSha)
60
- print("${stage1} failed")
61
- throw e
62
- }
63
- }
64
- }
65
- }
66
- def stage2 = 'Test - ruby'
67
- workflows[stage2] = {
68
- stage(stage2) {
69
- ciUtil.updateGithubCommitStatus(repoName, stage2, 'Started', 'pending', commitSha)
70
- try {
71
- siftRubyWorkflow.runSiftRubyTest(ruby2PodTemplateFile, ruby2PodLabel)
72
- ciUtil.updateGithubCommitStatus(repoName, stage2, 'SUCCESS', 'success', commitSha)
73
- } catch (Exception e) {
74
- ciUtil.updateGithubCommitStatus(repoName, stage2, 'FAILURE', 'failure', commitSha)
75
- print("${stage2} failed")
76
- throw e
77
- }
78
- }
79
- }
80
- parallel workflows
81
- }
82
- }
83
- }
84
- }
85
- post {
86
- success {
87
- script {
88
- ciUtil.updateGithubCommitStatus(repoName, statusContext, currentBuild.currentResult, 'success', commitSha)
89
- }
90
- }
91
- unsuccessful {
92
- script {
93
- ciUtil.updateGithubCommitStatus(repoName, statusContext, currentBuild.currentResult, 'failure', commitSha)
94
- ciUtil.notifySlack(repoName, commitSha)
95
- }
96
- }
97
- always {
98
- script {
99
- stackdriver.updatePipelineStatistics(this)
100
- }
101
- }
102
- }
103
- }