spark_api 1.0.2 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/README.md +86 -3
  2. data/VERSION +1 -1
  3. data/lib/spark_api.rb +1 -0
  4. data/lib/spark_api/authentication/oauth2.rb +43 -2
  5. data/lib/spark_api/configuration/oauth2_configurable.rb +3 -1
  6. data/lib/spark_api/models.rb +1 -0
  7. data/lib/spark_api/models/listing.rb +17 -1
  8. data/lib/spark_api/models/rental_calendar.rb +26 -0
  9. data/lib/spark_api/options_hash.rb +18 -0
  10. data/script/combined_flow_example.rb +55 -0
  11. data/script/oauth2_example.rb +2 -2
  12. data/spec/fixtures/listings/rental_calendar.json +19 -0
  13. data/spec/fixtures/listings/with_rental_calendar.json +52 -0
  14. data/spec/unit/spark_api/authentication/oauth2_spec.rb +79 -1
  15. data/spec/unit/spark_api/models/listing_spec.rb +7 -0
  16. data/spec/unit/spark_api/models/rental_calendar_spec.rb +30 -0
  17. data/spec/unit/spark_api/options_hash_spec.rb +14 -0
  18. metadata +15 -96
  19. data/bin/spark_api~ +0 -8
  20. data/lib/spark_api/authentication/api_auth.rb~ +0 -104
  21. data/lib/spark_api/authentication/base_auth.rb~ +0 -47
  22. data/lib/spark_api/authentication/oauth2.rb~ +0 -199
  23. data/lib/spark_api/authentication/oauth2_impl/grant_type_base.rb~ +0 -87
  24. data/lib/spark_api/authentication/oauth2_impl/grant_type_code.rb~ +0 -49
  25. data/lib/spark_api/authentication/oauth2_impl/grant_type_password.rb~ +0 -45
  26. data/lib/spark_api/authentication/oauth2_impl/grant_type_refresh.rb~ +0 -36
  27. data/lib/spark_api/authentication/oauth2_impl/middleware.rb~ +0 -39
  28. data/lib/spark_api/authentication/oauth2_impl/password_provider.rb~ +0 -25
  29. data/lib/spark_api/cli.rb~ +0 -158
  30. data/lib/spark_api/cli/api_auth.rb~ +0 -8
  31. data/lib/spark_api/cli/oauth2.rb~ +0 -14
  32. data/lib/spark_api/cli/setup.rb~ +0 -47
  33. data/lib/spark_api/configuration.rb~ +0 -54
  34. data/lib/spark_api/configuration/yaml.rb~ +0 -101
  35. data/lib/spark_api/faraday.rb~ +0 -64
  36. data/lib/spark_api/models.rb~ +0 -33
  37. data/lib/spark_api/models/account.rb~ +0 -115
  38. data/lib/spark_api/models/base.rb~ +0 -118
  39. data/lib/spark_api/models/connect_prefs.rb~ +0 -10
  40. data/lib/spark_api/models/constraint.rb~ +0 -16
  41. data/lib/spark_api/models/contact.rb~ +0 -49
  42. data/lib/spark_api/models/custom_fields.rb~ +0 -12
  43. data/lib/spark_api/models/document.rb~ +0 -11
  44. data/lib/spark_api/models/finders.rb~ +0 -45
  45. data/lib/spark_api/models/idx_link.rb~ +0 -47
  46. data/lib/spark_api/models/listing.rb~ +0 -197
  47. data/lib/spark_api/models/listing_cart.rb~ +0 -72
  48. data/lib/spark_api/models/market_statistics.rb~ +0 -33
  49. data/lib/spark_api/models/message.rb~ +0 -21
  50. data/lib/spark_api/models/note.rb~ +0 -41
  51. data/lib/spark_api/models/notification.rb~ +0 -42
  52. data/lib/spark_api/models/open_house.rb~ +0 -24
  53. data/lib/spark_api/models/photo.rb~ +0 -70
  54. data/lib/spark_api/models/property_types.rb~ +0 -7
  55. data/lib/spark_api/models/saved_search.rb~ +0 -16
  56. data/lib/spark_api/models/shared_listing.rb~ +0 -35
  57. data/lib/spark_api/models/standard_fields.rb~ +0 -50
  58. data/lib/spark_api/models/subresource.rb~ +0 -19
  59. data/lib/spark_api/models/system_info.rb~ +0 -14
  60. data/lib/spark_api/models/tour_of_home.rb~ +0 -24
  61. data/lib/spark_api/models/video.rb~ +0 -16
  62. data/lib/spark_api/models/virtual_tour.rb~ +0 -18
  63. data/lib/spark_api/multi_client.rb~ +0 -59
  64. data/lib/spark_api/paginate.rb~ +0 -109
  65. data/lib/spark_api/primary_array.rb~ +0 -29
  66. data/lib/spark_api/request.rb~ +0 -96
  67. data/lib/spark_api/response.rb~ +0 -70
  68. data/lib/spark_api/version.rb~ +0 -4
  69. data/script/console~ +0 -6
  70. data/script/example.rb~ +0 -27
  71. data/spec/unit/flexmls_api_spec.rb~ +0 -23
  72. data/spec/unit/spark_api/authentication/api_auth_spec.rb~ +0 -169
  73. data/spec/unit/spark_api/authentication/base_auth_spec.rb~ +0 -10
  74. data/spec/unit/spark_api/authentication/oauth2_impl/grant_type_base_spec.rb~ +0 -10
  75. data/spec/unit/spark_api/authentication/oauth2_spec.rb~ +0 -205
  76. data/spec/unit/spark_api/authentication_spec.rb~ +0 -38
  77. data/spec/unit/spark_api/configuration/yaml_spec.rb~ +0 -72
  78. data/spec/unit/spark_api/configuration_spec.rb~ +0 -122
  79. data/spec/unit/spark_api/faraday_spec.rb~ +0 -90
  80. data/spec/unit/spark_api/models/contact_spec.rb~ +0 -108
  81. data/spec/unit/spark_api/models/listing_cart_spec.rb~ +0 -127
  82. data/spec/unit/spark_api/models/listing_spec.rb~ +0 -320
  83. data/spec/unit/spark_api/models/message_spec.rb~ +0 -47
  84. data/spec/unit/spark_api/models/note_spec.rb~ +0 -63
  85. data/spec/unit/spark_api/models/notification_spec.rb~ +0 -62
  86. data/spec/unit/spark_api/models/shared_listing_spec.rb~ +0 -45
  87. data/spec/unit/spark_api/multi_client_spec.rb~ +0 -56
  88. data/spec/unit/spark_api/paginate_spec.rb~ +0 -224
  89. data/spec/unit/spark_api/primary_array_spec.rb~ +0 -41
  90. data/spec/unit/spark_api/request_spec.rb~ +0 -344
@@ -1,104 +0,0 @@
1
- module FlexmlsApi
2
-
3
- module Authentication
4
-
5
- #=API Authentication
6
- # Auth implementation for the API's original hash based authentication design. This is the
7
- # default authentication strategy used by the client. API Auth rely's on the user's API key
8
- # and secret and the active user is tied to the key owner.
9
-
10
- #==ApiAuth
11
- # Implementation the BaseAuth interface for API style authentication
12
- class ApiAuth < BaseAuth
13
-
14
- def initialize(client)
15
- super(client)
16
- end
17
-
18
- def authenticate
19
- sig = sign("#{@client.api_secret}ApiKey#{@client.api_key}")
20
- FlexmlsApi.logger.debug("Authenticating to #{@client.endpoint}")
21
- start_time = Time.now
22
- request_path = "/#{@client.version}/session?ApiKey=#{@client.api_key}&ApiSig=#{sig}"
23
- resp = @client.connection(true).post request_path, ""
24
- request_time = Time.now - start_time
25
- FlexmlsApi.logger.info("[#{(request_time * 1000).to_i}ms] Api: POST #{request_path}")
26
- FlexmlsApi.logger.debug("Authentication Response: #{resp.inspect}")
27
- @session = Session.new(resp.body.results.first)
28
- FlexmlsApi.logger.debug("Authentication: #{@session.inspect}")
29
- @session
30
- end
31
-
32
- def logout
33
- @client.delete("/session/#{@session.auth_token}") unless @session.nil?
34
- @session = nil
35
- end
36
-
37
- # Builds an ordered list of key value pairs and concatenates it all as one big string. Used
38
- # specifically for signing a request.
39
- def build_param_string(param_hash)
40
- return "" if param_hash.nil?
41
- sorted = param_hash.sort do |a,b|
42
- a.to_s <=> b.to_s
43
- end
44
- params = ""
45
- sorted.each do |key,val|
46
- params += key.to_s + val.to_s
47
- end
48
- params
49
- end
50
-
51
- # Sign a request
52
- def sign(sig)
53
- Digest::MD5.hexdigest(sig)
54
- end
55
-
56
- # Sign a request with request data.
57
- def sign_token(path, params = {}, post_data="")
58
- token_string = "#{@client.api_secret}ApiKey#{@client.api_key}ServicePath#{path}#{build_param_string(params)}#{post_data}"
59
- signed = sign(token_string)
60
- signed
61
- end
62
-
63
- # Perform an HTTP request (no data)
64
- def request(method, path, body, options)
65
- escaped_path = URI.escape(path)
66
- request_opts = {
67
- :AuthToken => @session.auth_token
68
- }
69
- unless @client.api_user.nil?
70
- request_opts.merge!(:ApiUser => "#{@client.api_user}")
71
- end
72
- request_opts.merge!(options)
73
- sig = sign_token(escaped_path, request_opts, body)
74
- request_path = "#{escaped_path}?#{build_url_parameters({"ApiSig"=>sig}.merge(request_opts))}"
75
- FlexmlsApi.logger.debug("Request: #{request_path}")
76
- if body.nil?
77
- response = @client.connection.send(method, request_path)
78
- else
79
- FlexmlsApi.logger.debug("Data: #{body}")
80
- response = @client.connection.send(method, request_path, body)
81
- end
82
- response
83
- end
84
-
85
- end
86
-
87
- # ==Session class
88
- # Handle on the api user session information as return by the api session service, including
89
- # roles, tokens and expiration
90
- class Session
91
- attr_accessor :auth_token, :expires, :roles
92
- def initialize(options={})
93
- @auth_token = options["AuthToken"]
94
- @expires = DateTime.parse options["Expires"]
95
- @roles = options["Roles"]
96
- end
97
- # Is the user session token expired?
98
- def expired?
99
- DateTime.now > @expires
100
- end
101
- end
102
-
103
- end
104
- end
@@ -1,47 +0,0 @@
1
- module FlexmlsApi
2
-
3
- module Authentication
4
- #=Authentication Base
5
- # This base class defines the basic interface supported by all client authentication
6
- # implementations.
7
- class BaseAuth
8
- attr_accessor :session
9
- # All ihheriting classes should accept the flexmls_api client as a part of initialization
10
- def initialize(client)
11
- @client = client
12
- end
13
-
14
- # Perform requests to authenticate the client with the API
15
- def authenticate
16
- raise "Implement me!"
17
- end
18
-
19
- # Called prior to running authenticate (except in case of api authentication errors)
20
- def authenticated?
21
- !(session.nil? || session.expired?)
22
- end
23
-
24
- # Terminate the active session
25
- def logout
26
- raise "Implement me!"
27
- end
28
-
29
- # Perform an HTTP request (no data)
30
- def request(method, path, body, options)
31
- raise "Implement me!"
32
- end
33
-
34
- # Format a hash as request parameters
35
- #
36
- # :returns:
37
- # Stringized form of the parameters as needed for an HTTP request
38
- def build_url_parameters(parameters={})
39
- array = parameters.map do |key,value|
40
- escaped_value = CGI.escape("#{value}")
41
- "#{key}=#{escaped_value}"
42
- end
43
- array.join "&"
44
- end
45
- end
46
- end
47
- end
@@ -1,199 +0,0 @@
1
- require 'uri'
2
-
3
-
4
- module FlexmlsApi
5
-
6
- module Authentication
7
-
8
- #=OAuth2 Authentication
9
- # Auth implementation to the API using the OAuth2 service endpoint. Current adheres to the 10
10
- # draft of the OAuth2 specification. With OAuth2, the application supplies credentials for the
11
- # application, and a separate a user authentication flow dictactes the active user for
12
- # requests.
13
- #
14
- #===Setup
15
- # When using this authentication method, there is a bit more setup involved to make the client
16
- # work. All applications need to extend the BaseOAuth2Provider class to supply the application
17
- # specific configuration. Also depending on the application type (command line, native, or web
18
- # based), the user authentication step will be handled differently.
19
-
20
- #==OAuth2
21
- # Implementation the BaseAuth interface for API style authentication
22
- class OAuth2 < BaseAuth
23
-
24
- def initialize(client)
25
- super(client)
26
- @provider = client.oauth2_provider
27
- end
28
-
29
- def session
30
- @provider.load_session()
31
- end
32
- def session=(s)
33
- @provider.save_session(s)
34
- end
35
-
36
- def authenticate
37
- granter = OAuth2Impl::GrantTypeBase.create(@client, @provider, session)
38
- self.session = granter.authenticate
39
- session
40
- end
41
-
42
- # Perform an HTTP request (no data)
43
- def request(method, path, body, options={})
44
- escaped_path = URI.escape(path)
45
- connection = @client.connection(true) # SSL Only!
46
- connection.headers.merge!(self.auth_header)
47
- parameter_string = options.size > 0 ? "?#{build_url_parameters(options)}" : ""
48
- request_path = "#{escaped_path}#{parameter_string}"
49
- FlexmlsApi.logger.debug("Request: #{request_path}")
50
- if body.nil?
51
- response = connection.send(method, request_path)
52
- else
53
- FlexmlsApi.logger.debug("Data: #{body}")
54
- response = connection.send(method, request_path, body)
55
- end
56
- response
57
- end
58
-
59
- def logout
60
- @provider.save_session(nil)
61
- end
62
-
63
- def authorization_url()
64
- params = {
65
- "client_id" => @provider.client_id,
66
- "response_type" => "code",
67
- "redirect_uri" => @provider.redirect_uri
68
- }
69
- "#{@provider.authorization_uri}?#{build_url_parameters(params)}"
70
- end
71
-
72
-
73
- protected
74
-
75
- def auth_header
76
- {"Authorization"=> "OAuth #{session.access_token}"}
77
- end
78
-
79
- def provider
80
- @provider
81
- end
82
- def client
83
- @client
84
- end
85
-
86
- end
87
-
88
- # Representation of a session with the api using oauth2
89
- class OAuthSession
90
- SESSION_ATTRIBUTES = [:access_token, :expires_in, :scope, :refresh_token, :refresh_timeout, :start_time]
91
- attr_accessor *SESSION_ATTRIBUTES
92
- def initialize(options={})
93
- @access_token = options["access_token"]
94
- @expires_in = options["expires_in"]
95
- @scope = options["scope"]
96
- @refresh_token = options["refresh_token"]
97
- @start_time = options.fetch("start_time", DateTime.now)
98
- @refresh_timeout = options.fetch("refresh_timeout",3600)
99
- if @start_time.is_a? String
100
- @start_time = DateTime.parse(@start_time)
101
- end
102
- end
103
- # Is the user session token expired?
104
- def expired?
105
- @start_time + Rational(@expires_in - @refresh_timeout, 86400) < DateTime.now
106
- end
107
-
108
- def to_json(*a)
109
- hash = {}
110
- SESSION_ATTRIBUTES.each do |k|
111
- value = self.send(k)
112
- hash[k.to_s] = value unless value.nil?
113
- end
114
- hash.to_json(*a)
115
- end
116
- end
117
-
118
- #=OAuth2 configuration provider for applications
119
- # Applications planning to use OAuth2 authentication with the API must extend this class as
120
- # part of the client configuration, providing values for the following attributes:
121
- # @authorization_uri - User oauth2 login page for flexmls
122
- # @access_uri - Location of the OAuth2 access token resource for the api. OAuth2 code and
123
- # credentials will be sent to this uri to generate an access token.
124
- # @redirect_uri - Application uri to redirect to
125
- # @client_id - OAuth2 provided application identifier
126
- # @client_secret - OAuth2 provided password for the client id
127
- class BaseOAuth2Provider
128
- attr_accessor *Configuration::OAUTH2_KEYS
129
- # Requirements for authorization_code grant type
130
- attr_accessor :code
131
- attr_accessor :grant_type
132
-
133
- def initialize(opts={})
134
- Configuration::OAUTH2_KEYS.each do |key|
135
- send("#{key}=", opts[key]) if opts.include? key
136
- end
137
- @grant_type = :authorization_code
138
- end
139
-
140
- def grant_type
141
- # backwards compatibility check
142
- @grant_type.nil? ? :authorization_code : @grant_type
143
- end
144
-
145
- # Application using the client must handle user redirect for user authentication. For
146
- # command line applications, this method is called prior to initial client requests so that
147
- # the process can notify the user to go to the url and retrieve the access_code for the app.
148
- # In a web based web application, this method can be mostly ignored. However, the web based
149
- # application is then responsible for ensuring the code is saved to the the provider instance
150
- # prior to any client requests are performed (or the error below will be thrown).
151
- def redirect(url)
152
- raise "To be implemented by client application"
153
- end
154
-
155
- #==For any persistence to be supported outside application process, the application shall
156
- # implement the following methods for storing and retrieving the user OAuth2 session
157
- # (e.g. to and from memcached).
158
-
159
- # Load the current OAuth session
160
- # returns - active OAuthSession or nil
161
- def load_session
162
- nil
163
- end
164
-
165
- # Save current session
166
- # session - active OAuthSession
167
- def save_session(session)
168
-
169
- end
170
-
171
- # Provides a default session time out
172
- # returns - the session timeout length (in seconds)
173
- def session_timeout
174
- 86400 # 1.day
175
- end
176
-
177
- end
178
-
179
- module OAuth2Impl
180
- require 'flexmls_api/authentication/oauth2_impl/middleware'
181
- require 'flexmls_api/authentication/oauth2_impl/grant_type_base'
182
- require 'flexmls_api/authentication/oauth2_impl/grant_type_refresh'
183
- require 'flexmls_api/authentication/oauth2_impl/grant_type_code'
184
- require 'flexmls_api/authentication/oauth2_impl/grant_type_password'
185
- require 'flexmls_api/authentication/oauth2_impl/password_provider'
186
-
187
- # Loads a provider class from a string
188
- def self.load_provider(string, args={})
189
- constant = Object
190
- string.split("::").compact.each { |name| constant = constant.const_get(name) unless name == ""}
191
- constant.new(args)
192
- rescue => e
193
- raise ArgumentError, "The value '#{string}' is an invalid class name for an oauth2 provider: #{e.message}"
194
- end
195
- end
196
-
197
- end
198
-
199
- end
@@ -1,87 +0,0 @@
1
- module FlexmlsApi
2
- module Authentication
3
- module OAuth2Impl
4
- class GrantTypeBase
5
- GRANT_TYPES = [:authorization_code, :password, :refresh_token]
6
-
7
- def self.create(client, provider, session=nil)
8
- granter = nil
9
- case provider.grant_type
10
- when :authorization_code
11
- granter = GrantTypeCode.new(client, provider, session)
12
- when :password
13
- granter = GrantTypePassword.new(client, provider, session)
14
- # This method should only be used internally to the library
15
- when :refresh_token
16
- granter = GrantTypeRefresh.new(client, provider, session)
17
- else
18
- raise ClientError, "Unsupported grant type [#{provider.grant_type}]"
19
- end
20
- FlexmlsApi.logger.debug("[oauth2] setup #{granter.class.name}")
21
- granter
22
- end
23
-
24
- attr_reader :provider, :client, :session
25
- def initialize(client, provider, session)
26
- @client = client
27
- @provider = provider
28
- @session = session
29
- end
30
- def authenticate
31
-
32
- end
33
-
34
- def refresh
35
-
36
- end
37
-
38
- protected
39
-
40
- def create_session(token_params)
41
- FlexmlsApi.logger.debug("[oauth2] create_session to #{provider.access_uri} params #{token_params}")
42
- uri = URI.parse(provider.access_uri)
43
- request_path = "#{uri.path}"
44
- response = oauth_access_connection("#{uri.scheme}://#{uri.host}").post(request_path, "#{token_params}").body
45
- response.expires_in = provider.session_timeout if response.expires_in.nil?
46
- FlexmlsApi.logger.debug("[oauth2] New session created #{response}")
47
- response
48
- end
49
-
50
- def needs_refreshing?
51
- !@session.nil? && !@session.refresh_token.nil? && @session.expired?
52
- end
53
-
54
- # Generate the appropriate request uri for authorizing this application for current user.
55
- def authorization_url()
56
- params = {
57
- "client_id" => @provider.client_id,
58
- "response_type" => "code",
59
- "redirect_uri" => @provider.redirect_uri
60
- }
61
- "#{@provider.authorization_uri}?#{build_url_parameters(params)}"
62
- end
63
-
64
- # Setup a faraday connection for dealing with an OAuth2 endpoint
65
- def oauth_access_connection(endpoint)
66
- opts = {
67
- :headers => @client.headers
68
- }
69
- opts[:ssl] = {:verify => false }
70
- opts[:url] = endpoint
71
- conn = Faraday::Connection.new(opts) do |builder|
72
- builder.adapter Faraday.default_adapter
73
- builder.use FlexmlsApi::Authentication::OAuth2Impl::Middleware
74
- end
75
- end
76
- def build_url_parameters(parameters={})
77
- array = parameters.map do |key,value|
78
- escaped_value = CGI.escape("#{value}")
79
- "#{key}=#{escaped_value}"
80
- end
81
- array.join "&"
82
- end
83
- end
84
-
85
- end
86
- end
87
- end
@@ -1,49 +0,0 @@
1
-
2
- module FlexmlsApi
3
- module Authentication
4
- module OAuth2Impl
5
- # OAuth2 authentication flow using username and password parameters for the user in the
6
- # request. This implementation is geared towards authentication styles for web applications
7
- # that have a OAuth flow for redirects.
8
- class GrantTypeCode < GrantTypeBase
9
- def initialize(client, provider, session)
10
- super(client, provider, session)
11
- end
12
- def authenticate
13
- if(provider.code.nil?)
14
- FlexmlsApi.logger.debug("[oauth2] No authoriztion code present. Redirecting to #{authorization_url}.")
15
- provider.redirect(authorization_url)
16
- end
17
- if needs_refreshing?
18
- new_session = refresh
19
- end
20
- return new_session unless new_session.nil?
21
- create_session(token_params)
22
- end
23
-
24
- def refresh()
25
- FlexmlsApi.logger.debug("[oauth2] Refresh oauth session.")
26
- refresher = GrantTypeRefresh.new(client,provider,session)
27
- refresher.params = {"redirect_uri" => @provider.redirect_uri}
28
- refresher.authenticate
29
- rescue ClientError => e
30
- FlexmlsApi.logger.info("[oauth2] Refreshing token failed, the library will try and authenticate from scratch: #{e.message}")
31
- nil
32
- end
33
-
34
- private
35
- def token_params
36
- params = {
37
- "client_id" => @provider.client_id,
38
- "client_secret" => @provider.client_secret,
39
- "grant_type" => "authorization_code",
40
- "code" => @provider.code,
41
- "redirect_uri" => @provider.redirect_uri
42
- }.to_json
43
- end
44
-
45
- end
46
-
47
- end
48
- end
49
- end