flexmls_api 0.4.5 → 0.6.4

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 (93) hide show
  1. data/Gemfile +2 -17
  2. data/Gemfile.lock +35 -27
  3. data/README.md +23 -1
  4. data/Rakefile +18 -5
  5. data/VERSION +1 -1
  6. data/bin/flexmls_api +8 -0
  7. data/lib/flexmls_api.rb +2 -0
  8. data/lib/flexmls_api/authentication.rb +5 -6
  9. data/lib/flexmls_api/authentication/api_auth.rb +4 -2
  10. data/lib/flexmls_api/authentication/oauth2.rb +51 -99
  11. data/lib/flexmls_api/authentication/oauth2_impl/grant_type_base.rb +85 -0
  12. data/lib/flexmls_api/authentication/oauth2_impl/grant_type_code.rb +48 -0
  13. data/lib/flexmls_api/authentication/oauth2_impl/grant_type_password.rb +45 -0
  14. data/lib/flexmls_api/authentication/oauth2_impl/grant_type_refresh.rb +36 -0
  15. data/lib/flexmls_api/authentication/oauth2_impl/middleware.rb +39 -0
  16. data/lib/flexmls_api/cli.rb +132 -0
  17. data/lib/flexmls_api/cli/api_auth.rb +8 -0
  18. data/lib/flexmls_api/cli/oauth2.rb +43 -0
  19. data/lib/flexmls_api/cli/setup.rb +44 -0
  20. data/lib/flexmls_api/configuration.rb +6 -6
  21. data/lib/flexmls_api/faraday.rb +11 -21
  22. data/lib/flexmls_api/models.rb +3 -0
  23. data/lib/flexmls_api/models/account.rb +48 -5
  24. data/lib/flexmls_api/models/base.rb +27 -2
  25. data/lib/flexmls_api/models/contact.rb +28 -9
  26. data/lib/flexmls_api/models/listing_cart.rb +72 -0
  27. data/lib/flexmls_api/models/note.rb +0 -2
  28. data/lib/flexmls_api/models/saved_search.rb +16 -0
  29. data/lib/flexmls_api/models/shared_listing.rb +35 -0
  30. data/lib/flexmls_api/multi_client.rb +37 -0
  31. data/lib/flexmls_api/paginate.rb +5 -0
  32. data/lib/flexmls_api/request.rb +7 -3
  33. data/script/console +6 -0
  34. data/script/example.rb +27 -0
  35. data/spec/fixtures/accounts/all.json +160 -0
  36. data/spec/fixtures/accounts/my.json +74 -0
  37. data/spec/fixtures/accounts/my_portal.json +20 -0
  38. data/spec/fixtures/accounts/my_put.json +5 -0
  39. data/spec/fixtures/accounts/my_save.json +5 -0
  40. data/spec/fixtures/accounts/office.json +142 -0
  41. data/spec/fixtures/base.json +13 -0
  42. data/spec/fixtures/contact_my.json +19 -0
  43. data/spec/fixtures/contact_new.json +11 -0
  44. data/spec/fixtures/contact_new_empty.json +8 -0
  45. data/spec/fixtures/contact_new_notify.json +11 -0
  46. data/spec/fixtures/contact_tags.json +11 -0
  47. data/spec/fixtures/contacts.json +6 -3
  48. data/spec/fixtures/contacts_post.json +10 -0
  49. data/spec/fixtures/empty.json +3 -0
  50. data/spec/fixtures/errors/failure.json +5 -0
  51. data/spec/fixtures/listing_cart.json +19 -0
  52. data/spec/fixtures/listing_cart_add_listing.json +13 -0
  53. data/spec/fixtures/listing_cart_add_listing_post.json +5 -0
  54. data/spec/fixtures/listing_cart_empty.json +5 -0
  55. data/spec/fixtures/listing_cart_new.json +12 -0
  56. data/spec/fixtures/listing_cart_post.json +10 -0
  57. data/spec/fixtures/listing_cart_remove_listing.json +13 -0
  58. data/spec/fixtures/note_new.json +5 -0
  59. data/spec/fixtures/{oauth2_access.json → oauth2/access.json} +0 -0
  60. data/spec/fixtures/oauth2/access_with_old_refresh.json +5 -0
  61. data/spec/fixtures/oauth2/access_with_refresh.json +5 -0
  62. data/spec/fixtures/oauth2/authorization_code_body.json +7 -0
  63. data/spec/fixtures/oauth2/error.json +3 -0
  64. data/spec/fixtures/oauth2/password_body.json +7 -0
  65. data/spec/fixtures/oauth2/refresh_body.json +7 -0
  66. data/spec/fixtures/saved_search.json +17 -0
  67. data/spec/fixtures/shared_listing_new.json +9 -0
  68. data/spec/fixtures/shared_listing_post.json +10 -0
  69. data/spec/mock_helper.rb +123 -0
  70. data/spec/oauth2_helper.rb +69 -0
  71. data/spec/spec_helper.rb +1 -57
  72. data/spec/unit/flexmls_api/authentication/api_auth_spec.rb +1 -0
  73. data/spec/unit/flexmls_api/authentication/oauth2_impl/grant_type_base_spec.rb +10 -0
  74. data/spec/unit/flexmls_api/authentication/oauth2_spec.rb +74 -79
  75. data/spec/unit/flexmls_api/configuration_spec.rb +25 -4
  76. data/spec/unit/flexmls_api/models/account_spec.rb +152 -85
  77. data/spec/unit/flexmls_api/models/base_spec.rb +69 -25
  78. data/spec/unit/flexmls_api/models/contact_spec.rb +48 -34
  79. data/spec/unit/flexmls_api/models/document_spec.rb +1 -7
  80. data/spec/unit/flexmls_api/models/listing_cart_spec.rb +114 -0
  81. data/spec/unit/flexmls_api/models/listing_spec.rb +8 -56
  82. data/spec/unit/flexmls_api/models/note_spec.rb +8 -38
  83. data/spec/unit/flexmls_api/models/photo_spec.rb +1 -11
  84. data/spec/unit/flexmls_api/models/saved_search_spec.rb +34 -0
  85. data/spec/unit/flexmls_api/models/shared_listing_spec.rb +30 -0
  86. data/spec/unit/flexmls_api/models/standard_fields_spec.rb +50 -30
  87. data/spec/unit/flexmls_api/models/tour_of_home_spec.rb +1 -7
  88. data/spec/unit/flexmls_api/models/video_spec.rb +1 -10
  89. data/spec/unit/flexmls_api/models/virtual_tour_spec.rb +1 -7
  90. data/spec/unit/flexmls_api/multi_client_spec.rb +48 -0
  91. data/spec/unit/flexmls_api/request_spec.rb +42 -5
  92. metadata +239 -93
  93. data/spec/unit/flexmls_api/standard_fields_spec.rb +0 -86
@@ -0,0 +1,85 @@
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
+ granter
21
+ end
22
+
23
+ attr_reader :provider, :client, :session
24
+ def initialize(client, provider, session)
25
+ @client = client
26
+ @provider = provider
27
+ @session = session
28
+ end
29
+ def authenticate
30
+
31
+ end
32
+
33
+ def refresh
34
+
35
+ end
36
+
37
+ protected
38
+
39
+ def create_session(token_params)
40
+ FlexmlsApi.logger.debug("Authenticating to #{provider.access_uri}")
41
+ uri = URI.parse(provider.access_uri)
42
+ request_path = "#{uri.path}"
43
+ response = oauth_access_connection("#{uri.scheme}://#{uri.host}").post(request_path, "#{token_params}").body
44
+ response.expires_in = provider.session_timeout if response.expires_in.nil?
45
+ response
46
+ end
47
+
48
+ def needs_refreshing?
49
+ !@session.nil? && !@session.refresh_token.nil? && @session.expired?
50
+ end
51
+
52
+ # Generate the appropriate request uri for authorizing this application for current user.
53
+ def authorization_url()
54
+ params = {
55
+ "client_id" => @provider.client_id,
56
+ "response_type" => "code",
57
+ "redirect_uri" => @provider.redirect_uri
58
+ }
59
+ "#{@provider.authorization_uri}?#{build_url_parameters(params)}"
60
+ end
61
+
62
+ # Setup a faraday connection for dealing with an OAuth2 endpoint
63
+ def oauth_access_connection(endpoint)
64
+ opts = {
65
+ :headers => @client.headers
66
+ }
67
+ opts[:ssl] = {:verify => false }
68
+ opts[:url] = endpoint
69
+ conn = Faraday::Connection.new(opts) do |builder|
70
+ builder.adapter Faraday.default_adapter
71
+ builder.use FlexmlsApi::Authentication::OAuth2Impl::Middleware
72
+ end
73
+ end
74
+ def build_url_parameters(parameters={})
75
+ array = parameters.map do |key,value|
76
+ escaped_value = CGI.escape("#{value}")
77
+ "#{key}=#{escaped_value}"
78
+ end
79
+ array.join "&"
80
+ end
81
+ end
82
+
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,48 @@
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("Redirecting to provider to get the authorization code")
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
+ refresher = GrantTypeRefresh.new(client,provider,session)
26
+ refresher.params = {"redirect_uri" => @provider.redirect_uri}
27
+ refresher.authenticate
28
+ rescue ClientError => e
29
+ FlexmlsApi.logger.info("Refreshing token failed, the library will try and authenticate from scratch: #{e.message}")
30
+ nil
31
+ end
32
+
33
+ private
34
+ def token_params
35
+ params = {
36
+ "client_id" => @provider.client_id,
37
+ "client_secret" => @provider.client_secret,
38
+ "grant_type" => "authorization_code",
39
+ "code" => @provider.code,
40
+ "redirect_uri" => @provider.redirect_uri
41
+ }.to_json
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,45 @@
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 native
7
+ # applications that need to use OAuth2
8
+ class GrantTypePassword < GrantTypeBase
9
+ def initialize(client, provider, session)
10
+ super(client, provider, session)
11
+ end
12
+ def authenticate
13
+ new_session = nil
14
+ if needs_refreshing?
15
+ new_session = refresh
16
+ end
17
+ return new_session unless new_session.nil?
18
+ create_session(token_params)
19
+ end
20
+
21
+ def refresh()
22
+ GrantTypeRefresh.new(client,provider,session).authenticate
23
+ rescue ClientError => e
24
+ FlexmlsApi.logger.info("Refreshing token failed, the library will try and authenticate from scratch: #{e.message}")
25
+ nil
26
+ end
27
+
28
+ private
29
+ def token_params
30
+ params = {
31
+ "client_id" => @provider.client_id,
32
+ "client_secret" => @provider.client_secret,
33
+ "grant_type" => "password",
34
+ "username" => @provider.username,
35
+ "password" => @provider.password,
36
+ }.to_json
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+
44
+
45
+
@@ -0,0 +1,36 @@
1
+
2
+ module FlexmlsApi
3
+ module Authentication
4
+ # OAuth2 authentication flow to refresh an access token
5
+ module OAuth2Impl
6
+ class GrantTypeRefresh < GrantTypeBase
7
+ attr_accessor :params
8
+ def initialize(client, provider, session)
9
+ super(client, provider, session)
10
+ @params = {}
11
+ end
12
+
13
+ def authenticate
14
+ new_session = nil
15
+ unless @session.refresh_token.nil?
16
+ FlexmlsApi.logger.debug("Refreshing authentication to #{provider.access_uri} using [#{session.refresh_token}]")
17
+ new_session = create_session(token_params)
18
+ end
19
+ new_session
20
+ end
21
+
22
+ private
23
+ def token_params
24
+ @params.merge({
25
+ "client_id" => @provider.client_id,
26
+ "client_secret" => @provider.client_secret,
27
+ "grant_type" => "refresh_token",
28
+ "refresh_token"=> session.refresh_token,
29
+ }).to_json
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,39 @@
1
+
2
+ module FlexmlsApi
3
+
4
+ module Authentication
5
+
6
+ module OAuth2Impl
7
+
8
+ #==OAuth2 Faraday response middleware
9
+ # HTTP Response after filter to package oauth2 responses and bubble up basic api errors.
10
+ class Middleware < Faraday::Response::ParseJson
11
+ def on_complete(finished_env)
12
+ body = parse(finished_env[:body])
13
+ FlexmlsApi.logger.debug("Response Body: #{body.inspect}")
14
+ unless body.is_a?(Hash)
15
+ raise InvalidResponse, "The server response could not be understood"
16
+ end
17
+ case finished_env[:status]
18
+ when 200..299
19
+ FlexmlsApi.logger.debug("Success!")
20
+ session = OAuthSession.new(body)
21
+ else
22
+ # Handle the WWW-Authenticate Response Header Field if present. This can be returned by
23
+ # OAuth2 implementations and wouldn't hurt to log.
24
+ auth_header_error = finished_env[:request_headers]["WWW-Authenticate"]
25
+ FlexmlsApi.logger.warn("Authentication error #{auth_header_error}") unless auth_header_error.nil?
26
+ raise ClientError, {:message => body["error"], :code =>0, :status => finished_env[:status]}
27
+ end
28
+ FlexmlsApi.logger.debug("Session= #{session.inspect}")
29
+ finished_env[:body] = session
30
+ end
31
+
32
+ def initialize(app)
33
+ super(app)
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,132 @@
1
+ require 'optparse'
2
+
3
+ module FlexmlsApi
4
+ module CLI
5
+ class ConsoleCLI
6
+ OPTIONS_ENV = {
7
+ :endpoint => "API_ENDPOINT",
8
+ # OAUTH2 Options
9
+ :access_uri => "ACCESS_URI",
10
+ :username=> "USERNAME",
11
+ :password=> "PASSWORD",
12
+ :client_id=> "CLIENT_ID",
13
+ :client_secret=> "CLIENT_SECRET",
14
+ # API AUTH Options
15
+ :api_key => "API_KEY",
16
+ :api_secret => "API_SECRET",
17
+ :api_user => "API_USER",
18
+ # OTHER
19
+ :verbose => "VERBOSE",
20
+ :console => "FLEXMLS_API_CONSOLE" # not a public option, meant to distinguish bin/flexmls_api and script/console
21
+ }
22
+
23
+ def self.execute(stdout, arguments=[])
24
+ options = setup_options(stdout,arguments)
25
+ libs = " -r irb/completion"
26
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
27
+ libs << (options[:oauth2] ? setup_oauth2 : setup_api_auth)
28
+
29
+ bundler = (options[:console] ? "bundle exec" : "")
30
+ cmd = "#{export_env(options)} #{bundler} #{irb} #{libs} --simple-prompt"
31
+ puts "Loading flexmls_api gem..."
32
+ exec "#{cmd}"
33
+ end
34
+
35
+ def self.irb()
36
+ RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
37
+ end
38
+
39
+ private
40
+ def self.setup_options(stdout,arguments)
41
+ options = {
42
+ :oauth2 => false,
43
+ :endpoint => ENV[OPTIONS_ENV[:endpoint]],
44
+ # OAUTH2 Options
45
+ :access_uri => ENV[OPTIONS_ENV[:access_uri]],
46
+ :username=> ENV[OPTIONS_ENV[:username]],
47
+ :password=> ENV[OPTIONS_ENV[:password]],
48
+ :client_id=> ENV[OPTIONS_ENV[:client_id]],
49
+ :client_secret=> ENV[OPTIONS_ENV[:client_secret]],
50
+ # API AUTH Options
51
+ :api_key => ENV[OPTIONS_ENV[:api_key]],
52
+ :api_secret => ENV[OPTIONS_ENV[:api_secret]],
53
+ :api_user => ENV[OPTIONS_ENV[:api_user]],
54
+ :console => ENV[OPTIONS_ENV[:console]]
55
+ }
56
+
57
+ parser = OptionParser.new do |opts|
58
+ opts.banner = <<-BANNER.gsub(/^ /,'')
59
+ FlexmlsApi Client Console - http://www.flexmls.com/developers/api/
60
+
61
+ Usage: #{File.basename($0)} [options]
62
+
63
+ Environment Variables: some options (as indicated below), will default to values of keys set in the environment.
64
+
65
+ Options are:
66
+ BANNER
67
+ opts.separator ""
68
+ opts.on("-o","--oauth2",
69
+ "Run the API using OAuth2 credentials. The client defaults to using the flexmls API authentication mode for access. ",
70
+ "See http://www.flexmls.com/developers/api/api-services/authentication/ for more information on authentication types.",
71
+ "Default: false") { |arg| options[:oauth2] = arg }
72
+ opts.on("-e","--endpoint",
73
+ "URI of the API.",
74
+ "Default: ENV['#{OPTIONS_ENV[:endpoint]}']") { |arg| options[:endpoint] = arg }
75
+
76
+ # OAUTH2
77
+ opts.on("--client_id",
78
+ "OAuth2 client id",
79
+ "Default: ENV['#{OPTIONS_ENV[:client_id]}']") { |arg| options[:client_id] = arg }
80
+ opts.on("--client_secret",
81
+ "OAuth2 client secret",
82
+ "Default: ENV['#{OPTIONS_ENV[:client_secret]}']") { |arg| options[:client_secret] = arg }
83
+ opts.on("-u","--username",
84
+ "OAuth2 username",
85
+ "Default: ENV['#{OPTIONS_ENV[:username]}']") { |arg| options[:username] = arg }
86
+ opts.on("-p","--password",
87
+ "OAuth2 password",
88
+ "Default: ENV['#{OPTIONS_ENV[:password]}']") { |arg| options[:password] = arg }
89
+ opts.on("--access_uri",
90
+ "OAuth2 path for granting access to the application",
91
+ "Default: ENV['#{OPTIONS_ENV[:access_uri]}']") { |arg| options[:access_uri] = arg }
92
+
93
+ # API AUTH
94
+ opts.on("--api_key",
95
+ "Authentication key for running the api using the default api authentication",
96
+ "Default: ENV['#{OPTIONS_ENV[:api_key]}']") { |arg| options[:api_key] = arg }
97
+ opts.on("--api_secret",
98
+ "API secret for the api key",
99
+ "Default: ENV['#{OPTIONS_ENV[:api_secret]}']") { |arg| options[:api_secret] = arg }
100
+ opts.on("--api_user",
101
+ "ID of the flexmls user to run the client as.",
102
+ "Default: ENV['#{OPTIONS_ENV[:api_user]}']") { |arg| options[:api_user] = arg }
103
+
104
+ opts.on("-v", "--verbose",
105
+ "Show detailed request logging information.") { |arg| options[:verbose] = arg }
106
+ opts.on("-h", "--help",
107
+ "Show this help message.") { stdout.puts opts; exit }
108
+ opts.parse!(arguments)
109
+
110
+ end
111
+
112
+ return options
113
+ end
114
+
115
+ def self.setup_api_auth
116
+ " -r #{File.dirname(__FILE__) + '/../../lib/flexmls_api/cli/api_auth.rb'}"
117
+ end
118
+
119
+ def self.setup_oauth2
120
+ " -r #{File.dirname(__FILE__) + '/../../lib/flexmls_api/cli/oauth2.rb'}"
121
+ end
122
+
123
+ def self.export_env(options)
124
+ run_env = ""
125
+ OPTIONS_ENV.each do |k,v|
126
+ run_env << " #{v}=\"#{options[k]}\"" unless options[k].nil?
127
+ end
128
+ run_env
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,8 @@
1
+ require File.dirname(__FILE__) + "/../cli/setup"
2
+
3
+ FlexmlsApi.configure do |config|
4
+ config.api_key = ENV["API_KEY"]
5
+ config.api_secret = ENV["API_SECRET"]
6
+ config.api_user = ENV["API_USER"] if ENV["API_USER"]
7
+ config.endpoint = ENV["API_ENDPOINT"] if ENV["API_ENDPOINT"]
8
+ end
@@ -0,0 +1,43 @@
1
+ require File.dirname(__FILE__) + "/../cli/setup"
2
+
3
+ class CLIOAuth2Provider < FlexmlsApi::Authentication::BaseOAuth2Provider
4
+ def initialize(credentials)
5
+ @authorization_uri = credentials[:authorization_uri]
6
+ @access_uri = credentials[:access_uri]
7
+ @redirect_uri = credentials[:redirect_uri]
8
+ @client_id = credentials[:client_id]
9
+ @client_secret = credentials[:client_secret]
10
+ @username = credentials[:username]
11
+ @password = credentials[:password]
12
+ @session = nil
13
+ end
14
+
15
+ def grant_type
16
+ :password
17
+ end
18
+
19
+ def load_session()
20
+ @session
21
+ end
22
+
23
+ def save_session(session)
24
+ @session = session
25
+ end
26
+
27
+ def destroy_session
28
+ @session = nil
29
+ end
30
+ end
31
+
32
+ FlexmlsApi.configure do |config|
33
+ config.oauth2_provider = CLIOAuth2Provider.new(
34
+ :authorization_uri=> ENV["AUTH_URI"],
35
+ :access_uri => ENV["ACCESS_URI"],
36
+ :username=> ENV["USERNAME"],
37
+ :password=> ENV["PASSWORD"],
38
+ :client_id=> ENV["CLIENT_ID"],
39
+ :client_secret=> ENV["CLIENT_SECRET"]
40
+ )
41
+ config.authentication_mode = FlexmlsApi::Authentication::OAuth2
42
+ config.endpoint = ENV["API_ENDPOINT"] if ENV["API_ENDPOINT"]
43
+ end