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