spark_api 1.0.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.
Files changed (257) hide show
  1. data/History.txt +139 -0
  2. data/LICENSE +14 -0
  3. data/README.md +153 -0
  4. data/Rakefile +18 -0
  5. data/VERSION +1 -0
  6. data/bin/spark_api +8 -0
  7. data/bin/spark_api~ +8 -0
  8. data/lib/spark_api.rb +46 -0
  9. data/lib/spark_api/authentication.rb +55 -0
  10. data/lib/spark_api/authentication/api_auth.rb +104 -0
  11. data/lib/spark_api/authentication/api_auth.rb~ +104 -0
  12. data/lib/spark_api/authentication/base_auth.rb +47 -0
  13. data/lib/spark_api/authentication/base_auth.rb~ +47 -0
  14. data/lib/spark_api/authentication/oauth2.rb +198 -0
  15. data/lib/spark_api/authentication/oauth2.rb~ +199 -0
  16. data/lib/spark_api/authentication/oauth2_impl/grant_type_base.rb +87 -0
  17. data/lib/spark_api/authentication/oauth2_impl/grant_type_base.rb~ +87 -0
  18. data/lib/spark_api/authentication/oauth2_impl/grant_type_code.rb +48 -0
  19. data/lib/spark_api/authentication/oauth2_impl/grant_type_code.rb~ +49 -0
  20. data/lib/spark_api/authentication/oauth2_impl/grant_type_password.rb +44 -0
  21. data/lib/spark_api/authentication/oauth2_impl/grant_type_password.rb~ +45 -0
  22. data/lib/spark_api/authentication/oauth2_impl/grant_type_refresh.rb +35 -0
  23. data/lib/spark_api/authentication/oauth2_impl/grant_type_refresh.rb~ +36 -0
  24. data/lib/spark_api/authentication/oauth2_impl/middleware.rb +38 -0
  25. data/lib/spark_api/authentication/oauth2_impl/middleware.rb~ +39 -0
  26. data/lib/spark_api/authentication/oauth2_impl/password_provider.rb +24 -0
  27. data/lib/spark_api/authentication/oauth2_impl/password_provider.rb~ +25 -0
  28. data/lib/spark_api/cli.rb +158 -0
  29. data/lib/spark_api/cli.rb~ +158 -0
  30. data/lib/spark_api/cli/api_auth.rb +8 -0
  31. data/lib/spark_api/cli/api_auth.rb~ +8 -0
  32. data/lib/spark_api/cli/oauth2.rb +14 -0
  33. data/lib/spark_api/cli/oauth2.rb~ +14 -0
  34. data/lib/spark_api/cli/setup.rb +47 -0
  35. data/lib/spark_api/cli/setup.rb~ +47 -0
  36. data/lib/spark_api/client.rb +27 -0
  37. data/lib/spark_api/configuration.rb +54 -0
  38. data/lib/spark_api/configuration.rb~ +54 -0
  39. data/lib/spark_api/configuration/yaml.rb +101 -0
  40. data/lib/spark_api/configuration/yaml.rb~ +101 -0
  41. data/lib/spark_api/connection.rb +42 -0
  42. data/lib/spark_api/faraday.rb +64 -0
  43. data/lib/spark_api/faraday.rb~ +64 -0
  44. data/lib/spark_api/models.rb +33 -0
  45. data/lib/spark_api/models.rb~ +33 -0
  46. data/lib/spark_api/models/account.rb +115 -0
  47. data/lib/spark_api/models/account.rb~ +115 -0
  48. data/lib/spark_api/models/base.rb +118 -0
  49. data/lib/spark_api/models/base.rb~ +118 -0
  50. data/lib/spark_api/models/connect_prefs.rb +10 -0
  51. data/lib/spark_api/models/connect_prefs.rb~ +10 -0
  52. data/lib/spark_api/models/constraint.rb +16 -0
  53. data/lib/spark_api/models/constraint.rb~ +16 -0
  54. data/lib/spark_api/models/contact.rb +49 -0
  55. data/lib/spark_api/models/contact.rb~ +49 -0
  56. data/lib/spark_api/models/custom_fields.rb +12 -0
  57. data/lib/spark_api/models/custom_fields.rb~ +12 -0
  58. data/lib/spark_api/models/document.rb +11 -0
  59. data/lib/spark_api/models/document.rb~ +11 -0
  60. data/lib/spark_api/models/finders.rb +45 -0
  61. data/lib/spark_api/models/finders.rb~ +45 -0
  62. data/lib/spark_api/models/idx_link.rb +47 -0
  63. data/lib/spark_api/models/idx_link.rb~ +47 -0
  64. data/lib/spark_api/models/listing.rb +197 -0
  65. data/lib/spark_api/models/listing.rb~ +197 -0
  66. data/lib/spark_api/models/listing_cart.rb +72 -0
  67. data/lib/spark_api/models/listing_cart.rb~ +72 -0
  68. data/lib/spark_api/models/market_statistics.rb +33 -0
  69. data/lib/spark_api/models/market_statistics.rb~ +33 -0
  70. data/lib/spark_api/models/message.rb +21 -0
  71. data/lib/spark_api/models/message.rb~ +21 -0
  72. data/lib/spark_api/models/note.rb +41 -0
  73. data/lib/spark_api/models/note.rb~ +41 -0
  74. data/lib/spark_api/models/notification.rb +42 -0
  75. data/lib/spark_api/models/notification.rb~ +42 -0
  76. data/lib/spark_api/models/open_house.rb +24 -0
  77. data/lib/spark_api/models/open_house.rb~ +24 -0
  78. data/lib/spark_api/models/photo.rb +70 -0
  79. data/lib/spark_api/models/photo.rb~ +70 -0
  80. data/lib/spark_api/models/property_types.rb +7 -0
  81. data/lib/spark_api/models/property_types.rb~ +7 -0
  82. data/lib/spark_api/models/saved_search.rb +16 -0
  83. data/lib/spark_api/models/saved_search.rb~ +16 -0
  84. data/lib/spark_api/models/shared_listing.rb +35 -0
  85. data/lib/spark_api/models/shared_listing.rb~ +35 -0
  86. data/lib/spark_api/models/standard_fields.rb +50 -0
  87. data/lib/spark_api/models/standard_fields.rb~ +50 -0
  88. data/lib/spark_api/models/subresource.rb +19 -0
  89. data/lib/spark_api/models/subresource.rb~ +19 -0
  90. data/lib/spark_api/models/system_info.rb +14 -0
  91. data/lib/spark_api/models/system_info.rb~ +14 -0
  92. data/lib/spark_api/models/tour_of_home.rb +24 -0
  93. data/lib/spark_api/models/tour_of_home.rb~ +24 -0
  94. data/lib/spark_api/models/video.rb +16 -0
  95. data/lib/spark_api/models/video.rb~ +16 -0
  96. data/lib/spark_api/models/virtual_tour.rb +18 -0
  97. data/lib/spark_api/models/virtual_tour.rb~ +18 -0
  98. data/lib/spark_api/multi_client.rb +59 -0
  99. data/lib/spark_api/multi_client.rb~ +59 -0
  100. data/lib/spark_api/paginate.rb +109 -0
  101. data/lib/spark_api/paginate.rb~ +109 -0
  102. data/lib/spark_api/primary_array.rb +29 -0
  103. data/lib/spark_api/primary_array.rb~ +29 -0
  104. data/lib/spark_api/request.rb +96 -0
  105. data/lib/spark_api/request.rb~ +96 -0
  106. data/lib/spark_api/response.rb +70 -0
  107. data/lib/spark_api/response.rb~ +70 -0
  108. data/lib/spark_api/version.rb +4 -0
  109. data/lib/spark_api/version.rb~ +4 -0
  110. data/script/console +6 -0
  111. data/script/console~ +6 -0
  112. data/script/example.rb +27 -0
  113. data/script/example.rb~ +27 -0
  114. data/spec/fixtures/accounts/all.json +160 -0
  115. data/spec/fixtures/accounts/my.json +74 -0
  116. data/spec/fixtures/accounts/my_portal.json +20 -0
  117. data/spec/fixtures/accounts/my_put.json +5 -0
  118. data/spec/fixtures/accounts/my_save.json +5 -0
  119. data/spec/fixtures/accounts/office.json +142 -0
  120. data/spec/fixtures/accounts/password_save.json +6 -0
  121. data/spec/fixtures/authentication_failure.json +7 -0
  122. data/spec/fixtures/base.json +13 -0
  123. data/spec/fixtures/contacts/contacts.json +28 -0
  124. data/spec/fixtures/contacts/my.json +19 -0
  125. data/spec/fixtures/contacts/new.json +11 -0
  126. data/spec/fixtures/contacts/new_empty.json +8 -0
  127. data/spec/fixtures/contacts/new_notify.json +11 -0
  128. data/spec/fixtures/contacts/post.json +10 -0
  129. data/spec/fixtures/contacts/tags.json +11 -0
  130. data/spec/fixtures/count.json +10 -0
  131. data/spec/fixtures/empty.json +3 -0
  132. data/spec/fixtures/errors/expired.json +7 -0
  133. data/spec/fixtures/errors/failure.json +5 -0
  134. data/spec/fixtures/errors/failure_with_constraint.json +17 -0
  135. data/spec/fixtures/errors/failure_with_msg.json +7 -0
  136. data/spec/fixtures/generic_delete.json +1 -0
  137. data/spec/fixtures/generic_failure.json +5 -0
  138. data/spec/fixtures/listing_carts/add_listing.json +13 -0
  139. data/spec/fixtures/listing_carts/add_listing_post.json +5 -0
  140. data/spec/fixtures/listing_carts/empty.json +5 -0
  141. data/spec/fixtures/listing_carts/listing_cart.json +19 -0
  142. data/spec/fixtures/listing_carts/new.json +12 -0
  143. data/spec/fixtures/listing_carts/post.json +10 -0
  144. data/spec/fixtures/listing_carts/remove_listing.json +13 -0
  145. data/spec/fixtures/listings/constraints.json +18 -0
  146. data/spec/fixtures/listings/constraints_with_pagination.json +24 -0
  147. data/spec/fixtures/listings/document_index.json +19 -0
  148. data/spec/fixtures/listings/multiple.json +69 -0
  149. data/spec/fixtures/listings/no_subresources.json +38 -0
  150. data/spec/fixtures/listings/open_houses.json +21 -0
  151. data/spec/fixtures/listings/photos/index.json +469 -0
  152. data/spec/fixtures/listings/photos/new.json +12 -0
  153. data/spec/fixtures/listings/photos/post.json +20 -0
  154. data/spec/fixtures/listings/put.json +5 -0
  155. data/spec/fixtures/listings/put_expiration_date.json +5 -0
  156. data/spec/fixtures/listings/saved_search.json +17 -0
  157. data/spec/fixtures/listings/shared_listing_get.json +14 -0
  158. data/spec/fixtures/listings/shared_listing_new.json +9 -0
  159. data/spec/fixtures/listings/shared_listing_post.json +10 -0
  160. data/spec/fixtures/listings/tour_of_homes.json +23 -0
  161. data/spec/fixtures/listings/videos_index.json +18 -0
  162. data/spec/fixtures/listings/virtual_tours_index.json +42 -0
  163. data/spec/fixtures/listings/with_documents.json +52 -0
  164. data/spec/fixtures/listings/with_permissions.json +44 -0
  165. data/spec/fixtures/listings/with_photos.json +110 -0
  166. data/spec/fixtures/listings/with_supplement.json +39 -0
  167. data/spec/fixtures/listings/with_videos.json +54 -0
  168. data/spec/fixtures/listings/with_vtour.json +48 -0
  169. data/spec/fixtures/logo_fbs.png +0 -0
  170. data/spec/fixtures/messages/new.json +14 -0
  171. data/spec/fixtures/messages/new_empty.json +7 -0
  172. data/spec/fixtures/messages/new_with_recipients.json +15 -0
  173. data/spec/fixtures/messages/post.json +5 -0
  174. data/spec/fixtures/notes/add.json +11 -0
  175. data/spec/fixtures/notes/agent_shared.json +11 -0
  176. data/spec/fixtures/notes/agent_shared_empty.json +7 -0
  177. data/spec/fixtures/notes/new.json +5 -0
  178. data/spec/fixtures/notifications/mark_read.json +1 -0
  179. data/spec/fixtures/notifications/new.json +8 -0
  180. data/spec/fixtures/notifications/new_empty.json +7 -0
  181. data/spec/fixtures/notifications/notifications.json +43 -0
  182. data/spec/fixtures/notifications/post.json +10 -0
  183. data/spec/fixtures/notifications/unread.json +10 -0
  184. data/spec/fixtures/oauth2/access.json +3 -0
  185. data/spec/fixtures/oauth2/access_with_old_refresh.json +5 -0
  186. data/spec/fixtures/oauth2/access_with_refresh.json +5 -0
  187. data/spec/fixtures/oauth2/authorization_code_body.json +7 -0
  188. data/spec/fixtures/oauth2/error.json +3 -0
  189. data/spec/fixtures/oauth2/password_body.json +7 -0
  190. data/spec/fixtures/oauth2/refresh_body.json +7 -0
  191. data/spec/fixtures/oauth2_error.json +3 -0
  192. data/spec/fixtures/property_types/property_types.json +31 -0
  193. data/spec/fixtures/session.json +10 -0
  194. data/spec/fixtures/standardfields/city.json +1031 -0
  195. data/spec/fixtures/standardfields/nearby.json +53 -0
  196. data/spec/fixtures/standardfields/standardfields.json +188 -0
  197. data/spec/fixtures/standardfields/stateorprovince.json +36 -0
  198. data/spec/fixtures/success.json +5 -0
  199. data/spec/json_helper.rb +76 -0
  200. data/spec/mock_helper.rb +124 -0
  201. data/spec/oauth2_helper.rb +68 -0
  202. data/spec/spec_helper.rb +48 -0
  203. data/spec/unit/flexmls_api_spec.rb~ +23 -0
  204. data/spec/unit/spark_api/authentication/api_auth_spec.rb +169 -0
  205. data/spec/unit/spark_api/authentication/api_auth_spec.rb~ +169 -0
  206. data/spec/unit/spark_api/authentication/base_auth_spec.rb +10 -0
  207. data/spec/unit/spark_api/authentication/base_auth_spec.rb~ +10 -0
  208. data/spec/unit/spark_api/authentication/oauth2_impl/grant_type_base_spec.rb +10 -0
  209. data/spec/unit/spark_api/authentication/oauth2_impl/grant_type_base_spec.rb~ +10 -0
  210. data/spec/unit/spark_api/authentication/oauth2_spec.rb +205 -0
  211. data/spec/unit/spark_api/authentication/oauth2_spec.rb~ +205 -0
  212. data/spec/unit/spark_api/authentication_spec.rb +38 -0
  213. data/spec/unit/spark_api/authentication_spec.rb~ +38 -0
  214. data/spec/unit/spark_api/configuration/yaml_spec.rb +72 -0
  215. data/spec/unit/spark_api/configuration/yaml_spec.rb~ +72 -0
  216. data/spec/unit/spark_api/configuration_spec.rb +122 -0
  217. data/spec/unit/spark_api/configuration_spec.rb~ +122 -0
  218. data/spec/unit/spark_api/faraday_spec.rb +90 -0
  219. data/spec/unit/spark_api/faraday_spec.rb~ +90 -0
  220. data/spec/unit/spark_api/models/account_spec.rb +176 -0
  221. data/spec/unit/spark_api/models/base_spec.rb +106 -0
  222. data/spec/unit/spark_api/models/connect_prefs_spec.rb +9 -0
  223. data/spec/unit/spark_api/models/constraint_spec.rb +19 -0
  224. data/spec/unit/spark_api/models/contact_spec.rb +108 -0
  225. data/spec/unit/spark_api/models/contact_spec.rb~ +108 -0
  226. data/spec/unit/spark_api/models/document_spec.rb +32 -0
  227. data/spec/unit/spark_api/models/listing_cart_spec.rb +127 -0
  228. data/spec/unit/spark_api/models/listing_cart_spec.rb~ +127 -0
  229. data/spec/unit/spark_api/models/listing_spec.rb +320 -0
  230. data/spec/unit/spark_api/models/listing_spec.rb~ +320 -0
  231. data/spec/unit/spark_api/models/message_spec.rb +47 -0
  232. data/spec/unit/spark_api/models/message_spec.rb~ +47 -0
  233. data/spec/unit/spark_api/models/note_spec.rb +63 -0
  234. data/spec/unit/spark_api/models/note_spec.rb~ +63 -0
  235. data/spec/unit/spark_api/models/notification_spec.rb +62 -0
  236. data/spec/unit/spark_api/models/notification_spec.rb~ +62 -0
  237. data/spec/unit/spark_api/models/open_house_spec.rb +39 -0
  238. data/spec/unit/spark_api/models/photo_spec.rb +92 -0
  239. data/spec/unit/spark_api/models/property_types_spec.rb +33 -0
  240. data/spec/unit/spark_api/models/saved_search_spec.rb +40 -0
  241. data/spec/unit/spark_api/models/shared_listing_spec.rb +45 -0
  242. data/spec/unit/spark_api/models/shared_listing_spec.rb~ +45 -0
  243. data/spec/unit/spark_api/models/standard_fields_spec.rb +60 -0
  244. data/spec/unit/spark_api/models/system_info_spec.rb +83 -0
  245. data/spec/unit/spark_api/models/tour_of_home_spec.rb +44 -0
  246. data/spec/unit/spark_api/models/video_spec.rb +36 -0
  247. data/spec/unit/spark_api/models/virtual_tour_spec.rb +44 -0
  248. data/spec/unit/spark_api/multi_client_spec.rb +56 -0
  249. data/spec/unit/spark_api/multi_client_spec.rb~ +56 -0
  250. data/spec/unit/spark_api/paginate_spec.rb +224 -0
  251. data/spec/unit/spark_api/paginate_spec.rb~ +224 -0
  252. data/spec/unit/spark_api/primary_array_spec.rb +41 -0
  253. data/spec/unit/spark_api/primary_array_spec.rb~ +41 -0
  254. data/spec/unit/spark_api/request_spec.rb +344 -0
  255. data/spec/unit/spark_api/request_spec.rb~ +344 -0
  256. data/spec/unit/spark_api_spec.rb +23 -0
  257. metadata +725 -0
@@ -0,0 +1,104 @@
1
+ module SparkApi
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
+ SparkApi.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
+ SparkApi.logger.info("[#{(request_time * 1000).to_i}ms] Api: POST #{request_path}")
26
+ SparkApi.logger.debug("Authentication Response: #{resp.inspect}")
27
+ @session = Session.new(resp.body.results.first)
28
+ SparkApi.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
+ SparkApi.logger.debug("Request: #{request_path}")
76
+ if body.nil?
77
+ response = @client.connection.send(method, request_path)
78
+ else
79
+ SparkApi.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
@@ -0,0 +1,104 @@
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
@@ -0,0 +1,47 @@
1
+ module SparkApi
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 spark_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
@@ -0,0 +1,47 @@
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
@@ -0,0 +1,198 @@
1
+ require 'uri'
2
+
3
+ module SparkApi
4
+
5
+ module Authentication
6
+
7
+ #=OAuth2 Authentication
8
+ # Auth implementation to the API using the OAuth2 service endpoint. Current adheres to the 10
9
+ # draft of the OAuth2 specification. With OAuth2, the application supplies credentials for the
10
+ # application, and a separate a user authentication flow dictactes the active user for
11
+ # requests.
12
+ #
13
+ #===Setup
14
+ # When using this authentication method, there is a bit more setup involved to make the client
15
+ # work. All applications need to extend the BaseOAuth2Provider class to supply the application
16
+ # specific configuration. Also depending on the application type (command line, native, or web
17
+ # based), the user authentication step will be handled differently.
18
+
19
+ #==OAuth2
20
+ # Implementation the BaseAuth interface for API style authentication
21
+ class OAuth2 < BaseAuth
22
+
23
+ def initialize(client)
24
+ super(client)
25
+ @provider = client.oauth2_provider
26
+ end
27
+
28
+ def session
29
+ @provider.load_session()
30
+ end
31
+ def session=(s)
32
+ @provider.save_session(s)
33
+ end
34
+
35
+ def authenticate
36
+ granter = OAuth2Impl::GrantTypeBase.create(@client, @provider, session)
37
+ self.session = granter.authenticate
38
+ session
39
+ end
40
+
41
+ # Perform an HTTP request (no data)
42
+ def request(method, path, body, options={})
43
+ escaped_path = URI.escape(path)
44
+ connection = @client.connection(true) # SSL Only!
45
+ connection.headers.merge!(self.auth_header)
46
+ parameter_string = options.size > 0 ? "?#{build_url_parameters(options)}" : ""
47
+ request_path = "#{escaped_path}#{parameter_string}"
48
+ SparkApi.logger.debug("Request: #{request_path}")
49
+ if body.nil?
50
+ response = connection.send(method, request_path)
51
+ else
52
+ SparkApi.logger.debug("Data: #{body}")
53
+ response = connection.send(method, request_path, body)
54
+ end
55
+ response
56
+ end
57
+
58
+ def logout
59
+ @provider.save_session(nil)
60
+ end
61
+
62
+ def authorization_url()
63
+ params = {
64
+ "client_id" => @provider.client_id,
65
+ "response_type" => "code",
66
+ "redirect_uri" => @provider.redirect_uri
67
+ }
68
+ "#{@provider.authorization_uri}?#{build_url_parameters(params)}"
69
+ end
70
+
71
+
72
+ protected
73
+
74
+ def auth_header
75
+ {"Authorization"=> "OAuth #{session.access_token}"}
76
+ end
77
+
78
+ def provider
79
+ @provider
80
+ end
81
+ def client
82
+ @client
83
+ end
84
+
85
+ end
86
+
87
+ # Representation of a session with the api using oauth2
88
+ class OAuthSession
89
+ SESSION_ATTRIBUTES = [:access_token, :expires_in, :scope, :refresh_token, :refresh_timeout, :start_time]
90
+ attr_accessor *SESSION_ATTRIBUTES
91
+ def initialize(options={})
92
+ @access_token = options["access_token"]
93
+ @expires_in = options["expires_in"]
94
+ @scope = options["scope"]
95
+ @refresh_token = options["refresh_token"]
96
+ @start_time = options.fetch("start_time", DateTime.now)
97
+ @refresh_timeout = options.fetch("refresh_timeout",3600)
98
+ if @start_time.is_a? String
99
+ @start_time = DateTime.parse(@start_time)
100
+ end
101
+ end
102
+ # Is the user session token expired?
103
+ def expired?
104
+ @start_time + Rational(@expires_in - @refresh_timeout, 86400) < DateTime.now
105
+ end
106
+
107
+ def to_json(*a)
108
+ hash = {}
109
+ SESSION_ATTRIBUTES.each do |k|
110
+ value = self.send(k)
111
+ hash[k.to_s] = value unless value.nil?
112
+ end
113
+ hash.to_json(*a)
114
+ end
115
+ end
116
+
117
+ #=OAuth2 configuration provider for applications
118
+ # Applications planning to use OAuth2 authentication with the API must extend this class as
119
+ # part of the client configuration, providing values for the following attributes:
120
+ # @authorization_uri - User oauth2 login page for the Spark platform
121
+ # @access_uri - Location of the OAuth2 access token resource for the api. OAuth2 code and
122
+ # credentials will be sent to this uri to generate an access token.
123
+ # @redirect_uri - Application uri to redirect to
124
+ # @client_id - OAuth2 provided application identifier
125
+ # @client_secret - OAuth2 provided password for the client id
126
+ class BaseOAuth2Provider
127
+ attr_accessor *Configuration::OAUTH2_KEYS
128
+ # Requirements for authorization_code grant type
129
+ attr_accessor :code
130
+ attr_accessor :grant_type
131
+
132
+ def initialize(opts={})
133
+ Configuration::OAUTH2_KEYS.each do |key|
134
+ send("#{key}=", opts[key]) if opts.include? key
135
+ end
136
+ @grant_type = :authorization_code
137
+ end
138
+
139
+ def grant_type
140
+ # backwards compatibility check
141
+ @grant_type.nil? ? :authorization_code : @grant_type
142
+ end
143
+
144
+ # Application using the client must handle user redirect for user authentication. For
145
+ # command line applications, this method is called prior to initial client requests so that
146
+ # the process can notify the user to go to the url and retrieve the access_code for the app.
147
+ # In a web based web application, this method can be mostly ignored. However, the web based
148
+ # application is then responsible for ensuring the code is saved to the the provider instance
149
+ # prior to any client requests are performed (or the error below will be thrown).
150
+ def redirect(url)
151
+ raise "To be implemented by client application"
152
+ end
153
+
154
+ #==For any persistence to be supported outside application process, the application shall
155
+ # implement the following methods for storing and retrieving the user OAuth2 session
156
+ # (e.g. to and from memcached).
157
+
158
+ # Load the current OAuth session
159
+ # returns - active OAuthSession or nil
160
+ def load_session
161
+ nil
162
+ end
163
+
164
+ # Save current session
165
+ # session - active OAuthSession
166
+ def save_session(session)
167
+
168
+ end
169
+
170
+ # Provides a default session time out
171
+ # returns - the session timeout length (in seconds)
172
+ def session_timeout
173
+ 86400 # 1.day
174
+ end
175
+
176
+ end
177
+
178
+ module OAuth2Impl
179
+ require 'spark_api/authentication/oauth2_impl/middleware'
180
+ require 'spark_api/authentication/oauth2_impl/grant_type_base'
181
+ require 'spark_api/authentication/oauth2_impl/grant_type_refresh'
182
+ require 'spark_api/authentication/oauth2_impl/grant_type_code'
183
+ require 'spark_api/authentication/oauth2_impl/grant_type_password'
184
+ require 'spark_api/authentication/oauth2_impl/password_provider'
185
+
186
+ # Loads a provider class from a string
187
+ def self.load_provider(string, args={})
188
+ constant = Object
189
+ string.split("::").compact.each { |name| constant = constant.const_get(name) unless name == ""}
190
+ constant.new(args)
191
+ rescue => e
192
+ raise ArgumentError, "The value '#{string}' is an invalid class name for an oauth2 provider: #{e.message}"
193
+ end
194
+ end
195
+
196
+ end
197
+
198
+ end