autodiscover 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ *0.1.0*
2
+
3
+ * First public release
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010-2011 WIMM Labs, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,81 @@
1
+ Autodiscover
2
+ ============
3
+
4
+ Ruby client for Microsoft's Autodiscover Service.
5
+
6
+ The Autodiscover Service is a component of the Exchange 2007 and Exchange 2010 architecture. Autoservice clients can access the URLs and settings needed to communicate with Exchange servers, such as the URL of the endpoint to use with the Exchange Web Services (EWS) API.
7
+
8
+ This library implements Microsoft's "Autodiscover HTTP Service Protocol Specification" to discover the endpoint for an Autodiscover server that supports a specified e-mail address and Microsoft's "Autodiscover Publishing and Lookup Protocol Specification" to get URLs and settings that are required to access Web services available from Exchange servers.
9
+
10
+ Dependencies
11
+ ------------
12
+
13
+ This library requires the following Gems:
14
+
15
+ * HTTPClient
16
+ * Nokogiri
17
+
18
+ The HTTPClient Gem in turn requires the rubyntlm Gem for Negotiate/NTLM authentication.
19
+
20
+ For unit testing the webmock Gem is also used.
21
+
22
+ How to Use
23
+ ----------
24
+
25
+ require 'autodiscover'
26
+
27
+ credentials = Autodiscover::Credentials.new('<e-mail address>', '<password>')
28
+ client = Autodiscover::Client.new
29
+ services = client.get_services(credentials)
30
+ ews_url = services.ews_url
31
+ ttl = services.ttl
32
+
33
+ Options
34
+ -------
35
+
36
+ ### Debugging
37
+
38
+ For debugging, we extend the use of the debug_dev option in the HTTPClient library.
39
+
40
+ debug_file = File.open('<filename path>', 'w')
41
+ credentials = Autodiscover::Credentials.new('<e-mail address>', '<password>')
42
+ client = Autodiscover::Client.new(:debug_dev => debug_file)
43
+ services = client.get_services(credentials)
44
+ debug_file.close
45
+
46
+ ### Connection Timeouts
47
+
48
+ To adjust the connection timeout values used when searching for Autodiscover server endpoints:
49
+
50
+ client = Autodiscover::Client.new(:connect_timeout => 5)
51
+
52
+ The units are seconds.
53
+
54
+ Installation
55
+ ------------
56
+
57
+ ### Configuring a Rails App to use the latest GitHub master version
58
+
59
+ gem 'autodiscover', :git => 'git://github.com/wimm/autodiscover.git'
60
+
61
+ ### To install the latest development version from the GitHub master
62
+
63
+ git clone http://github.com/wimm/autodiscover.git
64
+ cd autodiscover
65
+ gem build autodiscover.gemspec
66
+ sudo gem install autodiscover-<version>.gem
67
+
68
+ Bugs and Issues
69
+ ---------------
70
+
71
+ Limitations:
72
+
73
+ * Doesn't support querying the DNS for SRV Records
74
+ * Only returns the TTL and EWS_Url values from the EXPR Protocol response
75
+
76
+ Please submit additional bugs and issues here [http://github.com/wimm/autodiscover/issues](http://github.com/wimm/autodiscover/issues)
77
+
78
+ Copyright
79
+ ---------
80
+
81
+ Copyright (c) 2010-2011 WIMM Labs, Inc. See MIT-LICENSE for details.
@@ -0,0 +1,26 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 WIMM Labs, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'autodiscover/client'
25
+ require 'autodiscover/credentials'
26
+ require 'autodiscover/services'
@@ -0,0 +1,226 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 WIMM Labs, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'httpclient'
25
+ require 'nokogiri'
26
+
27
+ module Autodiscover
28
+ REDIRECT_LIMIT = 10 # attempts
29
+ CONNECT_TIMEOUT_DEFAULT = 10 # seconds
30
+
31
+ # Client objects are used to make queries to the autodiscover server, to
32
+ # specify configuration values, and to maintain state between requests.
33
+ class Client
34
+ # Creates a Client object.
35
+ #
36
+ # The following options can be specified:
37
+ #
38
+ # <tt>:connect_timeout</tt>:: Number of seconds to wait when trying to establish
39
+ # a connection. The default value is 10 seconds.
40
+ # <tt>:debug_dev</tt>:: Device that debug messages and all HTTP
41
+ # requests and responses are dumped to. The debug
42
+ # device must respond to <tt><<</tt> for dump.
43
+ def initialize(options={})
44
+ @debug_dev = options[:debug_dev]
45
+
46
+ @http = HTTPClient.new
47
+ @http.connect_timeout = options[:connect_timeout] || CONNECT_TIMEOUT_DEFAULT
48
+ @http.debug_dev = @debug_dev if @debug_dev
49
+
50
+ @redirect_count = 0
51
+ end
52
+
53
+ # Get a Services object from an \Autodiscover server that is
54
+ # available on an authenticated endpoint determined by the
55
+ # specified Credentials object.
56
+ def get_services(credentials, reset_redirect_count=true)
57
+ @redirect_count = 0 if reset_redirect_count
58
+
59
+ req_body = build_request_body credentials.email
60
+
61
+ try_standard_secure_urls(credentials, req_body) ||
62
+ try_standard_redirection_url(credentials, req_body) ||
63
+ try_dns_serv_record
64
+ end
65
+
66
+ private
67
+
68
+ def try_standard_secure_urls(credentials, req_body)
69
+ response = nil
70
+ [ "https://#{credentials.smtp_domain}/autodiscover/autodiscover.xml",
71
+ "https://autodiscover.#{credentials.smtp_domain}/autodiscover/autodiscover.xml"
72
+ ].each do |url|
73
+ @debug_dev << "AUTODISCOVER: trying #{url}\n" if @debug_dev
74
+ response = try_secure_url(url, credentials, req_body)
75
+ break if response
76
+ end
77
+ response
78
+ end
79
+
80
+ def try_standard_redirection_url(credentials, req_body)
81
+ url = "http://autodiscover.#{credentials.smtp_domain}/autodiscover/autodiscover.xml"
82
+ @debug_dev << "AUTODISCOVER: looking for redirect from #{url}\n" if @debug_dev
83
+ response = @http.get(url) rescue nil
84
+ return nil unless response
85
+
86
+ if response.status_code == 302
87
+ try_redirect_url(response.header['Location'].first, credentials, req_body)
88
+ else
89
+ nil
90
+ end
91
+ end
92
+
93
+ def try_secure_url(url, credentials, req_body)
94
+ @http.set_auth(url, credentials.email, credentials.password)
95
+
96
+ response = @http.post(url, req_body, {'Content-Type' => 'text/xml; charset=utf-8'}) rescue nil
97
+ return nil unless response
98
+
99
+ if response.status_code == 302
100
+ try_redirect_url(response.header['Location'].first, credentials, req_body)
101
+ elsif HTTP::Status.successful?(response.status_code)
102
+ result = parse_response(response.content)
103
+ case result
104
+ when Autodiscover::Services
105
+ return result
106
+ when Autodiscover::RedirectUrl
107
+ try_redirect_url(result.url, credentials, req_body)
108
+ when Autodiscover::RedirectAddress
109
+ begin
110
+ credentials.email = result.address
111
+ rescue ArgumentError
112
+ # An invalid email address was returned
113
+ return nil
114
+ end
115
+
116
+ try_redirect_addr(credentials)
117
+ end
118
+ else
119
+ nil
120
+ end
121
+ end
122
+
123
+ def try_redirect_url(url, credentials, req_body)
124
+ @redirect_count += 1
125
+ return nil if @redirect_count > REDIRECT_LIMIT
126
+
127
+ # Only permit redirects to secure addresses
128
+ return nil unless url =~ /^https:/i
129
+ try_secure_url(url, credentials, req_body)
130
+ end
131
+
132
+ def try_redirect_addr(credentials)
133
+ @redirect_count += 1
134
+ return nil if @redirect_count > REDIRECT_LIMIT
135
+
136
+ get_services(credentials, false)
137
+ end
138
+
139
+ def try_dns_serv_record
140
+ nil
141
+ end
142
+
143
+ def build_request_body(email)
144
+ Nokogiri::XML::Builder.new do |xml|
145
+ xml.Autodiscover('xmlns' => 'http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006') {
146
+ xml.Request {
147
+ xml.EMailAddress email
148
+ xml.AcceptableResponseSchema 'http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a'
149
+ }
150
+ }
151
+ end.to_xml
152
+ end
153
+
154
+ NAMESPACES = {
155
+ 'a' => 'http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006',
156
+ 'o' => 'http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a'
157
+ } #:nodoc:
158
+
159
+ def parse_response(body)
160
+ doc = parse_xml body
161
+ return nil unless doc
162
+
163
+ # The response must include an Account element. Return an error if not found.
164
+ account_e = doc.at_xpath('a:Autodiscover/o:Response/o:Account', NAMESPACES)
165
+ return nil unless account_e
166
+
167
+ # The response must include an Action element. Return an error if not found.
168
+ action_e = account_e.at_xpath('o:Action', NAMESPACES)
169
+ return nil unless action_e
170
+
171
+ case action_e.content
172
+ when /^settings$/i
173
+ # Response contains configuration settings in <Protocol> elements
174
+ # Only care about about "EXPR" type protocol configuration values
175
+ # for accessing Exchange services outside of the firewall
176
+ settings = {}
177
+ if protocol_e = account_e.at_xpath('o:Protocol[o:Type="EXPR"]', NAMESPACES)
178
+ # URL for the Web services virtual directory.
179
+ ews_url_e = protocol_e.at_xpath('o:EwsUrl', NAMESPACES)
180
+ settings['ews_url'] = ews_url_e.content if ews_url_e
181
+ # Time to Live (TTL) in hours. Default is 1 hour if no element is
182
+ # returned.
183
+ ttl_e = protocol_e.at_xpath('o:TTL', NAMESPACES)
184
+ settings['ttl'] = ttl_e ? ttl_e.content : 1
185
+ end
186
+ Autodiscover::Services.new(settings)
187
+ when /^redirectAddr$/i
188
+ # Response contains a new address that must be used to re-­Autodiscover
189
+ redirect_addr_e = account_e.at_xpath('o:RedirectAddr', NAMESPACES)
190
+ address = redirect_addr_e ? redirect_addr_e.content : nil
191
+ return nil unless address
192
+ Autodiscover::RedirectAddress.new(address)
193
+ when /^redirectUrl$/i
194
+ # Response contains a new URL that must be used to re-Autodiscover
195
+ redirect_url_e = account_e.at_xpath('o:RedirectUrl', NAMESPACES)
196
+ url = redirect_url_e ? redirect_url_e.content : nil
197
+ return nil unless url
198
+ Autodiscover::RedirectUrl.new(url)
199
+ else
200
+ nil
201
+ end
202
+ end
203
+
204
+ def parse_xml(doc)
205
+ Nokogiri::XML(doc) { |c| c.options = Nokogiri::XML::ParseOptions::STRICT }
206
+ rescue Nokogiri::XML::SyntaxError
207
+ nil
208
+ end
209
+ end
210
+
211
+ class RedirectUrl #:nodoc: all
212
+ attr_reader :url
213
+
214
+ def initialize(url)
215
+ @url = url
216
+ end
217
+ end
218
+
219
+ class RedirectAddress #:nodoc: all
220
+ attr_reader :address
221
+
222
+ def initialize(address)
223
+ @address = address
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,51 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 WIMM Labs, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module Autodiscover
25
+ # A Credentials object is used to determine the autodiscover service
26
+ # endpoint and to authenticate to it.
27
+ class Credentials
28
+ # E-mail address for the user.
29
+ attr_reader :email
30
+
31
+ # Password for the account.
32
+ attr_reader :password
33
+
34
+ # SMTP domain determined by the e-mail address.
35
+ attr_reader :smtp_domain #:nodoc:
36
+
37
+ def initialize(address, password)
38
+ self.email = address
39
+ @password = password
40
+ end
41
+
42
+ def email=(address) #:nodoc:
43
+ raise ArgumentError, "No email address specified" unless address
44
+ @smtp_domain = address[/^.+@(.*)$/, 1]
45
+ unless @smtp_domain =~ /.+\..+/
46
+ raise ArgumentError, "Invalid email address: #{address}"
47
+ end
48
+ @email = address
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,40 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 WIMM Labs, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module Autodiscover
25
+ # A Services object contains the endpoint URLs and settings returned
26
+ # from an \Autodiscover server.
27
+ class Services
28
+ # URL for Exchange Web Services API endpoint.
29
+ attr_reader :ews_url
30
+
31
+ # Time to Live (TTL), in hours, during which the settings remain valid.
32
+ # A value of zero indicates that rediscovery is not required.
33
+ attr_reader :ttl
34
+
35
+ def initialize(settings) #:nodoc:
36
+ @ews_url = settings['ews_url']
37
+ @ttl = settings['ttl']
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,195 @@
1
+ # Prefix LOAD_PATH with lib sub-directory to ensure we're
2
+ # testing the intended version.
3
+ $:.unshift File.join(File.dirname(__FILE__), "..", "lib")
4
+
5
+ # Load HTTPClient before webmock so the HTTPClient adapter will be used.
6
+ require 'httpclient'
7
+ require 'webmock/test_unit'
8
+ require 'test/unit'
9
+ require 'autodiscover'
10
+
11
+ SETTINGS_AUTODISCOVER_RESPONSE = <<END
12
+ <?xml version="1.0" encoding="utf-8"?>
13
+ <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
14
+ <Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
15
+ <User>
16
+ <DisplayName>spiff</DisplayName>
17
+ <LegacyDN>/o=outlook/ou=Exchange Administrative Group (spacecommand)/cn=Recipients/cn=spiff</LegacyDN>
18
+ <DeploymentId>11111111-2222-3333-4444-555555555555</DeploymentId>
19
+ </User>
20
+ <Account>
21
+ <AccountType>email</AccountType>
22
+ <Action>settings</Action>
23
+ <Protocol>
24
+ <Type>EXPR</Type>
25
+ <Server>outlook.spacecommand.sol</Server>
26
+ <SSL>On</SSL>
27
+ <AuthPackage>Basic</AuthPackage>
28
+ <EwsUrl>https://ews.spacecommand.sol/EWS/Exchange.asmx</EwsUrl>
29
+ </Protocol>
30
+ </Account>
31
+ </Response>
32
+ </Autodiscover>
33
+ END
34
+
35
+ REDIRECTURL_AUTODISCOVER_RESPONSE = <<END
36
+ <?xml version="1.0" encoding="utf-8"?>
37
+ <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
38
+ <Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
39
+ <Account>
40
+ <Action>redirectUrl</Action>
41
+ <RedirectUrl>https://earthcommand.org/autodiscover/autodiscover.xml</RedirectUrl>
42
+ </Account>
43
+ </Response>
44
+ </Autodiscover>
45
+ END
46
+
47
+ REDIRECTADDR_AUTODISCOVER_RESPONSE = <<END
48
+ <?xml version="1.0" encoding="utf-8"?>
49
+ <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
50
+ <Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
51
+ <Account>
52
+ <Action>redirectAddr</Action>
53
+ <RedirectAddr>calvin@spacecommand.sol</RedirectAddr>
54
+ </Account>
55
+ </Response>
56
+ </Autodiscover>
57
+ END
58
+
59
+ class AutodiscoverResponseTest < Test::Unit::TestCase
60
+ def setup
61
+ @credentials = Autodiscover::Credentials.new('spiff@spacecommand.sol', 'hobbes')
62
+ @client = Autodiscover::Client.new
63
+ WebMock::stub_request(:any, /spacecommand.sol/).to_timeout
64
+ end
65
+
66
+ # Test the cases where the Autodiscover service is configured to listen on
67
+ # the standard secure endpoint addresses.
68
+ [ "https://spiff%40spacecommand.sol:hobbes@spacecommand.sol/autodiscover/autodiscover.xml",
69
+ "https://spiff%40spacecommand.sol:hobbes@autodiscover.spacecommand.sol/autodiscover/autodiscover.xml"
70
+ ].each_with_index do |url, i|
71
+ define_method "test_standard_secure_urls_#{i}" do
72
+ WebMock::stub_request(:post, url).to_return(
73
+ :body => SETTINGS_AUTODISCOVER_RESPONSE,
74
+ :status => 200,
75
+ :headers => { 'Content-Length' => SETTINGS_AUTODISCOVER_RESPONSE.size }
76
+ )
77
+ response = @client.get_services(@credentials)
78
+ assert_not_nil response
79
+ assert_equal 'https://ews.spacecommand.sol/EWS/Exchange.asmx', response.ews_url
80
+ end
81
+ end
82
+
83
+ # Test the cases where a standard secure address is used to redirect
84
+ # to a secure endpoint that the Autodiscover service is available upon.
85
+ def test_redirect_from_standard_secure_url
86
+ WebMock::stub_request(:post, /spacecommand.sol/).to_return(
87
+ :status => 302,
88
+ :headers => { 'Location' => 'https://earthcommand.org/autodiscover/autodiscover.xml' }
89
+ )
90
+ WebMock::stub_request(:post, 'https://spiff%40spacecommand.sol:hobbes@earthcommand.org/autodiscover/autodiscover.xml').to_return(
91
+ :body => SETTINGS_AUTODISCOVER_RESPONSE,
92
+ :status => 200,
93
+ :headers => { 'Content-Length' => SETTINGS_AUTODISCOVER_RESPONSE.size }
94
+ )
95
+ response = @client.get_services(@credentials)
96
+ assert_not_nil response
97
+ assert_equal 'https://ews.spacecommand.sol/EWS/Exchange.asmx', response.ews_url
98
+ end
99
+
100
+ # Test the cases where the standard insecure redirect address is used to redirect
101
+ # to a secure endpoint that the Autodiscover service is available upon.
102
+ def test_standard_redirection_url
103
+ WebMock::stub_request(:get, "http://autodiscover.spacecommand.sol/autodiscover/autodiscover.xml").to_return(
104
+ :status => 302,
105
+ :headers => { 'Location' => 'https://earthcommand.org/autodiscover/autodiscover.xml' }
106
+ )
107
+ WebMock::stub_request(:post, 'https://spiff%40spacecommand.sol:hobbes@earthcommand.org/autodiscover/autodiscover.xml').to_return(
108
+ :body => SETTINGS_AUTODISCOVER_RESPONSE,
109
+ :status => 200,
110
+ :headers => { 'Content-Length' => SETTINGS_AUTODISCOVER_RESPONSE.size }
111
+ )
112
+ response = @client.get_services(@credentials)
113
+ assert_not_nil response
114
+ assert_equal 'https://ews.spacecommand.sol/EWS/Exchange.asmx', response.ews_url
115
+ end
116
+
117
+ # Test the case where a post to a standard secure address returns a redirectUrl response that
118
+ # redirects to a secure endpoint that returns a valid settings response.
119
+ def test_redirect_url_response
120
+ WebMock::stub_request(:post, "https://spiff%40spacecommand.sol:hobbes@spacecommand.sol/autodiscover/autodiscover.xml").to_return(
121
+ :body => REDIRECTURL_AUTODISCOVER_RESPONSE,
122
+ :status => 200,
123
+ :headers => { 'Content-Length' => REDIRECTURL_AUTODISCOVER_RESPONSE.size }
124
+ )
125
+ WebMock::stub_request(:post, 'https://spiff%40spacecommand.sol:hobbes@earthcommand.org/autodiscover/autodiscover.xml').to_return(
126
+ :body => SETTINGS_AUTODISCOVER_RESPONSE,
127
+ :status => 200,
128
+ :headers => { 'Content-Length' => SETTINGS_AUTODISCOVER_RESPONSE.size }
129
+ )
130
+ response = @client.get_services(@credentials)
131
+ assert_not_nil response
132
+ assert_equal 'https://ews.spacecommand.sol/EWS/Exchange.asmx', response.ews_url
133
+ end
134
+
135
+ # Test the case where a post to a standard secure address returns a redirectAddr response that
136
+ # includes a new email address to use.
137
+ def test_redirect_addr_response
138
+ WebMock::stub_request(:post, "https://spiff%40spacecommand.sol:hobbes@autodiscover.spacecommand.sol/autodiscover/autodiscover.xml").to_return(
139
+ :body => REDIRECTADDR_AUTODISCOVER_RESPONSE,
140
+ :status => 200,
141
+ :headers => { 'Content-Length' => REDIRECTURL_AUTODISCOVER_RESPONSE.size }
142
+ )
143
+ WebMock::stub_request(:post, 'https://calvin%40spacecommand.sol:hobbes@spacecommand.sol/autodiscover/autodiscover.xml').to_return(
144
+ :body => SETTINGS_AUTODISCOVER_RESPONSE,
145
+ :status => 200,
146
+ :headers => { 'Content-Length' => SETTINGS_AUTODISCOVER_RESPONSE.size }
147
+ )
148
+ response = @client.get_services(@credentials)
149
+ assert_not_nil response
150
+ assert_equal 'https://ews.spacecommand.sol/EWS/Exchange.asmx', response.ews_url
151
+ end
152
+
153
+ # Test the case where there is an infinite loop of HTTP redirects. A limit should be hit
154
+ # and a nil result should be returned.
155
+ def test_http_redirect_limit
156
+ WebMock::stub_request(:post, /spacecommand.sol/).to_return(
157
+ :status => 302,
158
+ :headers => { 'Location' => 'https://spacecommand.sol/autodiscover/autodiscover.xml' }
159
+ )
160
+ response = @client.get_services(@credentials)
161
+ assert_nil response
162
+ end
163
+
164
+ # Test the case where there is an infinite loop created by circular redirectUrl responses.
165
+ # The redirect limit should be reached and a nil result should be returned.
166
+ def test_redirect_url_response_limit
167
+ WebMock::stub_request(:post, "https://spiff%40spacecommand.sol:hobbes@spacecommand.sol/autodiscover/autodiscover.xml").to_return(
168
+ :body => REDIRECTURL_AUTODISCOVER_RESPONSE,
169
+ :status => 200,
170
+ :headers => { 'Content-Length' => REDIRECTURL_AUTODISCOVER_RESPONSE.size }
171
+ )
172
+ WebMock::stub_request(:post, 'https://spiff%40spacecommand.sol:hobbes@earthcommand.org/autodiscover/autodiscover.xml').to_return(
173
+ :body => REDIRECTURL_AUTODISCOVER_RESPONSE,
174
+ :status => 200,
175
+ :headers => { 'Content-Length' => SETTINGS_AUTODISCOVER_RESPONSE.size }
176
+ )
177
+ response = @client.get_services(@credentials)
178
+ assert_nil response
179
+ end
180
+
181
+ def test_redirect_addr_response_limit
182
+ WebMock::stub_request(:post, "https://spiff%40spacecommand.sol:hobbes@spacecommand.sol/autodiscover/autodiscover.xml").to_return(
183
+ :body => REDIRECTADDR_AUTODISCOVER_RESPONSE,
184
+ :status => 200,
185
+ :headers => { 'Content-Length' => REDIRECTURL_AUTODISCOVER_RESPONSE.size }
186
+ )
187
+ WebMock::stub_request(:post, 'https://calvin%40spacecommand.sol:hobbes@spacecommand.sol/autodiscover/autodiscover.xml').to_return(
188
+ :body => REDIRECTADDR_AUTODISCOVER_RESPONSE,
189
+ :status => 200,
190
+ :headers => { 'Content-Length' => SETTINGS_AUTODISCOVER_RESPONSE.size }
191
+ )
192
+ response = @client.get_services(@credentials)
193
+ assert_nil response
194
+ end
195
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: autodiscover
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - David King
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-01-02 00:00:00 -08:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: nokogiri
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: httpclient
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :runtime
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: webmock
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ type: :development
58
+ version_requirements: *id003
59
+ description: Library to find the Autodiscover server and to get from it the URLs and settings needed to access Web services available from Exchange servers.
60
+ email: dking@bestinclass.com
61
+ executables: []
62
+
63
+ extensions: []
64
+
65
+ extra_rdoc_files:
66
+ - MIT-LICENSE
67
+ - README.md
68
+ files:
69
+ - CHANGELOG
70
+ - README.md
71
+ - MIT-LICENSE
72
+ - lib/autodiscover/client.rb
73
+ - lib/autodiscover/credentials.rb
74
+ - lib/autodiscover/services.rb
75
+ - lib/autodiscover.rb
76
+ - test/unit_test.rb
77
+ has_rdoc: true
78
+ homepage: http://github.com/wimm/autodiscover
79
+ licenses: []
80
+
81
+ post_install_message:
82
+ rdoc_options: []
83
+
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ segments:
92
+ - 1
93
+ - 8
94
+ - 7
95
+ version: 1.8.7
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ requirements: []
105
+
106
+ rubyforge_project:
107
+ rubygems_version: 1.3.7
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: Ruby client for Microsoft's Autodiscover Service
111
+ test_files:
112
+ - test/unit_test.rb