autodiscover 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 67ca31043ee9ef53aebe329e4a07d65b0d0fb36a
4
+ data.tar.gz: 1bc1b1a2063a628051f8cb218b983db4088fc41e
5
+ SHA512:
6
+ metadata.gz: c643328d246d2d51e3882af159b2c3fabd27854441deb673b5bde042542faf352901b17a96a7c266becf63cb4748730393d16e859772dbce0db038dab59f50bc
7
+ data.tar.gz: 22413ce0ba0e22beefccec5409ac183ac8244d70567a601128bdab8831bd652e2eb2f98cc9900f967de6b8e6a211c309349ea7ed9d33eb2a5e6caeb2a2ab91db
@@ -0,0 +1,19 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.ruby-version
11
+ /.ruby-gemset
12
+ /.rvm
13
+ *.bundle
14
+ *.so
15
+ *.o
16
+ *.a
17
+ mkmf.log
18
+ scratch.rb
19
+ *.sw[op]
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.0
4
+ - rbx-2
5
+ - ruby-head
6
+ - jruby-head
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in autodiscover.gemspec
4
+ gemspec
data/README.md CHANGED
@@ -1,9 +1,11 @@
1
+ [![Build Status](https://travis-ci.org/WinRb/autodiscover.svg?branch=master)](https://travis-ci.org/WinRb/autodiscover)
2
+
1
3
  Autodiscover
2
4
  ============
3
5
 
4
6
  Ruby client for Microsoft's Autodiscover Service.
5
7
 
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.
8
+ The Autodiscover Service is a component of the Microsoft Exchange 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
9
 
8
10
  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
11
 
@@ -14,53 +16,58 @@ This library requires the following Gems:
14
16
 
15
17
  * HTTPClient
16
18
  * Nokogiri
19
+ * Nori
17
20
 
18
21
  The HTTPClient Gem in turn requires the rubyntlm Gem for Negotiate/NTLM authentication.
19
22
 
20
- For unit testing the webmock Gem is also used.
21
-
22
23
  How to Use
23
24
  ----------
24
25
 
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
26
+ ```ruby
27
+ require 'autodiscover'
28
+
29
+ client = Autodiscover::Client.new(email: "blumbergh@initech.local", password: "tps_eq_awesome")
30
+ data = client.autodiscover
31
+
32
+ # Get the EWS endpoint
33
+ data.ews_url
34
+
35
+ # Get an Exchange Version ingestible by EWS
36
+ data.exchange_version
37
+
38
+ # Access the raw Autodiscover data in its entirety
39
+ data.response
40
+ ```
32
41
 
33
42
  Options
34
43
  -------
35
44
 
36
- ### Debugging
37
-
38
- For debugging, we extend the use of the debug_dev option in the HTTPClient library.
45
+ Besides `:email` and `:password`, `Autodiscover::Client` can take a few other options as can the #autodiscover method.
39
46
 
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
47
+ Examples:
45
48
 
46
- ### Connection Timeouts
49
+ ```ruby
50
+ # Use a different username than your e-mail.
51
+ client = Autodiscover::Client.new(email: "blumbergh@initech.local", password: "tps_eq_awesome", username: 'INITECH\blumbergh')
47
52
 
48
- To adjust the connection timeout values used when searching for Autodiscover server endpoints:
53
+ # Override the domain
54
+ client = Autodiscover::Client.new(email: "blumbergh@initech.local", password: "tps_eq_awesome", domain: "tpsreports.local")
49
55
 
50
- client = Autodiscover::Client.new(:connect_timeout => 5)
56
+ # Ignore SSL Errors
57
+ client.autodiscover(ignore_ssl_errors: true)
58
+ ```
51
59
 
52
- The units are seconds.
53
60
 
54
61
  Installation
55
62
  ------------
56
63
 
57
64
  ### Configuring a Rails App to use the latest GitHub master version
58
65
 
59
- gem 'autodiscover', :git => 'git://github.com/wimm/autodiscover.git'
66
+ gem 'autodiscover', :git => 'git://github.com/WinRb/autodiscover.git'
60
67
 
61
68
  ### To install the latest development version from the GitHub master
62
69
 
63
- git clone http://github.com/wimm/autodiscover.git
70
+ git clone http://github.com/WinRb/autodiscover.git
64
71
  cd autodiscover
65
72
  gem build autodiscover.gemspec
66
73
  sudo gem install autodiscover-<version>.gem
@@ -71,11 +78,5 @@ Bugs and Issues
71
78
  Limitations:
72
79
 
73
80
  * 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
 
81
- Copyright (c) 2010-2011 WIMM Labs, Inc. See MIT-LICENSE for details.
82
+ Please submit additional bugs and issues here [http://github.com/WinRb/autodiscover/issues](http://github.com/WinRb/autodiscover/issues)
@@ -0,0 +1,19 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ task :default => :test
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << 'lib'
8
+ t.libs << 'test'
9
+ t.pattern = 'test/**/*_test.rb'
10
+ t.verbose = false
11
+ end
12
+
13
+ desc "Open a Pry console for this library"
14
+ task :console do
15
+ require "pry"
16
+ require "autodiscover"
17
+ ARGV.clear
18
+ Pry.start
19
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'autodiscover/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'autodiscover'
8
+ s.version = Autodiscover::VERSION
9
+ s.summary = "Ruby client for Microsoft's Autodiscover Service"
10
+ s.description = "The Autodiscover Service provides information about a Microsoft Exchange environment such as service URLs, versions and many other attributes."
11
+ s.required_ruby_version = '>= 2.1.0'
12
+
13
+ s.authors = ["David King", "Dan Wanek"]
14
+ s.email = ["dking@bestinclass.com", "dan.wanek@gmail.com"]
15
+ s.homepage = 'http://github.com/WinRb/autodiscover'
16
+
17
+ s.files = `git ls-files -z`.split("\x0")
18
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_runtime_dependency "nokogiri"
22
+ s.add_runtime_dependency "nori"
23
+ s.add_runtime_dependency "httpclient"
24
+
25
+ s.add_development_dependency "minitest", "~> 5.6.0"
26
+ s.add_development_dependency "mocha", "~> 1.1.0"
27
+ s.add_development_dependency "bundler"
28
+ s.add_development_dependency "rake"
29
+ s.add_development_dependency "pry"
30
+ end
@@ -1,26 +1,13 @@
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
- #++
1
+ require "autodiscover/version"
2
+ require "nokogiri"
3
+ require "nori"
4
+ require "httpclient"
23
5
 
24
- require 'autodiscover/client'
25
- require 'autodiscover/credentials'
26
- require 'autodiscover/services'
6
+ module Autodiscover
7
+ end
8
+
9
+ require "autodiscover/errors"
10
+ require "autodiscover/client"
11
+ require "autodiscover/pox_request"
12
+ require "autodiscover/pox_response"
13
+ require "autodiscover/server_version_parser"
@@ -1,226 +1,33 @@
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
1
  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
2
  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
3
 
4
+ attr_reader :domain, :email, :http
5
+
6
+ # @param email [String] An e-mail to use for autodiscovery. It will be
7
+ # used as the default username.
8
+ # @param password [String]
9
+ # @param username [String] An optional username if you want to authenticate
10
+ # with something other than the e-mail. For instance DOMAIN\user
11
+ # @param domain [String] An optional domain to provide as an override for
12
+ # the one parsed from the e-mail.
13
+ def initialize(email:, password:, username: nil, domain: nil)
14
+ @email = email
15
+ @domain = domain || @email.split("@").last
46
16
  @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
17
+ @username = username || email
18
+ @http.set_auth(nil, @username, password)
78
19
  end
79
20
 
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)
21
+ # @param type [Symbol] The type of response. Right now this is just :pox
22
+ # @param [Hash] **options
23
+ def autodiscover(type: :pox, **options)
24
+ case type
25
+ when :pox
26
+ PoxRequest.new(self, **options).autodiscover
88
27
  else
89
- nil
28
+ raise Autodiscover::ArgumentError, "Not a valid autodiscover type (#{type})."
90
29
  end
91
30
  end
92
31
 
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
32
  end
226
- end
33
+ end