flexmls_api 0.4.5 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
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