spark_api 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/History.txt +14 -0
  2. data/README.md +42 -233
  3. data/VERSION +1 -1
  4. data/lib/spark_api.rb +1 -0
  5. data/lib/spark_api/authentication/oauth2.rb +39 -9
  6. data/lib/spark_api/authentication/oauth2_impl/cli_provider.rb +96 -0
  7. data/lib/spark_api/authentication/oauth2_impl/faraday_middleware.rb +28 -0
  8. data/lib/spark_api/authentication/oauth2_impl/grant_type_base.rb +7 -2
  9. data/lib/spark_api/authentication/oauth2_impl/single_session_provider.rb +27 -0
  10. data/lib/spark_api/cli.rb +29 -10
  11. data/lib/spark_api/cli/api_auth.rb +1 -0
  12. data/lib/spark_api/cli/oauth2.rb +23 -8
  13. data/lib/spark_api/cli/setup.rb +31 -0
  14. data/lib/spark_api/configuration.rb +10 -2
  15. data/lib/spark_api/configuration/yaml.rb +6 -1
  16. data/lib/spark_api/connection.rb +1 -1
  17. data/lib/spark_api/errors.rb +48 -0
  18. data/lib/spark_api/models.rb +3 -0
  19. data/lib/spark_api/models/account.rb +9 -1
  20. data/lib/spark_api/models/base.rb +24 -19
  21. data/lib/spark_api/models/concerns.rb +7 -0
  22. data/lib/spark_api/models/concerns/destroyable.rb +32 -0
  23. data/lib/spark_api/models/concerns/savable.rb +66 -0
  24. data/lib/spark_api/models/contact.rb +6 -25
  25. data/lib/spark_api/models/dirty.rb +57 -0
  26. data/lib/spark_api/models/finders.rb +0 -4
  27. data/lib/spark_api/models/saved_search.rb +10 -0
  28. data/lib/spark_api/models/subresource.rb +5 -1
  29. data/lib/spark_api/models/subscription.rb +52 -0
  30. data/lib/spark_api/request.rb +17 -4
  31. data/lib/spark_api/response.rb +0 -37
  32. data/script/combined_flow_example.rb +3 -3
  33. data/script/oauth2_example.rb +3 -3
  34. data/spec/fixtures/base.json +3 -1
  35. data/spec/fixtures/contacts/new.json +2 -3
  36. data/spec/fixtures/contacts/new_empty.json +2 -3
  37. data/spec/fixtures/contacts/new_notify.json +1 -1
  38. data/spec/fixtures/{listings/saved_search.json → saved_searches/get.json} +1 -1
  39. data/spec/fixtures/saved_searches/new.json +8 -0
  40. data/spec/fixtures/saved_searches/post.json +12 -0
  41. data/spec/fixtures/saved_searches/update.json +6 -0
  42. data/spec/fixtures/subscriptions/get.json +19 -0
  43. data/spec/fixtures/subscriptions/new.json +13 -0
  44. data/spec/fixtures/subscriptions/post.json +10 -0
  45. data/spec/fixtures/subscriptions/put.json +12 -0
  46. data/spec/fixtures/subscriptions/subscribe.json +5 -0
  47. data/spec/fixtures/subscriptions/update.json +6 -0
  48. data/spec/mock_helper.rb +14 -6
  49. data/spec/oauth2_helper.rb +2 -0
  50. data/spec/spec_helper.rb +4 -7
  51. data/spec/unit/spark_api/authentication/api_auth_spec.rb +0 -1
  52. data/spec/unit/spark_api/authentication/oauth2_impl/faraday_middleware_spec.rb +32 -0
  53. data/spec/unit/spark_api/authentication/oauth2_impl/single_session_provider_spec.rb +9 -0
  54. data/spec/unit/spark_api/authentication/oauth2_spec.rb +29 -3
  55. data/spec/unit/spark_api/authentication_spec.rb +4 -10
  56. data/spec/unit/spark_api/configuration/yaml_spec.rb +4 -3
  57. data/spec/unit/spark_api/configuration_spec.rb +22 -8
  58. data/spec/unit/spark_api/models/account_spec.rb +5 -0
  59. data/spec/unit/spark_api/models/base_spec.rb +27 -0
  60. data/spec/unit/spark_api/models/concerns/destroyable_spec.rb +28 -0
  61. data/spec/unit/spark_api/models/concerns/savable_spec.rb +61 -0
  62. data/spec/unit/spark_api/models/contact_spec.rb +5 -5
  63. data/spec/unit/spark_api/models/dirty_spec.rb +46 -0
  64. data/spec/unit/spark_api/models/finders_spec.rb +0 -7
  65. data/spec/unit/spark_api/models/saved_search_spec.rb +34 -3
  66. data/spec/unit/spark_api/models/shared_listing_spec.rb +1 -1
  67. data/spec/unit/spark_api/models/subscription_spec.rb +106 -0
  68. data/spec/unit/spark_api/multi_client_spec.rb +14 -4
  69. data/spec/unit/spark_api/paginate_spec.rb +0 -1
  70. data/spec/unit/spark_api/request_spec.rb +10 -0
  71. data/spec/unit/spark_api_spec.rb +0 -3
  72. metadata +127 -45
  73. data/lib/spark_api/authentication/oauth2_impl/password_provider.rb +0 -24
@@ -1,3 +1,17 @@
1
+ == v1.2.0 2012-11-14
2
+ * SSL verification enabled by default
3
+ * Sparkbar token access support
4
+ * Fixed oauth2 CLI usage
5
+ * Model behaviors for CRUD operations
6
+ * Dirty module to help manage dirty attributes for updating a model
7
+ * Removed nonsensical "all" method for finder models
8
+ * ApiUser support for OAuth2 flow
9
+ * Subscriptions models
10
+ * Single session OAuth2 support
11
+ * Update RecipientIds attribute on subscribe/unsubscribe
12
+ * Restrict API wrapped json post data for hashes that have content
13
+ * Fix sorting on nils in accounts model
14
+ * Pluralize module
1
15
  == v1.1.0 2012-08-07
2
16
  * Upgraded faraday and other gems, cleand up the middleware
3
17
  == v1.0.4 2012-07-26
data/README.md CHANGED
@@ -5,6 +5,9 @@ A Ruby wrapper for the Spark REST API. Loosely based on ActiveResource to provid
5
5
 
6
6
  Documentation
7
7
  -------------
8
+
9
+ For further client documentation, please consult our [wiki](wiki).
10
+
8
11
  For full information on the API, see [http://sparkplatform.com/docs/overview/api](http://sparkplatform.com/docs/overview/api)
9
12
 
10
13
 
@@ -14,268 +17,74 @@ Installation
14
17
 
15
18
  Usage Examples
16
19
  ------------------------
17
-
18
- #### Ruby Script: OpenId/OAuth2 Combined Flow
19
- # initialize the gem with your key/secret.
20
- # See also: script/combined_flow_example.rb
21
- # api_key, api_secret, and callback are all required.
22
- # The following options are required:
23
- # - api_key: Your client key
24
- # - api_secret: Your client secret
25
- # - callback: Your redirect_uri, which the end user will be redirected
26
- # to after authorizing your application to access their data.
27
- # - auth_endpoint: The URI to redirect the user's web browser to, in order for them to
28
- # authorize your application to access their data.
29
- # other options and their defaults:
30
- # - endpoint: 'https://api.sparkapi.com'
31
- # - version: 'v1'
32
- # - ssl: true
33
- # - user_agent: 'Spark API Ruby Gem'
34
- SparkApi.configure do |config|
35
- config.authentication_mode = SparkApi::Authentication::OpenIdOAuth2Hybrid
36
- config.api_key = "YOUR_CLIENT_ID"
37
- config.api_secret = "YOUR_CLIENT_SECRET"
38
- config.callback = "YOUR_REDIRECT_URI"
39
- config.auth_endpoint = "https://developers.sparkplatform.com/openid"
40
- config.endpoint = 'https://developers.sparkapi.com'
41
- end
42
-
43
- # Code is retrieved from the method: SparkApi.client.authenticator.authorization_url
44
- # See script/combined_flow_example.rb for more details.
45
-
46
-
47
- SparkApi.client.oauth2_provider.code = "CODE_FROM_ABOVE_URI"
48
- SparkApi.client.authenticate
49
-
50
- # Alternatively, if you've already received an access token, you may
51
- # do the following instead of the above two lines:
52
- # SparkApi.client.session = SparkApi::Authentication::OAuthSession.new "access_token"=> "ACCESS_TOKEN",
53
- # "refresh_token" => "REFRESH_TOKEN", "expires_in" => 86400
54
-
55
- # mixin the models so you can use them without prefix
56
- include SparkApi::Models
57
-
58
- # Grab your listings!
59
- my_listings = Listing.my()
60
-
61
- #### Ruby Script: OAuth 2
62
- # initialize the gem with your OAuth 2 key/secret.
63
- # See also: script/oauth2_example.rb
64
- # api_key, api_secret, and callback are all required.
65
- # The following options are required:
66
- # - api_key: Your OAuth 2 client key
67
- # - api_secret: Your OAuth 2 client secret
68
- # - callback: Your OAuth 2 redirect_uri, which the end user will be redirected
69
- # to after authorizing your application to access their data.
70
- # - auth_endpoint: The URI to redirect the user's web browser to, in order for them to
71
- # authorize your application to access their data.
72
- # other options and their defaults:
73
- # - endpoint: 'https://api.sparkapi.com'
74
- # - version: 'v1'
75
- # - ssl: true
76
- # - user_agent: 'Spark API Ruby Gem'
77
20
  SparkApi.configure do |config|
78
- config.authentication_mode = SparkApi::Authentication::OAuth2
79
- config.api_key = "YOUR_CLIENT_ID"
80
- config.api_secret = "YOUR_CLIENT_SECRET"
81
- config.callback = "YOUR_REDIRECT_URI"
82
- config.auth_endpoint = "https://developers.sparkplatform.com/oauth2"
83
- config.endpoint = 'https://developers.sparkapi.com'
21
+ config.endpoint = 'https://sparkapi.com'
22
+ # Using Spark API Authentication, refer to the Authentication documentation for OAuth2
23
+ config.api_key = 'MY_SPARK_API_KEY'
24
+ config.api_secret = 'MY_SPARK_API_SECRET'
84
25
  end
85
-
86
- # Code is retrieved from the method. SparkApi.client.authenticator.authorization_url
87
- # See script/oauth2_example.rb for more details.
26
+ SparkApi.client.get '/my/account'
88
27
 
89
28
 
90
- SparkApi.client.oauth2_provider.code = "CODE_FROM_ABOVE_URI"
91
- SparkApi.client.authenticate
92
-
93
- # Alternatively, if you've already received an access token, you may
94
- # do the following instead of the above two lines:
95
- #SparkApi.client.session = SparkApi::Authentication::OAuthSession.new "access_token"=> "ACCESS_TOKEN",
96
- # "refresh_token" => "REFRESH_TOKEN", "expires_in" => 86400
97
-
98
- # mixin the models so you can use them without prefix
99
- include SparkApi::Models
29
+ #### Interactive Console
30
+ Included in the gem is an interactive spark_api console to interact with the api in a manner similar to the rails console. Below is a brief example of interacting with the console:
100
31
 
101
- # Grab your listings!
102
- my_listings = Listing.my()
32
+ > spark_api --api_key MY_SPARK_API_KEY --api_secret MY_SPARK_API_SECRET
33
+ SparkApi> SparkApi.client.get '/my/account'
103
34
 
35
+ Using OAuth2 requires different arguments, and is a bit more complicated as it requires a step for logging in through the browser to gain access to the access code for a client_id.
104
36
 
105
- #### Ruby Script: OpenId Only
106
- # initialize the gem with your key/secret.
107
- # api_key, api_secret, and callback are all required.
108
- # The following options are required:
109
- # - api_key: Your client key
110
- # - api_secret: Your client secret
111
- # - callback: Your redirect_uri, which the end user will be redirected
112
- # to after authorizing your application to access their data.
113
- # - auth_endpoint: The URI to redirect the user's web browser to, in order for them to
114
- # authorize your application to access their data.
115
- # other options and their defaults:
116
- # - user_agent: 'Spark API Ruby Gem'
117
- SparkApi.configure do |config|
118
- config.authentication_mode = SparkApi::Authentication::OpenId
119
- config.api_key = "YOUR_CLIENT_ID"
120
- config.api_secret = "YOUR_CLIENT_SECRET"
121
- config.callback = "YOUR_REDIRECT_URI"
122
- config.auth_endpoint = "https://developers.sparkplatform.com/openid"
123
- end
37
+ > bundle exec spark_api --oauth2 --client_id my_oauth2_client_id --client_secret my_oauth2_client_secret
38
+ Loading spark_api gem...
39
+ SparkApi:001:0> Account.my.Name
40
+ Missing OAuth2 session, redirecting...
41
+ Please visit https://sparkplatform.com/oauth2?client_id=my_oauth2_client_id&response_type=code&redirect_uri=https%3A%2F%2Fsparkplatform.com%2Foauth2%2Fcallback, login as a user, and paste the authorization code here:
42
+ Authorization code?
43
+ 9zsrc7jk7m4x7r4kers8n6sp5
44
+ "Demo User"
45
+ SparkApi:002:0> Account.my.UserType
46
+ "Member"
124
47
 
125
- # Code is retrieved from the method:
126
- # See script/combined_flow_example.rb for more details.
127
- # Optionally, additional a GET parameters can be supplied as a hash to
128
- # authorization_url.
129
- # SparkApi.client.authenticator.authorization_url
48
+ You can also provide other options from the command line, see "spark_api -h" for more information.
130
49
 
131
- # That's it! No API Access is available when using OpenID alone.
50
+ #### HTTP Interface
51
+ The base client provides a bare bones HTTP interface for working with the RESTful Spark API. This is basically a stylized curl interface that handles authentication, error handling, and processes JSON results as Ruby Hashes.
132
52
 
53
+ SparkApi.client.get "/listings/#{listing_id}", :_expand => "CustomFields"
54
+ SparkApi.client.post "/listings/#{listing_id}/photos", photo_body_hash
55
+ SparkApi.client.put "/listings/#{listing_id}/photos/#{photo_id}", updated_photo_name_hash
56
+ SparkApi.client.delete "/listings/#{listing_id}/photos/#{photo_id}"
133
57
 
134
- #### Ruby Script
135
- # initialize the gem with your key/secret
136
- # api_key and _api_secret are the only required settings
137
- # other options and their defaults:
138
- # - endpoint: 'https://api.sparkapi.com'
139
- # - version: 'v1'
140
- # - ssl: false
141
- # - user_agent: 'Spark API Ruby Gem'
142
- SparkApi.configure do |config|
143
- config.endpoint = 'https://developers.sparkapi.com'
144
- config.api_key = 'my_api_key'
145
- config.api_secret = 'my_api_secret'
146
- end
58
+ #### [API Models](wiki/API-Models)
59
+ The client also provides ActiveModelesque interface for working with the api responses. Notably, the models use finder methods for searching, and similar instanciation and persistence also on supported services.
147
60
 
148
- # mixin the models so you can use them without prefix
61
+ # Tip: mixin the models so you can use them without namespaces
149
62
  include SparkApi::Models
150
-
151
- # Grab your listings!
152
- my_listings = Listing.my()
153
-
154
-
155
- #### Interactive Console
156
- Included in the gem is a simple setup script to run the client in IRB. To use it, first create the file called _.spark_api_testing_ filling in the credentials for your account.
157
-
158
- API_USER="12345678901234567890123456" # 26-digit identifier of an API user
159
- API_ENDPOINT="https://developers.sparkapi.com"
160
- API_KEY="my_api_key"
161
- API_SECRET="my_api_secret"
162
-
163
- export API_USER API_ENDPOINT API_KEY API_SECRET
164
-
165
- Now, to run with this setup, run the following from the command line:
166
-
167
- > source .spark_api_testing
168
- > spark_api
169
- SparkApi> SparkApi.client.get '/my/account'
170
-
171
- You can also provide these options from the command line, see "spark_api -h" for more information
63
+ listings = Listing.find(:all, :_filter => "ListPrice Gt 150000.0 And ListPrice Lt 200000.0", :_orderby => "-ListPrice")
64
+ puts "Top list price: $%.2f" % listings.first.ListPrice
65
+ # Top list price: $199999.99
66
+ puts Account.find(:first, :_filter => "UserType Eq 'Member' And Name Eq 'John*'").Name
67
+ # John Doe
172
68
 
173
69
 
174
70
  Authentication Types
175
71
  --------------
176
72
  Authentication is handled transparently by the request framework in the gem, so you should never need to manually make an authentication request. More than one mode of authentication is supported, so the client needs to be configured accordingly.
177
73
 
178
- #### API Authentication (Default)
74
+ #### [Spark API Authentication](wiki/Spark-Authentication) (Default)
179
75
  Usually supplied for a single user, this authentication mode is the simplest, and is setup as the default. The example usage above demonstrates how to get started using this authentication mode.
180
76
 
181
- #### OpenId/OAuth2 Combined Flow (Preferred)
77
+ #### [OpenId/OAuth2 Combined Flow](wiki/Hybrid-Authentication) (Preferred)
182
78
  Authorization mode the separates application and user authentication. This mode requires the end user to be redirected to Spark Platform's openid endpoint. See "script/combined_flow_example.rb" for an example.
183
79
 
184
80
  Read more about Spark Platform's combined flow <a href="http://sparkplatform.com/docs/authentication/openid_oauth2_authentication">here</a>.
185
81
 
186
- #### OAuth2 Authorization
82
+ #### [OAuth2 Authorization](wiki/OAuth2-Only-Authentication)
187
83
  Authorization mode the separates application and user authentication. This mode requires the end user to be redirected to Spark Platform's auth endpoint. See "script/oauth2_example.rb" for an example.
188
84
 
189
85
  Read more about Spark Platform's OAuth 2 flow <a href="http://sparkplatform.com/docs/authentication/oauth2_authentication">here</a>.
190
86
 
191
- #### OpenId Authentication
87
+ #### [OpenId Authentication](wiki/OpenId-Only-Authentication)
192
88
  There is also the option to only access a user's Spark Platform identity without accessing any data (e.g. listings, contacts, etc.). In circumstances where you ONLY with to authenticate users through the Spark Platform, but do not have a use case to access any of their data, consider the OpenId authentication flow in lieu of API Authentication or the Combined flow.
193
89
 
194
90
  Read more about Spark Platform's OpenId flow <a href="http://sparkplatform.com/docs/authentication/openid_authentication">here</a>.
195
-
196
- Error Codes
197
- ---------------------
198
- <table>
199
- <thead>
200
- <tr>
201
- <th>HTTP Code</th>
202
- <th>Spark API Error Code</th>
203
- <th>Exception Raised</th>
204
- <th>Description</th>
205
- </tr>
206
- </thead>
207
- <tbody>
208
- <tr>
209
- <td><tt>401</tt></td>
210
- <td><tt>1000</tt></td>
211
- <td><tt></tt></td>
212
- <td>Invalid API Key and/or Request signed improperly</td>
213
- </tr>
214
- <tr>
215
- <td><tt>401</tt></td>
216
- <td><tt>1010</tt></td>
217
- <td><tt></tt></td>
218
- <td>API key is disabled</td>
219
- </tr>
220
- <tr>
221
- <td><tt>403</tt></td>
222
- <td><tt>1015</tt></td>
223
- <td><tt></tt></td>
224
- <td><tt>ApiUser</tt> must be supplied, or the provided key does not have access to the supplied user</td>
225
- </tr>
226
- <tr>
227
- <td><tt>401</tt></td>
228
- <td><tt>1020</tt></td>
229
- <td><tt></tt></td>
230
- <td>Session token has expired</td>
231
- </tr>
232
- <tr>
233
- <td><tt>403</tt></td>
234
- <td><tt>1030</tt></td>
235
- <td><tt></tt></td>
236
- <td>SSL required for this type of request</td>
237
- </tr>
238
- <tr>
239
- <td><tt>400</tt></td>
240
- <td><tt>1035</tt></td>
241
- <td><tt></tt></td>
242
- <td>POST data not supplied as valid JSON. Issued if the <tt>Content-Type</tt> header is not <tt>application/json/</tt> and/or if the POST data is not in valid JSON format.</td>
243
- </tr>
244
- <tr>
245
- <td><tt>400</tt></td>
246
- <td><tt>1040</tt></td>
247
- <td><tt></tt></td>
248
- <td>The <tt>_filter</tt> syntax was invalid or a specified field to search on does not exist</td>
249
- </tr>
250
- <tr>
251
- <td><tt>400</tt></td>
252
- <td><tt>1050</tt></td>
253
- <td><tt></tt></td>
254
- <td>(message varies) A required parameter was not provided</td>
255
- </tr>
256
- <tr>
257
- <td><tt>400</tt></td>
258
- <td><tt>1053</tt></td>
259
- <td><tt></tt></td>
260
- <td>(message varies) A parameter was provided but does not adhere to constraints</td>
261
- </tr>
262
- <tr>
263
- <td><tt>409</tt></td>
264
- <td><tt>1055</tt></td>
265
- <td><tt></tt></td>
266
- <td>(message varies)Issued when a write is requested that will conflict with existing data. For example, adding a new contact with an e-mail that already exists.</td>
267
- </tr>
268
- <tr>
269
- <td><tt>403</tt></td>
270
- <td><tt>1500</tt></td>
271
- <td><tt></tt></td>
272
- <td>The resource is not available at the current API key's service level. For example, this error applies if a user attempts to access the IDX Links API via a free API key. </td>
273
- </tr>
274
- <tr>
275
- <td><tt>503</tt></td>
276
- <td><tt>1550</tt></td>
277
- <td><tt></tt></td>
278
- <td>Over rate limit</td>
279
- </tbody>
280
- </table>
281
-
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.2
1
+ 1.2.0
@@ -3,6 +3,7 @@ require 'logger'
3
3
  require 'multi_json'
4
4
 
5
5
  require 'spark_api/version'
6
+ require 'spark_api/errors'
6
7
  require 'spark_api/configuration'
7
8
  require 'spark_api/multi_client'
8
9
  require 'spark_api/authentication'
@@ -43,6 +43,7 @@ module SparkApi
43
43
  escaped_path = URI.escape(path)
44
44
  connection = @client.connection(true) # SSL Only!
45
45
  connection.headers.merge!(self.auth_header)
46
+ options.merge!(:ApiUser => "#{@client.api_user}") unless @client.api_user.nil?
46
47
  parameter_string = options.size > 0 ? "?#{build_url_parameters(options)}" : ""
47
48
  request_path = "#{escaped_path}#{parameter_string}"
48
49
  SparkApi.logger.debug("Request: #{request_path}")
@@ -67,19 +68,42 @@ module SparkApi
67
68
  }
68
69
  "#{@provider.authorization_uri}?#{build_url_parameters(params)}"
69
70
  end
70
-
71
+
72
+ # Create a sparkbar token based on the current user's access token
73
+ def sparkbar_token()
74
+ raise ClientError, "OAuth2Provider must configure the sparkbar_uri to use sparkbar tokens" if provider.sparkbar_uri.nil?
75
+ SparkApi.logger.debug("[sparkbar] create token to #{provider.sparkbar_uri}")
76
+ uri = URI.parse(provider.sparkbar_uri)
77
+ request_path = "#{uri.path}"
78
+
79
+ SparkApi.logger.info("[sparkbar] create token to #{request_path}, #{session.access_token.inspect}")
80
+ response = sparkbar_connection("#{uri.scheme}://#{uri.host}").post(request_path, "access_token=#{session.access_token}").body
81
+ token = response["token"]
82
+ SparkApi.logger.debug("[sparkbar] New token created #{token}")
83
+ token
84
+ end
85
+
71
86
  protected
72
87
 
88
+ attr_reader :provider, :client
89
+
73
90
  def auth_header
74
91
  {"Authorization"=> "OAuth #{session.access_token}"}
75
92
  end
76
93
 
77
- def provider
78
- @provider
79
- end
80
- def client
81
- @client
82
- end
94
+ # Faraday handle to the sparkbar
95
+ def sparkbar_connection(endpoint)
96
+ opts = {
97
+ :headers => client.headers
98
+ }
99
+ opts[:headers].delete(:content_type)
100
+ opts[:ssl] = {:verify => false } unless @client.ssl_verify
101
+ opts[:url] = endpoint
102
+ conn = Faraday::Connection.new(opts) do |conn|
103
+ conn.response :sparkbar_impl
104
+ conn.adapter Faraday.default_adapter
105
+ end
106
+ end
83
107
 
84
108
  end
85
109
 
@@ -142,16 +166,21 @@ module SparkApi
142
166
  end
143
167
  # Is the user session token expired?
144
168
  def expired?
169
+ return false if @expires_in.nil?
145
170
  @start_time + Rational(@expires_in - @refresh_timeout, 86400) < DateTime.now
146
171
  end
147
172
 
148
173
  def to_json(*a)
174
+ to_hash.to_json(*a)
175
+ end
176
+
177
+ def to_hash
149
178
  hash = {}
150
179
  SESSION_ATTRIBUTES.each do |k|
151
180
  value = self.send(k)
152
181
  hash[k.to_s] = value unless value.nil?
153
182
  end
154
- hash.to_json(*a)
183
+ hash
155
184
  end
156
185
  end
157
186
 
@@ -222,8 +251,9 @@ module SparkApi
222
251
  require 'spark_api/authentication/oauth2_impl/grant_type_refresh'
223
252
  require 'spark_api/authentication/oauth2_impl/grant_type_code'
224
253
  require 'spark_api/authentication/oauth2_impl/grant_type_password'
225
- require 'spark_api/authentication/oauth2_impl/password_provider'
254
+ require 'spark_api/authentication/oauth2_impl/cli_provider'
226
255
  require 'spark_api/authentication/oauth2_impl/simple_provider'
256
+ require 'spark_api/authentication/oauth2_impl/single_session_provider'
227
257
 
228
258
  # Loads a provider class from a string
229
259
  def self.load_provider(string, args={})
@@ -0,0 +1,96 @@
1
+ require "highline/import"
2
+
3
+ module SparkApi
4
+ module Authentication
5
+ module OAuth2Impl
6
+ class CLIProvider < SparkApi::Authentication::BaseOAuth2Provider
7
+ SESSION_FILE = '.spark_api_oauth2'
8
+
9
+ def initialize(credentials)
10
+ super(credentials)
11
+ @grant_type = :password
12
+ @persistent_sessions = false
13
+ @session_alias = "default"
14
+ end
15
+
16
+ attr_accessor :persistent_sessions, :session_alias
17
+
18
+ def redirect(url)
19
+ puts "Missing OAuth2 session, redirecting..."
20
+ puts "Please visit #{url}, login as a user, and paste the authorization code here:"
21
+ self.code = ask("Authorization code?") do |q|
22
+ q.whitespace = :strip_and_collapse
23
+ q.validate = /^\w+$/
24
+ end
25
+ end
26
+
27
+ def load_session()
28
+ @session ||= load_persistent_session
29
+ end
30
+
31
+ def save_session(session)
32
+ @session = session
33
+ save_persistent_session
34
+ end
35
+
36
+ def destroy_session
37
+ destroy_persistent_session
38
+ @session = nil
39
+ end
40
+
41
+ def persistent_sessions?
42
+ persistent_sessions == true
43
+ end
44
+
45
+ private
46
+
47
+ def load_persistent_session
48
+ return nil unless persistent_sessions?
49
+ s = load_file[session_key]["session"]
50
+ OAuthSession.new(s)
51
+ rescue => e
52
+ puts "no file: #{e.message}"
53
+ end
54
+
55
+ def save_persistent_session
56
+ return unless persistent_sessions? && !@session.nil?
57
+ yaml = load_file
58
+ unless yaml.include? session_key
59
+ yaml[session_key] = {}
60
+ yaml[session_key]["created"] = Time.now
61
+ end
62
+ yaml[session_key]["session"] = @session.to_hash
63
+ yaml[session_key]["modified"] = Time.now
64
+ File.open(filename, "w") {|f| f.write(yaml.to_yaml) }
65
+ end
66
+
67
+ def session_key
68
+ "#{client_id}_#{session_alias}"
69
+ end
70
+
71
+ def destroy_persistent_session
72
+ return unless persistent_sessions?
73
+ yaml = load_file
74
+ return unless yaml.include? session_key
75
+ yaml[session_key].delete("session")
76
+ File.open(filename, "w") {|f| f.write(yaml.to_yaml) }
77
+ end
78
+
79
+ def filename
80
+ ENV['HOME'] + "/" + SESSION_FILE
81
+ end
82
+
83
+ def load_file
84
+ yaml = {}
85
+ begin
86
+ yaml = YAML.load(File.open(filename))
87
+ yaml = {} if yaml == false
88
+ rescue => e
89
+ puts "no file: #{e.message}"
90
+ end
91
+ yaml
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end