autodiscover 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +81 -0
- data/lib/autodiscover.rb +26 -0
- data/lib/autodiscover/client.rb +226 -0
- data/lib/autodiscover/credentials.rb +51 -0
- data/lib/autodiscover/services.rb +40 -0
- data/test/unit_test.rb +195 -0
- metadata +112 -0
data/CHANGELOG
ADDED
data/MIT-LICENSE
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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.
|
data/lib/autodiscover.rb
ADDED
@@ -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
|
data/test/unit_test.rb
ADDED
@@ -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
|