recurly 2.0.14 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of recurly might be problematic. Click here for more details.

data/README.markdown CHANGED
@@ -12,7 +12,7 @@ Recurly is packaged as a Ruby gem. We recommend you install it with
12
12
  [Bundler](http://gembundler.com/) by adding the following line to your Gemfile:
13
13
 
14
14
  ``` ruby
15
- gem 'recurly', '~> 2.0.13'
15
+ gem 'recurly', '~> 2.1.0'
16
16
  ```
17
17
 
18
18
  Recurly will automatically use [Nokogiri](http://nokogiri.org/) (for a nice
@@ -62,7 +62,7 @@ Instructions and examples are available on
62
62
  [Recurly's documentation site](http://docs.recurly.com/api/basics).
63
63
 
64
64
  Recurly's gem API is available
65
- [here](http://rubydoc.info/gems/recurly/2.0.10/frames).
65
+ [here](http://rubydoc.info/gems/recurly/2.1.0/frames).
66
66
 
67
67
 
68
68
  ## Contributing
data/bin/recurly CHANGED
@@ -18,8 +18,12 @@ options = {}
18
18
  OptionParser.new do |opts|
19
19
  opts.banner = 'Usage: recurly [options] -- [irb options]'
20
20
 
21
- opts.on '-k', '--api-key [api key]', 'Your API key' do |api_key|
22
- options[:api_key] = api_key
21
+ opts.on '-k', '--api-key [api key]', 'Your API key' do |key|
22
+ options[:api_key] = key
23
+ end
24
+
25
+ opts.on '-K', '--private-key [private key]', 'Your Recurly.js private key' do |key|
26
+ options[:private_key] = key
23
27
  end
24
28
 
25
29
  opts.on '-v', '--verbose', 'Show full request/response log' do |verbose|
@@ -47,6 +51,7 @@ end.parse!
47
51
 
48
52
  require 'recurly/all'
49
53
  Recurly.api_key = options[:api_key] || ENV['RECURLY_API_KEY']
54
+ Recurly.js.private_key = options[:private_key] || ENV['RECURLY_JS_PRIVATE_KEY']
50
55
  include Recurly
51
56
 
52
57
  require 'logger'
data/lib/recurly.rb CHANGED
@@ -57,8 +57,6 @@ module Recurly
57
57
  end
58
58
 
59
59
  # Assigns a logger to log requests/responses and more.
60
- # The logger can only be set if the environment variable
61
- # `RECURLY_INSECURE_DEBUG` equals `true`.
62
60
  #
63
61
  # @return [Logger, nil]
64
62
  # @example
@@ -70,26 +68,10 @@ module Recurly
70
68
  # Recurly.logger = nil # Or Recurly.logger = Logger.new nil
71
69
  attr_accessor :logger
72
70
 
73
- def logger=(logger)
74
- if ENV['RECURLY_INSECURE_DEBUG'].to_s.downcase == 'true'
75
- @logger = logger
76
- puts <<-MSG
77
- [WARNING] Recurly logger enabled. The logger has the potential to leak
78
- PII and should never be used in production environments.
79
- MSG
80
- else
81
- puts <<-MSG
82
- [WARNING] Recurly logger has been disabled. If you wish to use it,
83
- only do so in a non-production environment and make sure
84
- the `RECURLY_INSECURE_DEBUG` environment variable is set to `true`.
85
- MSG
86
- end
87
- end
88
-
89
71
  # Convenience logging method includes a Logger#progname dynamically.
90
72
  # @return [true, nil]
91
73
  def log level, message
92
- logger and logger.send(level, name) { message }
74
+ logger.send(level, name) { message }
93
75
  end
94
76
  end
95
77
  end
data/lib/recurly/api.rb CHANGED
@@ -15,7 +15,6 @@ module Recurly
15
15
  require 'recurly/api/errors'
16
16
 
17
17
  @@base_uri = "https://api.recurly.com/v2/"
18
- @@valid_domains = [".recurly.com"]
19
18
 
20
19
  class << self
21
20
  # @return [String]
@@ -56,13 +55,6 @@ module Recurly
56
55
  URI.parse @@base_uri
57
56
  end
58
57
 
59
- def validate_uri!(uri)
60
- domain = @@valid_domains.detect { |d| uri.host.end_with?(d) }
61
- unless domain
62
- raise ArgumentError, "URI #{uri} is invalid. You may only make requests to a Recurly domain."
63
- end
64
- end
65
-
66
58
  # @return [String]
67
59
  def user_agent
68
60
  "Recurly/#{Version}; #{RUBY_DESCRIPTION}"
@@ -39,9 +39,8 @@ module Recurly
39
39
  if options[:params] && !options[:params].empty?
40
40
  uri += "?#{options[:params].map { |k, v| "#{k}=#{v}" }.join '&' }"
41
41
  end
42
- self.validate_uri!(uri)
43
42
  request = METHODS[method].new uri.request_uri, head
44
- request.basic_auth Recurly.api_key, nil
43
+ request.basic_auth(*[Recurly.api_key, nil].flatten[0, 2])
45
44
  if options[:body]
46
45
  head['Content-Type'] = content_type
47
46
  request.body = options[:body]
@@ -36,6 +36,13 @@ module Recurly
36
36
  indifferent
37
37
  end
38
38
 
39
+ def stringify_keys! hash
40
+ hash.keys.each do |key|
41
+ stringify_keys! hash[key] if hash[key].is_a? Hash
42
+ hash[key.to_s] = hash.delete key if key.is_a? Symbol
43
+ end
44
+ end
45
+
39
46
  extend self
40
47
  end
41
48
  end
data/lib/recurly/js.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'openssl'
2
+ require 'base64'
2
3
 
3
4
  module Recurly
4
5
  # A collection of helper methods to use to verify
@@ -8,8 +9,8 @@ module Recurly
8
9
  class RequestForgery < Error
9
10
  end
10
11
 
11
- # Used to prevent strings from being escaped during digest.
12
- class SafeString < String
12
+ # Raised when the timestamp is over an hour old. Prevents replay attacks.
13
+ class RequestTooOld < RequestForgery
13
14
  end
14
15
 
15
16
  class << self
@@ -22,46 +23,36 @@ module Recurly
22
23
  end
23
24
  attr_writer :private_key
24
25
 
25
- # @return [String]
26
- def sign_subscription plan_code, account_code, extras = {}
27
- sign 'subscriptioncreate', {
28
- 'plan_code' => plan_code,
29
- 'account_code' => account_code
30
- }, extras
31
- end
32
-
33
- # @return [String]
34
- def sign_billing_info account_code, extras = {}
35
- sign 'billinginfoupdate', { 'account_code' => account_code }, extras
36
- end
37
-
38
- # @return [String]
39
- def sign_transaction(
40
- amount_in_cents, currency = nil, account_code = nil, extras = {}
41
- )
42
- sign 'transactioncreate', {
43
- 'amount_in_cents' => amount_in_cents,
44
- 'currency' => currency || Recurly.default_currency,
45
- 'account_code' => account_code
46
- }, extras
47
- end
48
-
49
- # @return [true]
50
- # @raise [RequestForgery] If verification fails.
51
- def verify_subscription! params
52
- verify! 'subscriptioncreated', params
53
- end
54
-
55
- # @return [true]
56
- # @raise [RequestForgery] If verification fails.
57
- def verify_billing_info! params
58
- verify! 'billinginfoupdated', params
26
+ # Create a signature for a given hash for Recurly.js
27
+ # @param Array of objects and hash of data to sign
28
+ def sign *records
29
+ data = records.last.is_a?(Hash) ? records.pop.dup : {}
30
+ records.each do |record|
31
+ data[record.class.member_name] = record.signable_attributes
32
+ end
33
+ Helper.stringify_keys! data
34
+ data['timestamp'] ||= Time.now.to_i
35
+ data['nonce'] ||= Base64.encode64(
36
+ OpenSSL::Random.random_bytes(32)
37
+ ).gsub(/\W/, '')
38
+ unsigned = to_query data
39
+ signed = OpenSSL::HMAC.hexdigest 'sha1', private_key, unsigned
40
+ [signed, unsigned].join '|'
59
41
  end
60
42
 
61
- # @return [true]
62
- # @raise [RequestForgery] If verification fails.
63
- def verify_transaction! params
64
- verify! 'transactioncreated', params
43
+ # Fetches a record using a token provided by Recurly.js.
44
+ # @param [String] Token to look up
45
+ # @return [BillingInfo, Invoice, Subscription] The record created or
46
+ # modified by Recurly.js
47
+ # @raise [API::NotFound] No record was found for the token provided.
48
+ # @example
49
+ # begin
50
+ # Recurly.js.fetch params[:token]
51
+ # rescue Recurly::API::NotFound
52
+ # # Handle potential tampering here.
53
+ # end
54
+ def fetch token
55
+ Resource.from_response API.get "recurly_js/result/#{token}"
65
56
  end
66
57
 
67
58
  # @return [String]
@@ -71,61 +62,14 @@ module Recurly
71
62
 
72
63
  private
73
64
 
74
- def collect_keypaths extras, prefix = nil
75
- if extras.is_a? Hash
76
- extras.map { |key, value|
77
- collect_keypaths value, prefix ? "#{prefix}.#{key}" : key.to_s
78
- }.flatten.sort
79
- else
80
- prefix
81
- end
82
- end
83
-
84
- def sign claim, params, extras = {}, timestamp = Time.now
85
- hexdigest = OpenSSL::HMAC.hexdigest(
86
- OpenSSL::Digest::Digest.new('SHA1'),
87
- Digest::SHA1.digest(private_key),
88
- digest([timestamp = timestamp.to_i, claim, params.merge(extras)])
89
- )
90
- ["#{hexdigest}-#{timestamp}", *collect_keypaths(extras)].join '+'
91
- end
92
-
93
- def verify! claim, params
94
- params = Hash[params.map { |key, value| [key.to_s, value] }]
95
- signature = params.delete('signature') or raise(
96
- RequestForgery, 'missing signature'
97
- )
98
- timestamp = signature.split('-').last
99
- age = Time.now.to_i - timestamp.to_i
100
- unless (-3600..3600).include? age
101
- raise RequestForgery, 'stale timestamp'
102
- end
103
-
104
- if signature != sign(claim, params, {}, timestamp)
105
- raise RequestForgery,
106
- "signature can't be verified (invalid request or private key)"
107
- end
108
-
109
- true
110
- end
111
-
112
- def digest data
113
- case data
114
- when Array
115
- return if data.empty?
116
- SafeString.new "[#{data.map { |d| digest d }.compact.join ','}]"
65
+ def to_query object, key = nil
66
+ case object
117
67
  when Hash
118
- data = Hash[data.map { |key, value| [key.to_s, value] }]
119
- digest data.keys.sort.map { |key|
120
- next unless value = digest(data[key])
121
- SafeString.new "#{"#{key}:" unless key =~ /^\d+$/}#{value}"
122
- }
123
- when SafeString
124
- data
125
- when String
126
- SafeString.new data.gsub(/([\[\]\,\:\\])/, '\\\\\1')
68
+ object.map { |k, v| to_query v, key ? "#{key}[#{k}]" : k }.sort * '&'
69
+ when Array
70
+ object.map { |o| to_query o, "#{key}[]" } * '&'
127
71
  else
128
- data
72
+ "#{CGI.escape key.to_s}=#{CGI.escape object.to_s}"
129
73
  end
130
74
  end
131
75
  end
@@ -1,5 +1,4 @@
1
1
  require 'date'
2
- require 'erb'
3
2
 
4
3
  module Recurly
5
4
  # The base class for all Recurly resources (e.g. {Account}, {Subscription},
@@ -184,7 +183,6 @@ module Recurly
184
183
  # Recurly::Account.member_path "code" # => "accounts/code"
185
184
  # Recurly::Account.member_path nil # => "accounts"
186
185
  def member_path uuid
187
- uuid = ERB::Util.url_encode(uuid) if uuid
188
186
  [collection_path, uuid].compact.join '/'
189
187
  end
190
188
 
@@ -321,8 +319,9 @@ module Recurly
321
319
  request_options[:head] = { 'If-None-Match' => etag }
322
320
  end
323
321
 
322
+ uri = uuid =~ /^http/ ? uuid : member_path(uuid)
324
323
  begin
325
- from_response API.get(member_path(uuid), {}, options)
324
+ from_response API.get(uri, {}, request_options)
326
325
  rescue API::NotFound => e
327
326
  raise NotFound, e.description
328
327
  end
@@ -371,7 +370,7 @@ module Recurly
371
370
  if xml.name == member_name
372
371
  record = new
373
372
  elsif Recurly.const_defined?(class_name = Helper.classify(xml.name))
374
- record = Recurly.const_get(class_name).new
373
+ record = Recurly.const_get(class_name).send :new
375
374
  elsif root = xml.root and root.elements.empty?
376
375
  return XML.cast root
377
376
  else
@@ -478,7 +477,7 @@ module Recurly
478
477
  attributes = args.shift || {}
479
478
  self[member_name] = associated.send(
480
479
  :new, attributes.merge(:uri => "#{path}/#{member_name}")
481
- )
480
+ ).tap { |child| child.attributes[self.class.member_name] = self }
482
481
  }
483
482
  define_method("create_#{member_name}") { |*args|
484
483
  send("build_#{member_name}", *args).tap { |child| child.save }
@@ -552,12 +551,9 @@ module Recurly
552
551
  return if response.body.length.zero?
553
552
  fresh = self.class.from_response response
554
553
  else
555
- options = {:etag => (etag unless changed?)}
556
- fresh = if @href
557
- self.class.from_response API.get(@href, {}, options)
558
- else
559
- self.class.find(to_param, options)
560
- end
554
+ fresh = self.class.find(
555
+ @href || to_param, :etag => (etag unless changed?)
556
+ )
561
557
  end
562
558
  fresh and copy_from fresh
563
559
  persist! true
@@ -845,6 +841,10 @@ module Recurly
845
841
  raise NotFound, e.description
846
842
  end
847
843
 
844
+ def signable_attributes
845
+ Hash[xml_keys.map { |key| [key, self[key]] }]
846
+ end
847
+
848
848
  def == other
849
849
  other.is_a?(self.class) && other.to_s == to_s
850
850
  end
@@ -88,6 +88,12 @@ module Recurly
88
88
  load_from links['next'], nil if links.key? 'next'
89
89
  end
90
90
 
91
+ # @return [Array, nil] Refreshes the pager's collection of records with
92
+ # the previous page.
93
+ def prev
94
+ load_from links['prev'], nil if links.key? 'prev'
95
+ end
96
+
91
97
  # @return [Array, nil] Refreshes the pager's collection of records with
92
98
  # the first page.
93
99
  def start
@@ -211,7 +217,7 @@ module Recurly
211
217
  load! unless defined? @collection
212
218
  return @collection.send name, *args, &block
213
219
  end
214
-
220
+
215
221
  super
216
222
  end
217
223
  end
@@ -38,6 +38,7 @@ module Recurly
38
38
  pending_subscription
39
39
  subscription_add_ons
40
40
  coupon_code
41
+ total_billing_cycles
41
42
  )
42
43
  alias to_param uuid
43
44
 
@@ -134,5 +135,9 @@ module Recurly
134
135
  reload self[:reactivate].call
135
136
  true
136
137
  end
138
+
139
+ def signable_attributes
140
+ super.merge :plan_code => plan_code
141
+ end
137
142
  end
138
143
  end
@@ -70,6 +70,10 @@ module Recurly
70
70
  )
71
71
  end
72
72
 
73
+ def signable_attributes
74
+ super.merge :amount_in_cents => amount_in_cents, :currency => currency
75
+ end
76
+
73
77
  # @return [String]
74
78
  def inspect
75
79
  attributes = self.class.attribute_names
@@ -1,8 +1,8 @@
1
1
  module Recurly
2
2
  module Version
3
3
  MAJOR = 2
4
- MINOR = 0
5
- PATCH = 14
4
+ MINOR = 1
5
+ PATCH = 0
6
6
  PRE = nil
7
7
 
8
8
  VERSION = [MAJOR, MINOR, PATCH, PRE].compact.join('.').freeze
metadata CHANGED
@@ -1,58 +1,83 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: recurly
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.14
4
+ version: 2.1.0
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Recurly
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2018-05-09 00:00:00.000000000 Z
12
+ date: 2012-03-12 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: rake
15
- requirement: !ruby/object:Gem::Requirement
16
+ requirement: &70249525629500 !ruby/object:Gem::Requirement
17
+ none: false
16
18
  requirements:
17
- - - "~>"
19
+ - - ~>
18
20
  - !ruby/object:Gem::Version
19
21
  version: 0.9.2
20
22
  type: :development
21
23
  prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: 0.9.2
24
+ version_requirements: *70249525629500
27
25
  - !ruby/object:Gem::Dependency
28
26
  name: minitest
29
- requirement: !ruby/object:Gem::Requirement
27
+ requirement: &70249525628300 !ruby/object:Gem::Requirement
28
+ none: false
30
29
  requirements:
31
- - - "~>"
30
+ - - ~>
32
31
  - !ruby/object:Gem::Version
33
32
  version: 2.6.1
34
33
  type: :development
35
34
  prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: 2.6.1
35
+ version_requirements: *70249525628300
41
36
  - !ruby/object:Gem::Dependency
42
37
  name: webmock
43
- requirement: !ruby/object:Gem::Requirement
38
+ requirement: &70249525627820 !ruby/object:Gem::Requirement
39
+ none: false
44
40
  requirements:
45
- - - "~>"
41
+ - - ~>
46
42
  - !ruby/object:Gem::Version
47
43
  version: 1.7.6
48
44
  type: :development
49
45
  prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
46
+ version_requirements: *70249525627820
47
+ - !ruby/object:Gem::Dependency
48
+ name: guard-minitest
49
+ requirement: &70249525627320 !ruby/object:Gem::Requirement
50
+ none: false
51
51
  requirements:
52
- - - "~>"
52
+ - - ~>
53
53
  - !ruby/object:Gem::Version
54
- version: 1.7.6
55
- description: 'An API client library for Recurly: http://recurly.com'
54
+ version: 0.4.0
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70249525627320
58
+ - !ruby/object:Gem::Dependency
59
+ name: rb-fsevent
60
+ requirement: &70249525626760 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 0.4.3.1
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70249525626760
69
+ - !ruby/object:Gem::Dependency
70
+ name: growl_notify
71
+ requirement: &70249525626280 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 0.0.1
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70249525626280
80
+ description: ! 'An API client library for Recurly: http://recurly.com'
56
81
  email: support@recurly.com
57
82
  executables:
58
83
  - recurly
@@ -60,18 +85,15 @@ extensions: []
60
85
  extra_rdoc_files:
61
86
  - README.markdown
62
87
  files:
63
- - README.markdown
64
- - bin/recurly
65
88
  - lib/rails/generators/recurly/config_generator.rb
66
89
  - lib/rails/recurly.rb
67
- - lib/recurly.rb
68
90
  - lib/recurly/account.rb
69
91
  - lib/recurly/add_on.rb
70
92
  - lib/recurly/adjustment.rb
71
93
  - lib/recurly/all.rb
72
- - lib/recurly/api.rb
73
94
  - lib/recurly/api/errors.rb
74
95
  - lib/recurly/api/net_http_adapter.rb
96
+ - lib/recurly/api.rb
75
97
  - lib/recurly/billing_info.rb
76
98
  - lib/recurly/coupon.rb
77
99
  - lib/recurly/helper.rb
@@ -80,40 +102,46 @@ files:
80
102
  - lib/recurly/money.rb
81
103
  - lib/recurly/plan.rb
82
104
  - lib/recurly/redemption.rb
83
- - lib/recurly/resource.rb
84
105
  - lib/recurly/resource/pager.rb
85
- - lib/recurly/subscription.rb
106
+ - lib/recurly/resource.rb
86
107
  - lib/recurly/subscription/add_ons.rb
87
- - lib/recurly/transaction.rb
108
+ - lib/recurly/subscription.rb
88
109
  - lib/recurly/transaction/errors.rb
110
+ - lib/recurly/transaction.rb
89
111
  - lib/recurly/version.rb
90
- - lib/recurly/xml.rb
91
112
  - lib/recurly/xml/nokogiri.rb
92
113
  - lib/recurly/xml/rexml.rb
114
+ - lib/recurly/xml.rb
115
+ - lib/recurly.rb
116
+ - README.markdown
117
+ - !binary |-
118
+ YmluL3JlY3VybHk=
93
119
  homepage: http://github.com/recurly/recurly-client-ruby
94
120
  licenses:
95
121
  - MIT
96
- metadata: {}
97
122
  post_install_message:
98
123
  rdoc_options:
99
- - "--main"
124
+ - --main
100
125
  - README.markdown
101
126
  require_paths:
102
127
  - lib
103
128
  required_ruby_version: !ruby/object:Gem::Requirement
129
+ none: false
104
130
  requirements:
105
- - - ">="
131
+ - - ! '>='
106
132
  - !ruby/object:Gem::Version
107
133
  version: '0'
108
134
  required_rubygems_version: !ruby/object:Gem::Requirement
135
+ none: false
109
136
  requirements:
110
- - - ">="
137
+ - - ! '>='
111
138
  - !ruby/object:Gem::Version
112
139
  version: '0'
113
140
  requirements: []
114
141
  rubyforge_project:
115
- rubygems_version: 2.7.6
142
+ rubygems_version: 1.8.11
116
143
  signing_key:
117
- specification_version: 4
144
+ specification_version: 3
118
145
  summary: Recurly API Client
119
146
  test_files: []
147
+ has_rdoc: true
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA256:
3
- metadata.gz: b504b0f597b977a0b15913cfa563e756f2668f3ccdd9bdd24b5070ac1921039c
4
- data.tar.gz: 52b63c17d628a0775239bfa043b4395e8caa1aef4f2e3cb21e97fc07cb4c20d2
5
- SHA512:
6
- metadata.gz: 5c3b9ca3efb027f16030f60c6cc5e4e8994b8a514fcb717d3b7fee675c3a405a569634c287953c5c760fc73df81c7d5c71abd8543f0da5245c4bb462089a438e
7
- data.tar.gz: 953ae522e3f47bc0fee546ed9aab5c8ee252f1c337d65491a966e7ab3a13b730f7cd8fc707868ea4aa94b21baa44c4ba9b394c15a7780ba63bebaad409800612