paypal-sdk-rest 0.10.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/README.md +44 -0
  4. data/lib/generators/paypal/sdk/USAGE +3 -0
  5. data/lib/generators/paypal/sdk/install_generator.rb +17 -0
  6. data/lib/generators/paypal/sdk/templates/paypal.rb +2 -0
  7. data/lib/generators/paypal/sdk/templates/paypal.yml +31 -0
  8. data/lib/paypal-sdk-core.rb +38 -0
  9. data/lib/paypal-sdk/core/api.rb +20 -0
  10. data/lib/paypal-sdk/core/api/base.rb +162 -0
  11. data/lib/paypal-sdk/core/api/data_types/array_with_block.rb +44 -0
  12. data/lib/paypal-sdk/core/api/data_types/base.rb +224 -0
  13. data/lib/paypal-sdk/core/api/data_types/enum.rb +26 -0
  14. data/lib/paypal-sdk/core/api/data_types/simple_types.rb +52 -0
  15. data/lib/paypal-sdk/core/api/ipn.rb +66 -0
  16. data/lib/paypal-sdk/core/api/rest.rb +163 -0
  17. data/lib/paypal-sdk/core/authentication.rb +66 -0
  18. data/lib/paypal-sdk/core/config.rb +249 -0
  19. data/lib/paypal-sdk/core/credential.rb +16 -0
  20. data/lib/paypal-sdk/core/credential/base.rb +27 -0
  21. data/lib/paypal-sdk/core/credential/certificate.rb +32 -0
  22. data/lib/paypal-sdk/core/credential/signature.rb +22 -0
  23. data/lib/paypal-sdk/core/credential/third_party/subject.rb +25 -0
  24. data/lib/paypal-sdk/core/credential/third_party/token.rb +39 -0
  25. data/lib/paypal-sdk/core/exceptions.rb +96 -0
  26. data/lib/paypal-sdk/core/logging.rb +45 -0
  27. data/lib/paypal-sdk/core/openid_connect.rb +122 -0
  28. data/lib/paypal-sdk/core/openid_connect/api.rb +49 -0
  29. data/lib/paypal-sdk/core/openid_connect/data_types.rb +73 -0
  30. data/lib/paypal-sdk/core/openid_connect/get_api.rb +28 -0
  31. data/lib/paypal-sdk/core/openid_connect/request_data_type.rb +52 -0
  32. data/lib/paypal-sdk/core/openid_connect/set_api.rb +36 -0
  33. data/lib/paypal-sdk/core/util.rb +11 -0
  34. data/lib/paypal-sdk/core/util/http_helper.rb +159 -0
  35. data/lib/paypal-sdk/core/util/oauth_signature.rb +64 -0
  36. data/lib/paypal-sdk/core/util/ordered_hash.rb +165 -0
  37. data/lib/paypal-sdk/rest/data_types.rb +1 -0
  38. data/lib/paypal-sdk/rest/version.rb +1 -1
  39. data/spec/config/paypal.yml +27 -0
  40. data/spec/config/sample_data.yml +3 -0
  41. data/spec/core/api/data_type_spec.rb +189 -0
  42. data/spec/core/api/rest_spec.rb +147 -0
  43. data/spec/core/config_spec.rb +192 -0
  44. data/spec/core/logging_spec.rb +28 -0
  45. data/spec/core/openid_connect_spec.rb +144 -0
  46. data/spec/log/http.log +71 -32
  47. data/spec/log/rest_http.log +133 -0
  48. data/spec/spec_helper.rb +7 -0
  49. data/spec/support/sample_data.rb +5 -0
  50. metadata +82 -5
@@ -0,0 +1,73 @@
1
+ require 'paypal-sdk-core'
2
+
3
+ module PayPal::SDK::Core
4
+ module OpenIDConnect
5
+ module DataTypes
6
+ class Base < PayPal::SDK::Core::API::DataTypes::Base
7
+ end
8
+
9
+ class Address < Base
10
+ def self.load_members
11
+ object_of :street_address, String
12
+ object_of :locality, String
13
+ object_of :region, String
14
+ object_of :postal_code, String
15
+ object_of :country, String
16
+ end
17
+ end
18
+
19
+ class Userinfo < Base
20
+ def self.load_members
21
+ object_of :user_id, String
22
+ object_of :sub, String
23
+ object_of :name, String
24
+ object_of :given_name, String
25
+ object_of :family_name, String
26
+ object_of :middle_name, String
27
+ object_of :picture, String
28
+ object_of :email, String
29
+ object_of :email_verified, Boolean
30
+ object_of :gender, String
31
+ object_of :birthday, String
32
+ object_of :zoneinfo, String
33
+ object_of :locale, String
34
+ object_of :language, String
35
+ object_of :verified, Boolean
36
+ object_of :phone_number, String
37
+ object_of :address, Address
38
+ object_of :verified_account, Boolean
39
+ object_of :account_type, String
40
+ object_of :account_creation_date, String
41
+ object_of :age_range, String
42
+ object_of :payer_id, String
43
+ end
44
+ end
45
+
46
+ class Tokeninfo < Base
47
+ def self.load_members
48
+ object_of :scope, String
49
+ object_of :access_token, String
50
+ object_of :refresh_token, String
51
+ object_of :token_type, String
52
+ object_of :id_token, String
53
+ object_of :expires_in, Integer
54
+ end
55
+ end
56
+
57
+ class Error < Base
58
+ def self.load_members
59
+ object_of :error, String
60
+ object_of :error_description, String
61
+ object_of :error_uri, String
62
+ end
63
+ end
64
+
65
+
66
+ constants.each do |data_type_klass|
67
+ data_type_klass = const_get(data_type_klass)
68
+ data_type_klass.load_members if defined? data_type_klass.load_members
69
+ end
70
+
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,28 @@
1
+ module PayPal::SDK
2
+ module Core
3
+ module OpenIDConnect
4
+ module GetAPI
5
+ # Get API object
6
+ # === Example
7
+ # Payment.api
8
+ # payment.api
9
+ def api
10
+ @api || parent_api
11
+ end
12
+
13
+ # Parent API object
14
+ def parent_api
15
+ superclass.respond_to?(:api) ? superclass.api : RequestDataType.api
16
+ end
17
+
18
+ def client_id
19
+ api.config.openid_client_id || api.config.client_id
20
+ end
21
+
22
+ def client_secret
23
+ api.config.openid_client_secret || api.config.client_secret
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,52 @@
1
+ module PayPal::SDK
2
+ module Core
3
+ module OpenIDConnect
4
+ module RequestDataType
5
+
6
+ # Get a local API object or Class level API object
7
+ def api
8
+ @api || self.class.api
9
+ end
10
+
11
+ class << self
12
+ # Global API object
13
+ # === Example
14
+ # RequestDataType.api
15
+ def api
16
+ @api ||= API.new({})
17
+ end
18
+
19
+ def client_id
20
+ api.config.openid_client_id || api.config.client_id
21
+ end
22
+
23
+ def client_secret
24
+ api.config.openid_client_secret || api.config.client_secret
25
+ end
26
+
27
+ # Setter for RequestDataType.api
28
+ # === Example
29
+ # RequestDataType.set_config(..)
30
+ include SetAPI
31
+
32
+ # Configure depended module, when RequestDataType is include.
33
+ # === Example
34
+ # class Payment < DataTypes
35
+ # include RequestDataType
36
+ # end
37
+ # Payment.set_config(..)
38
+ # payment.set_config(..)
39
+ # Payment.api
40
+ # payment.api
41
+ def included(klass)
42
+ klass.class_eval do
43
+ extend GetAPI
44
+ extend SetAPI
45
+ include SetAPI
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,36 @@
1
+ module PayPal::SDK
2
+ module Core
3
+ module OpenIDConnect
4
+ module SetAPI
5
+ # Set new api
6
+ # === Examples
7
+ # payment.set_config(:development)
8
+ # payment.set_config(:client_id => "XYZ", :client_secret => "SECRET")
9
+ # payment.set_config
10
+ # payment.api = API.new(:development)
11
+ def set_config(*args)
12
+ if args[0].is_a?(API)
13
+ @api = args[0]
14
+ else
15
+ @api ||= API.new({})
16
+ @api.set_config(*args) # Just override the configuration and Not
17
+ @api
18
+ end
19
+ end
20
+ alias_method :config=, :set_config
21
+ alias_method :set_api, :set_config
22
+ alias_method :api=, :set_config
23
+
24
+ # Override client id
25
+ def client_id=(client_id)
26
+ set_config(:client_id => client_id)
27
+ end
28
+
29
+ # Override client secret
30
+ def client_secret=(client_secret)
31
+ set_config(:client_secret => client_secret)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,11 @@
1
+ module PayPal
2
+ module SDK
3
+ module Core
4
+ module Util
5
+ autoload :OauthSignature, "paypal-sdk/core/util/oauth_signature"
6
+ autoload :OrderedHash, "paypal-sdk/core/util/ordered_hash"
7
+ autoload :HTTPHelper, "paypal-sdk/core/util/http_helper"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,159 @@
1
+ require 'net/https'
2
+ require 'uri'
3
+ require 'cgi'
4
+
5
+ module PayPal::SDK::Core
6
+ module Util
7
+ module HTTPHelper
8
+
9
+ include Configuration
10
+ include Logging
11
+ include Authentication
12
+ include Exceptions
13
+
14
+ # Create HTTP connection based on given service name or configured end point
15
+ def create_http_connection(uri)
16
+ new_http(uri).tap do |http|
17
+ if config.http_timeout
18
+ http.open_timeout = config.http_timeout
19
+ http.read_timeout = config.http_timeout
20
+ end
21
+ configure_ssl(http) if uri.scheme == "https"
22
+ end
23
+ end
24
+
25
+ # New raw HTTP object
26
+ def new_http(uri)
27
+ if config.http_proxy
28
+ proxy = URI.parse(config.http_proxy)
29
+ Net::HTTP.new(uri.host, uri.port, proxy.host, proxy.port, proxy.user, proxy.password)
30
+ else
31
+ Net::HTTP.new(uri.host, uri.port)
32
+ end
33
+ end
34
+
35
+ # Default ca file
36
+ def default_ca_file
37
+ File.expand_path("../../../../../data/paypal.crt", __FILE__)
38
+ end
39
+
40
+ # Apply ssl configuration to http object
41
+ def configure_ssl(http)
42
+ http.tap do |https|
43
+ https.use_ssl = true
44
+ https.ca_file = default_ca_file
45
+ https.verify_mode = OpenSSL::SSL::VERIFY_PEER
46
+ config.ssl_options.each do |key, value|
47
+ http.send("#{key}=", value)
48
+ end
49
+ add_certificate(https)
50
+ end
51
+ end
52
+
53
+ # Join url
54
+ def url_join(path, action)
55
+ path.sub(/\/?$/, "/#{action}")
56
+ end
57
+
58
+ # Make Http call
59
+ # * payload - Hash(:http, :method, :uri, :body, :header)
60
+ def http_call(payload)
61
+ if Config.config.verbose_logging
62
+ logger.info payload.inspect
63
+ end
64
+
65
+ response =
66
+ log_http_call(payload) do
67
+ http = payload[:http] || create_http_connection(payload[:uri])
68
+ http.start do |session|
69
+ if [ :get, :delete, :head ].include? payload[:method]
70
+ session.send(payload[:method], payload[:uri].request_uri, payload[:header])
71
+ else
72
+ session.send(payload[:method], payload[:uri].request_uri, payload[:body], payload[:header])
73
+ end
74
+ end
75
+ end
76
+
77
+ if Config.config.verbose_logging
78
+ if response.code.to_i == 200
79
+ logger.info(response.body)
80
+ else
81
+ logger.warn(response.body)
82
+ end
83
+ end
84
+
85
+ handle_response(response)
86
+ end
87
+
88
+ # Log Http call
89
+ # * payload - Hash(:http, :method, :uri, :body, :header)
90
+ def log_http_call(payload)
91
+ logger.info "Request[#{payload[:method]}]: #{payload[:uri].to_s}"
92
+ start_time = Time.now
93
+ response = yield
94
+ logger.info sprintf("Response[%s]: %s, Duration: %.3fs", response.code,
95
+ response.message, Time.now - start_time)
96
+ response
97
+ end
98
+
99
+ # Generate header based on given header keys and properties
100
+ # === Arguments
101
+ # * <tt>header_keys</tt> -- List of Header keys for the properties
102
+ # * <tt>properties</tt> -- properties
103
+ # === Return
104
+ # Hash with header as key property as value
105
+ # === Example
106
+ # map_header_value( { :username => "X-PAYPAL-USERNAME"}, { :username => "guest" })
107
+ # # Return: { "X-PAYPAL-USERNAME" => "guest" }
108
+ def map_header_value(header_keys, properties)
109
+ header = {}
110
+ properties.each do |key, value|
111
+ key = header_keys[key]
112
+ header[key] = value.to_s if key and value
113
+ end
114
+ header
115
+ end
116
+
117
+ def encode_www_form(hash)
118
+ if defined? URI.encode_www_form
119
+ URI.encode_www_form(hash)
120
+ else
121
+ hash.map{|key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}" }.join("&")
122
+ end
123
+ end
124
+
125
+ # Handles response and error codes from the remote service.
126
+ def handle_response(response)
127
+ case response.code.to_i
128
+ when 301, 302, 303, 307
129
+ raise(Redirection.new(response))
130
+ when 200...400
131
+ response
132
+ when 400
133
+ raise(BadRequest.new(response))
134
+ when 401
135
+ raise(UnauthorizedAccess.new(response))
136
+ when 403
137
+ raise(ForbiddenAccess.new(response))
138
+ when 404
139
+ raise(ResourceNotFound.new(response))
140
+ when 405
141
+ raise(MethodNotAllowed.new(response))
142
+ when 409
143
+ raise(ResourceConflict.new(response))
144
+ when 410
145
+ raise(ResourceGone.new(response))
146
+ when 422
147
+ raise(ResourceInvalid.new(response))
148
+ when 401...500
149
+ raise(ClientError.new(response))
150
+ when 500...600
151
+ raise(ServerError.new(response))
152
+ else
153
+ raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
154
+ end
155
+ end
156
+
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,64 @@
1
+ require 'uri'
2
+ require 'cgi'
3
+ require 'openssl'
4
+ require 'base64'
5
+
6
+ module PayPal::SDK::Core
7
+ module Util
8
+ class OauthSignature
9
+ attr_accessor :username, :password, :token, :token_secret, :url, :timestamp
10
+
11
+ def initialize(username, password, token, token_secret, url, timestamp = nil)
12
+ @username = username
13
+ @password = password
14
+ @token = token
15
+ @token_secret = token_secret
16
+ @url = url
17
+ @timestamp = timestamp || Time.now.to_i.to_s
18
+ end
19
+
20
+ def authorization_string
21
+ signature = oauth_signature
22
+ "token=#{token},signature=#{signature},timestamp=#{timestamp}"
23
+ end
24
+
25
+ def oauth_signature
26
+ key = [
27
+ paypal_encode(password),
28
+ paypal_encode(token_secret),
29
+ ].join("&").gsub(/%[0-9A-F][0-9A-F]/, &:downcase )
30
+
31
+ digest = OpenSSL::HMAC.digest('sha1', key, base_string)
32
+ Base64.encode64(digest).chomp
33
+ end
34
+
35
+ def base_string
36
+ params = {
37
+ "oauth_consumer_key" => username,
38
+ "oauth_version" => "1.0",
39
+ "oauth_signature_method" => "HMAC-SHA1",
40
+ "oauth_token" => token,
41
+ "oauth_timestamp" => timestamp,
42
+ }
43
+ sorted_query_string = params.sort.map{|v| v.join("=") }.join("&")
44
+
45
+ base = [
46
+ "POST",
47
+ paypal_encode(url),
48
+ paypal_encode(sorted_query_string)
49
+ ].join("&")
50
+ base = base.gsub(/%[0-9A-F][0-9A-F]/, &:downcase )
51
+ end
52
+
53
+ # The PayPalURLEncoder java class percent encodes everything other than 'a-zA-Z0-9 _'.
54
+ # Then it converts ' ' to '+'.
55
+ # Ruby's CGI.encode takes care of the ' ' and '*' to satisfy PayPal
56
+ # (but beware, URI.encode percent encodes spaces, and does nothing with '*').
57
+ # Finally, CGI.encode does not encode '.-', which we need to do here.
58
+ def paypal_encode str
59
+ CGI.escape(str.to_s).gsub('.', '%2E').gsub('-', '%2D')
60
+ end
61
+ end
62
+ end
63
+ end
64
+
@@ -0,0 +1,165 @@
1
+ module PayPal::SDK::Core
2
+ module Util
3
+ class OrderedHash < ::Hash #:nodoc:
4
+
5
+ def to_yaml_type
6
+ "!tag:yaml.org,2002:map"
7
+ end
8
+
9
+ # Hash is ordered in Ruby 1.9!
10
+ if RUBY_VERSION < '1.9'
11
+
12
+ # In MRI the Hash class is core and written in C. In particular, methods are
13
+ # programmed with explicit C function calls and polymorphism is not honored.
14
+ #
15
+ # For example, []= is crucial in this implementation to maintain the @keys
16
+ # array but hash.c invokes rb_hash_aset() originally. This prevents method
17
+ # reuse through inheritance and forces us to reimplement stuff.
18
+ #
19
+ # For instance, we cannot use the inherited #merge! because albeit the algorithm
20
+ # itself would work, our []= is not being called at all by the C code.
21
+
22
+ def initialize(*args, &block)
23
+ super
24
+ @keys = []
25
+ end
26
+
27
+ def self.[](*args)
28
+ ordered_hash = new
29
+
30
+ if (args.length == 1 && args.first.is_a?(Array))
31
+ args.first.each do |key_value_pair|
32
+ next unless (key_value_pair.is_a?(Array))
33
+ ordered_hash[key_value_pair[0]] = key_value_pair[1]
34
+ end
35
+
36
+ return ordered_hash
37
+ end
38
+
39
+ unless (args.size % 2 == 0)
40
+ raise ArgumentError.new("odd number of arguments for Hash")
41
+ end
42
+
43
+ args.each_with_index do |val, ind|
44
+ next if (ind % 2 != 0)
45
+ ordered_hash[val] = args[ind + 1]
46
+ end
47
+
48
+ ordered_hash
49
+ end
50
+
51
+ def initialize_copy(other)
52
+ super
53
+ # make a deep copy of keys
54
+ @keys = other.keys
55
+ end
56
+
57
+ def []=(key, value)
58
+ @keys << key if !has_key?(key)
59
+ super
60
+ end
61
+
62
+ def delete(key)
63
+ if has_key? key
64
+ index = @keys.index(key)
65
+ @keys.delete_at index
66
+ end
67
+ super
68
+ end
69
+
70
+ def delete_if
71
+ super
72
+ sync_keys!
73
+ self
74
+ end
75
+
76
+ def reject!
77
+ super
78
+ sync_keys!
79
+ self
80
+ end
81
+
82
+ def reject(&block)
83
+ dup.reject!(&block)
84
+ end
85
+
86
+ def keys
87
+ @keys.dup
88
+ end
89
+
90
+ def values
91
+ @keys.collect { |key| self[key] }
92
+ end
93
+
94
+ def to_hash
95
+ self
96
+ end
97
+
98
+ def to_a
99
+ @keys.map { |key| [ key, self[key] ] }
100
+ end
101
+
102
+ def each_key
103
+ @keys.each { |key| yield key }
104
+ end
105
+
106
+ def each_value
107
+ @keys.each { |key| yield self[key]}
108
+ end
109
+
110
+ def each
111
+ @keys.each {|key| yield [key, self[key]]}
112
+ end
113
+
114
+ alias_method :each_pair, :each
115
+
116
+ def clear
117
+ super
118
+ @keys.clear
119
+ self
120
+ end
121
+
122
+ def shift
123
+ k = @keys.first
124
+ v = delete(k)
125
+ [k, v]
126
+ end
127
+
128
+ def merge!(other_hash)
129
+ if block_given?
130
+ other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
131
+ else
132
+ other_hash.each { |k, v| self[k] = v }
133
+ end
134
+ self
135
+ end
136
+
137
+ alias_method :update, :merge!
138
+
139
+ def merge(other_hash, &block)
140
+ dup.merge!(other_hash, &block)
141
+ end
142
+
143
+ # When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
144
+ def replace(other)
145
+ super
146
+ @keys = other.keys
147
+ self
148
+ end
149
+
150
+ def invert
151
+ OrderedHash[self.to_a.map!{|key_value_pair| key_value_pair.reverse}]
152
+ end
153
+
154
+ def inspect
155
+ "#<OrderedHash #{super}>"
156
+ end
157
+
158
+ private
159
+ def sync_keys!
160
+ @keys.delete_if {|k| !has_key?(k)}
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end