omniauth 0.2.4 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of omniauth might be problematic. Click here for more details.
- data/.autotest +9 -0
- data/.document +5 -0
- data/.gitignore +36 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/.yardopts +4 -0
- data/Gemfile +6 -1
- data/README.markdown +121 -0
- data/Rakefile +50 -2
- data/lib/omniauth/version.rb +18 -4
- data/oa-basic/.gemtest +0 -0
- data/oa-basic/.rspec +3 -0
- data/oa-basic/.yardopts +4 -0
- data/oa-basic/LICENSE +19 -0
- data/oa-basic/README.rdoc +34 -0
- data/oa-basic/Rakefile +6 -0
- data/oa-basic/lib/oa-basic.rb +1 -0
- data/oa-basic/lib/omniauth/basic.rb +8 -0
- data/oa-basic/lib/omniauth/strategies/http_basic.rb +56 -0
- data/oa-basic/lib/omniauth/version.rb +19 -0
- data/oa-basic/oa-basic.gemspec +28 -0
- data/oa-basic/spec/omniauth/strategies/basic_spec.rb +7 -0
- data/oa-basic/spec/spec_helper.rb +11 -0
- data/oa-core/.gemtest +0 -0
- data/oa-core/.rspec +3 -0
- data/oa-core/.yardopts +4 -0
- data/oa-core/LICENSE +19 -0
- data/oa-core/Rakefile +6 -0
- data/oa-core/autotest/discover.rb +1 -0
- data/oa-core/lib/oa-core.rb +1 -0
- data/oa-core/lib/omniauth/builder.rb +33 -0
- data/oa-core/lib/omniauth/core.rb +135 -0
- data/oa-core/lib/omniauth/form.rb +184 -0
- data/oa-core/lib/omniauth/strategy.rb +227 -0
- data/oa-core/lib/omniauth/test.rb +12 -0
- data/oa-core/lib/omniauth/test/phony_session.rb +8 -0
- data/oa-core/lib/omniauth/test/strategy_macros.rb +34 -0
- data/oa-core/lib/omniauth/test/strategy_test_case.rb +49 -0
- data/oa-core/lib/omniauth/version.rb +19 -0
- data/oa-core/oa-core.gemspec +24 -0
- data/oa-core/spec/omniauth/builder_spec.rb +20 -0
- data/oa-core/spec/omniauth/core_spec.rb +79 -0
- data/oa-core/spec/omniauth/strategy_spec.rb +358 -0
- data/oa-core/spec/spec_helper.rb +12 -0
- data/oa-enterprise/.gemtest +0 -0
- data/oa-enterprise/.rspec +3 -0
- data/oa-enterprise/.yardopts +4 -0
- data/oa-enterprise/LICENSE +19 -0
- data/oa-enterprise/README.rdoc +82 -0
- data/oa-enterprise/Rakefile +6 -0
- data/oa-enterprise/lib/oa-enterprise.rb +1 -0
- data/oa-enterprise/lib/omniauth/enterprise.rb +8 -0
- data/oa-enterprise/lib/omniauth/strategies/cas.rb +47 -0
- data/oa-enterprise/lib/omniauth/strategies/cas/configuration.rb +98 -0
- data/oa-enterprise/lib/omniauth/strategies/cas/service_ticket_validator.rb +84 -0
- data/oa-enterprise/lib/omniauth/strategies/ldap.rb +111 -0
- data/oa-enterprise/lib/omniauth/strategies/ldap/adaptor.rb +279 -0
- data/oa-enterprise/lib/omniauth/version.rb +19 -0
- data/oa-enterprise/oa-enterprise.gemspec +32 -0
- data/oa-enterprise/spec/fixtures/cas_failure.xml +4 -0
- data/oa-enterprise/spec/fixtures/cas_success.xml +8 -0
- data/oa-enterprise/spec/omniauth/strategies/cas_spec.rb +94 -0
- data/oa-enterprise/spec/spec_helper.rb +14 -0
- data/oa-more/.gemtest +0 -0
- data/oa-more/.rspec +3 -0
- data/oa-more/.yardopts +4 -0
- data/oa-more/LICENSE +19 -0
- data/oa-more/README.rdoc +22 -0
- data/oa-more/Rakefile +6 -0
- data/oa-more/lib/oa-more.rb +1 -0
- data/oa-more/lib/omniauth/more.rb +9 -0
- data/oa-more/lib/omniauth/strategies/flickr.rb +86 -0
- data/oa-more/lib/omniauth/strategies/windows_live.rb +39 -0
- data/oa-more/lib/omniauth/strategies/windows_live/windowslivelogin.rb +1143 -0
- data/oa-more/lib/omniauth/strategies/yupoo.rb +67 -0
- data/oa-more/lib/omniauth/version.rb +19 -0
- data/oa-more/oa-more.gemspec +30 -0
- data/oa-more/spec/omniauth/strategies/flickr_spec.rb +7 -0
- data/oa-more/spec/spec_helper.rb +11 -0
- data/oa-oauth/.gemtest +0 -0
- data/oa-oauth/.rspec +3 -0
- data/oa-oauth/.yardopts +4 -0
- data/oa-oauth/LICENSE +19 -0
- data/oa-oauth/README.rdoc +35 -0
- data/oa-oauth/Rakefile +6 -0
- data/oa-oauth/autotest/discover.rb +1 -0
- data/oa-oauth/lib/oa-oauth.rb +1 -0
- data/oa-oauth/lib/omniauth/oauth.rb +53 -0
- data/oa-oauth/lib/omniauth/strategies/bitly.rb +46 -0
- data/oa-oauth/lib/omniauth/strategies/dailymile.rb +64 -0
- data/oa-oauth/lib/omniauth/strategies/doit.rb +60 -0
- data/oa-oauth/lib/omniauth/strategies/dopplr.rb +53 -0
- data/oa-oauth/lib/omniauth/strategies/douban.rb +60 -0
- data/oa-oauth/lib/omniauth/strategies/evernote.rb +54 -0
- data/oa-oauth/lib/omniauth/strategies/facebook.rb +70 -0
- data/oa-oauth/lib/omniauth/strategies/foursquare.rb +62 -0
- data/oa-oauth/lib/omniauth/strategies/github.rb +50 -0
- data/oa-oauth/lib/omniauth/strategies/goodreads.rb +44 -0
- data/oa-oauth/lib/omniauth/strategies/google.rb +80 -0
- data/oa-oauth/lib/omniauth/strategies/gowalla.rb +64 -0
- data/oa-oauth/lib/omniauth/strategies/hyves.rb +67 -0
- data/oa-oauth/lib/omniauth/strategies/identica.rb +49 -0
- data/oa-oauth/lib/omniauth/strategies/instagram.rb +56 -0
- data/oa-oauth/lib/omniauth/strategies/instapaper.rb +40 -0
- data/oa-oauth/lib/omniauth/strategies/linked_in.rb +55 -0
- data/oa-oauth/lib/omniauth/strategies/meetup.rb +56 -0
- data/oa-oauth/lib/omniauth/strategies/miso.rb +41 -0
- data/oa-oauth/lib/omniauth/strategies/mixi.rb +59 -0
- data/oa-oauth/lib/omniauth/strategies/netflix.rb +65 -0
- data/oa-oauth/lib/omniauth/strategies/oauth.rb +85 -0
- data/oa-oauth/lib/omniauth/strategies/oauth2.rb +98 -0
- data/oa-oauth/lib/omniauth/strategies/qzone.rb +69 -0
- data/oa-oauth/lib/omniauth/strategies/rdio.rb +45 -0
- data/oa-oauth/lib/omniauth/strategies/renren.rb +87 -0
- data/oa-oauth/lib/omniauth/strategies/salesforce.rb +44 -0
- data/oa-oauth/lib/omniauth/strategies/smug_mug.rb +42 -0
- data/oa-oauth/lib/omniauth/strategies/sound_cloud.rb +46 -0
- data/oa-oauth/lib/omniauth/strategies/t163.rb +57 -0
- data/oa-oauth/lib/omniauth/strategies/teambox.rb +49 -0
- data/oa-oauth/lib/omniauth/strategies/thirty_seven_signals.rb +41 -0
- data/oa-oauth/lib/omniauth/strategies/tqq.rb +64 -0
- data/oa-oauth/lib/omniauth/strategies/trade_me.rb +45 -0
- data/oa-oauth/lib/omniauth/strategies/trip_it.rb +22 -0
- data/oa-oauth/lib/omniauth/strategies/tsina.rb +79 -0
- data/oa-oauth/lib/omniauth/strategies/tsohu.rb +57 -0
- data/oa-oauth/lib/omniauth/strategies/tumblr.rb +60 -0
- data/oa-oauth/lib/omniauth/strategies/twitter.rb +57 -0
- data/oa-oauth/lib/omniauth/strategies/type_pad.rb +76 -0
- data/oa-oauth/lib/omniauth/strategies/vimeo.rb +54 -0
- data/oa-oauth/lib/omniauth/strategies/vkontakte.rb +84 -0
- data/oa-oauth/lib/omniauth/strategies/xauth.rb +67 -0
- data/oa-oauth/lib/omniauth/strategies/yahoo.rb +55 -0
- data/oa-oauth/lib/omniauth/strategies/yammer.rb +43 -0
- data/oa-oauth/lib/omniauth/strategies/you_tube.rb +73 -0
- data/oa-oauth/lib/omniauth/version.rb +19 -0
- data/oa-oauth/oa-oauth.gemspec +33 -0
- data/oa-oauth/spec/fixtures/basecamp_200.xml +24 -0
- data/oa-oauth/spec/fixtures/campfire_200.json +10 -0
- data/oa-oauth/spec/omniauth/strategies/bitly_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/dailymile_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/doit_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/dopplr_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/douban_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/evernote_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/facebook_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/foursquare_spec.rb +18 -0
- data/oa-oauth/spec/omniauth/strategies/github_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/goodreads_spec.rb +6 -0
- data/oa-oauth/spec/omniauth/strategies/google_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/gowalla_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/hyves_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/identica_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/linked_in_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/meetup_spec.rb +14 -0
- data/oa-oauth/spec/omniauth/strategies/miso_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/netflix_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/oauth2_spec.rb +0 -0
- data/oa-oauth/spec/omniauth/strategies/oauth_spec.rb +77 -0
- data/oa-oauth/spec/omniauth/strategies/rdio_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/salesforce_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/smug_mug_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/sound_cloud_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/t163_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/teambox_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/thirty_seven_signals_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/trade_me_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/trip_it_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/tsina_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/tumblr_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/twitter_spec.rb +20 -0
- data/oa-oauth/spec/omniauth/strategies/type_pad_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/vimeo_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/vkontakte_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/yahoo_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/yammer_spec.rb +5 -0
- data/oa-oauth/spec/omniauth/strategies/you_tube_spec.rb +5 -0
- data/oa-oauth/spec/spec_helper.rb +27 -0
- data/oa-oauth/spec/support/shared_examples.rb +29 -0
- data/oa-openid/.gemtest +0 -0
- data/oa-openid/.rspec +3 -0
- data/oa-openid/.yardopts +4 -0
- data/oa-openid/LICENSE +19 -0
- data/oa-openid/README.rdoc +51 -0
- data/oa-openid/Rakefile +6 -0
- data/oa-openid/lib/oa-openid.rb +1 -0
- data/oa-openid/lib/omniauth/openid.rb +59 -0
- data/oa-openid/lib/omniauth/openid/gapps.rb +32 -0
- data/oa-openid/lib/omniauth/strategies/google_apps.rb +23 -0
- data/oa-openid/lib/omniauth/strategies/open_id.rb +132 -0
- data/oa-openid/lib/omniauth/version.rb +19 -0
- data/oa-openid/oa-openid.gemspec +29 -0
- data/oa-openid/spec/omniauth/strategies/open_id_spec.rb +71 -0
- data/oa-openid/spec/spec_helper.rb +14 -0
- data/omniauth.gemspec +3 -3
- data/tasks/all.rb +134 -0
- metadata +199 -9
- data/README.rdoc +0 -17
@@ -0,0 +1 @@
|
|
1
|
+
require 'omniauth/enterprise'
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'omniauth/enterprise'
|
2
|
+
|
3
|
+
module OmniAuth
|
4
|
+
module Strategies
|
5
|
+
class CAS
|
6
|
+
include OmniAuth::Strategy
|
7
|
+
|
8
|
+
autoload :Configuration, 'omniauth/strategies/cas/configuration'
|
9
|
+
autoload :ServiceTicketValidator, 'omniauth/strategies/cas/service_ticket_validator'
|
10
|
+
|
11
|
+
def initialize(app, options = {}, &block)
|
12
|
+
super(app, options[:name] || :cas, options.dup, &block)
|
13
|
+
@configuration = OmniAuth::Strategies::CAS::Configuration.new(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def request_phase
|
19
|
+
[
|
20
|
+
302,
|
21
|
+
{
|
22
|
+
'Location' => @configuration.login_url(callback_url),
|
23
|
+
'Content-Type' => 'text/plain'
|
24
|
+
},
|
25
|
+
["You are being redirected to CAS for sign-in."]
|
26
|
+
]
|
27
|
+
end
|
28
|
+
|
29
|
+
def callback_phase
|
30
|
+
ticket = request.params['ticket']
|
31
|
+
return fail!(:no_ticket, 'No CAS Ticket') unless ticket
|
32
|
+
validator = ServiceTicketValidator.new(@configuration, callback_url, ticket)
|
33
|
+
@user_info = validator.user_info
|
34
|
+
return fail!(:invalid_ticket, 'Invalid CAS Ticket') if @user_info.nil? || @user_info.empty?
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
def auth_hash
|
39
|
+
OmniAuth::Utils.deep_merge(super, {
|
40
|
+
'uid' => @user_info.delete('user'),
|
41
|
+
'extra' => @user_info
|
42
|
+
})
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
module OmniAuth
|
4
|
+
module Strategies
|
5
|
+
class CAS
|
6
|
+
class Configuration
|
7
|
+
|
8
|
+
DEFAULT_LOGIN_URL = "%s/login"
|
9
|
+
|
10
|
+
DEFAULT_SERVICE_VALIDATE_URL = "%s/serviceValidate"
|
11
|
+
|
12
|
+
# @param [Hash] params configuration options
|
13
|
+
# @option params [String, nil] :cas_server the CAS server root URL; probably something like
|
14
|
+
# `http://cas.mycompany.com` or `http://cas.mycompany.com/cas`; optional.
|
15
|
+
# @option params [String, nil] :cas_login_url (:cas_server + '/login') the URL to which to
|
16
|
+
# redirect for logins; options if `:cas_server` is specified,
|
17
|
+
# required otherwise.
|
18
|
+
# @option params [String, nil] :cas_service_validate_url (:cas_server + '/serviceValidate') the
|
19
|
+
# URL to use for validating service tickets; optional if `:cas_server` is
|
20
|
+
# specified, requred otherwise.
|
21
|
+
# @option params [Boolean, nil] :disable_ssl_verification disable verification for SSL cert,
|
22
|
+
# helpful when you developing with a fake cert.
|
23
|
+
def initialize(params)
|
24
|
+
parse_params params
|
25
|
+
end
|
26
|
+
|
27
|
+
# Build a CAS login URL from +service+.
|
28
|
+
#
|
29
|
+
# @param [String] service the service (a.k.a. return-to) URL
|
30
|
+
#
|
31
|
+
# @return [String] a URL like `http://cas.mycompany.com/login?service=...`
|
32
|
+
def login_url(service)
|
33
|
+
append_service @login_url, service
|
34
|
+
end
|
35
|
+
|
36
|
+
# Build a service-validation URL from +service+ and +ticket+.
|
37
|
+
# If +service+ has a ticket param, first remove it. URL-encode
|
38
|
+
# +service+ and add it and the +ticket+ as paraemters to the
|
39
|
+
# CAS serviceValidate URL.
|
40
|
+
#
|
41
|
+
# @param [String] service the service (a.k.a. return-to) URL
|
42
|
+
# @param [String] ticket the ticket to validate
|
43
|
+
#
|
44
|
+
# @return [String] a URL like `http://cas.mycompany.com/serviceValidate?service=...&ticket=...`
|
45
|
+
def service_validate_url(service, ticket)
|
46
|
+
service = service.sub(/[?&]ticket=[^?&]+/, '')
|
47
|
+
url = append_service(@service_validate_url, service)
|
48
|
+
url << '&ticket=' << Rack::Utils.escape(ticket)
|
49
|
+
end
|
50
|
+
|
51
|
+
def disable_ssl_verification?
|
52
|
+
@disable_ssl_verification
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def parse_params(params)
|
58
|
+
if params[:cas_server].nil? && params[:cas_login_url].nil?
|
59
|
+
raise ArgumentError.new(":cas_server or :cas_login_url MUST be provided")
|
60
|
+
end
|
61
|
+
@login_url = params[:cas_login_url]
|
62
|
+
@login_url ||= DEFAULT_LOGIN_URL % params[:cas_server]
|
63
|
+
validate_is_url 'login URL', @login_url
|
64
|
+
|
65
|
+
if params[:cas_server].nil? && params[:cas_service_validate_url].nil?
|
66
|
+
raise ArgumentError.new(":cas_server or :cas_service_validate_url MUST be provided")
|
67
|
+
end
|
68
|
+
@service_validate_url = params[:cas_service_validate_url]
|
69
|
+
@service_validate_url ||= DEFAULT_SERVICE_VALIDATE_URL % params[:cas_server]
|
70
|
+
validate_is_url 'service-validate URL', @service_validate_url
|
71
|
+
|
72
|
+
@disable_ssl_verification = params[:disable_ssl_verification]
|
73
|
+
end
|
74
|
+
|
75
|
+
IS_NOT_URL_ERROR_MESSAGE = "%s is not a valid URL"
|
76
|
+
|
77
|
+
def validate_is_url(name, possibly_a_url)
|
78
|
+
url = URI.parse(possibly_a_url) rescue nil
|
79
|
+
raise ArgumentError.new(IS_NOT_URL_ERROR_MESSAGE % name) unless url.kind_of?(URI::HTTP)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Adds +service+ as an URL-escaped parameter to +base+.
|
83
|
+
#
|
84
|
+
# @param [String] base the base URL
|
85
|
+
# @param [String] service the service (a.k.a. return-to) URL.
|
86
|
+
#
|
87
|
+
# @return [String] the new joined URL.
|
88
|
+
def append_service(base, service)
|
89
|
+
result = base.dup
|
90
|
+
result << (result.include?('?') ? '&' : '?')
|
91
|
+
result << 'service='
|
92
|
+
result << Rack::Utils.escape(service)
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
module OmniAuth
|
6
|
+
module Strategies
|
7
|
+
class CAS
|
8
|
+
class ServiceTicketValidator
|
9
|
+
|
10
|
+
VALIDATION_REQUEST_HEADERS = { 'Accept' => '*/*' }
|
11
|
+
|
12
|
+
# Build a validator from a +configuration+, a
|
13
|
+
# +return_to+ URL, and a +ticket+.
|
14
|
+
#
|
15
|
+
# @param [OmniAuth::Strategies::CAS::Configuration] configuration the CAS configuration
|
16
|
+
# @param [String] return_to_url the URL of this CAS client service
|
17
|
+
# @param [String] ticket the service ticket to validate
|
18
|
+
def initialize(configuration, return_to_url, ticket)
|
19
|
+
@configuration = configuration
|
20
|
+
@uri = URI.parse(@configuration.service_validate_url(return_to_url, ticket))
|
21
|
+
end
|
22
|
+
|
23
|
+
# Request validation of the ticket from the CAS server's
|
24
|
+
# serviceValidate (CAS 2.0) function.
|
25
|
+
#
|
26
|
+
# Swallows all XML parsing errors (and returns +nil+ in those cases).
|
27
|
+
#
|
28
|
+
# @return [Hash, nil] a user information hash if the response is valid; +nil+ otherwise.
|
29
|
+
#
|
30
|
+
# @raise any connection errors encountered.
|
31
|
+
def user_info
|
32
|
+
parse_user_info(find_authentication_success(get_service_response_body))
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# turns an `<cas:authenticationSuccess>` node into a Hash;
|
38
|
+
# returns nil if given nil
|
39
|
+
def parse_user_info(node)
|
40
|
+
return nil if node.nil?
|
41
|
+
node.children.inject({}) do |hash, child|
|
42
|
+
unless child.kind_of?(Nokogiri::XML::Text) ||
|
43
|
+
child.name == 'cas:proxies' ||
|
44
|
+
child.name == 'proxies'
|
45
|
+
hash[child.name.sub(/^cas:/, '')] = child.content
|
46
|
+
end
|
47
|
+
hash
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# finds an `<cas:authenticationSuccess>` node in
|
52
|
+
# a `<cas:serviceResponse>` body if present; returns nil
|
53
|
+
# if the passed body is nil or if there is no such node.
|
54
|
+
def find_authentication_success(body)
|
55
|
+
return nil if body.nil? || body == ''
|
56
|
+
begin
|
57
|
+
doc = Nokogiri::XML(body)
|
58
|
+
begin
|
59
|
+
doc.xpath('/cas:serviceResponse/cas:authenticationSuccess')
|
60
|
+
rescue Nokogiri::XML::XPath::SyntaxError
|
61
|
+
doc.xpath('/serviceResponse/authenticationSuccess')
|
62
|
+
end
|
63
|
+
rescue Nokogiri::XML::XPath::SyntaxError
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# retrieves the `<cas:serviceResponse>` XML from the CAS server
|
69
|
+
def get_service_response_body
|
70
|
+
result = ''
|
71
|
+
http = ::Net::HTTP.new(@uri.host, @uri.port)
|
72
|
+
http.use_ssl = @uri.port == 443 || @uri.instance_of?(URI::HTTPS)
|
73
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl? && @configuration.disable_ssl_verification?
|
74
|
+
http.start do |c|
|
75
|
+
response = c.get "#{@uri.path}?#{@uri.query}", VALIDATION_REQUEST_HEADERS.dup
|
76
|
+
result = response.body
|
77
|
+
end
|
78
|
+
result
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'omniauth/enterprise'
|
2
|
+
require 'net/ldap'
|
3
|
+
require 'sasl/base'
|
4
|
+
require 'sasl'
|
5
|
+
|
6
|
+
module OmniAuth
|
7
|
+
module Strategies
|
8
|
+
class LDAP
|
9
|
+
include OmniAuth::Strategy
|
10
|
+
|
11
|
+
autoload :Adaptor, 'omniauth/strategies/ldap/adaptor'
|
12
|
+
@@config = {'name' => 'cn',
|
13
|
+
'first_name' => 'givenName',
|
14
|
+
'last_name' => 'sn',
|
15
|
+
'email' => ['mail', "email", 'userPrincipalName'],
|
16
|
+
'phone' => ['telephoneNumber', 'homePhone', 'facsimileTelephoneNumber'],
|
17
|
+
'mobile_number' => ['mobile', 'mobileTelephoneNumber'],
|
18
|
+
'nickname' => ['uid', 'userid', 'sAMAccountName'],
|
19
|
+
'title' => 'title',
|
20
|
+
'location' => {"%0, %1, %2, %3 %4" => [['address', 'postalAddress', 'homePostalAddress', 'street', 'streetAddress'], ['l'], ['st'],['co'],['postOfficeBox']]},
|
21
|
+
'uid' => 'dn',
|
22
|
+
'url' => ['wwwhomepage'],
|
23
|
+
'image' => 'jpegPhoto',
|
24
|
+
'description' => 'description'}
|
25
|
+
|
26
|
+
# Initialize the LDAP Middleware
|
27
|
+
#
|
28
|
+
# @param [Rack Application] app Standard Rack middleware argument.
|
29
|
+
# @option options [String, 'LDAP Authentication'] :title A title for the authentication form.
|
30
|
+
def initialize(app, options = {}, &block)
|
31
|
+
super(app, options[:name] || :ldap, options.dup, &block)
|
32
|
+
@name_proc = (@options.delete(:name_proc) || Proc.new {|name| name})
|
33
|
+
@adaptor = OmniAuth::Strategies::LDAP::Adaptor.new(options)
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def request_phase
|
39
|
+
if env['REQUEST_METHOD'] == 'GET'
|
40
|
+
get_credentials
|
41
|
+
else
|
42
|
+
session['omniauth.ldap'] = {'username' => request['username'], 'password' => request['password']}
|
43
|
+
redirect callback_path
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_credentials
|
48
|
+
OmniAuth::Form.build(:title => (options[:title] || "LDAP Authentication")) do
|
49
|
+
text_field 'Login', 'username'
|
50
|
+
password_field 'Password', 'password'
|
51
|
+
end.to_response
|
52
|
+
end
|
53
|
+
|
54
|
+
def callback_phase
|
55
|
+
begin
|
56
|
+
creds = session['omniauth.ldap']
|
57
|
+
session.delete 'omniauth.ldap'
|
58
|
+
@ldap_user_info = {}
|
59
|
+
begin
|
60
|
+
(@adaptor.bind(:allow_anonymous => true) unless @adaptor.bound?)
|
61
|
+
rescue Exception => e
|
62
|
+
puts "failed to bind with the default credentials: " + e.message
|
63
|
+
end
|
64
|
+
@ldap_user_info = @adaptor.search(:filter => Net::LDAP::Filter.eq(@adaptor.uid, @name_proc.call(creds['username'])),:limit => 1) if @adaptor.bound?
|
65
|
+
bind_dn = creds['username']
|
66
|
+
bind_dn = @ldap_user_info[:dn].to_a.first if @ldap_user_info[:dn]
|
67
|
+
@adaptor.bind(:bind_dn => bind_dn, :password => creds['password'])
|
68
|
+
@ldap_user_info = @adaptor.search(:filter => Net::LDAP::Filter.eq(@adaptor.uid, @name_proc.call(creds['username'])),:limit => 1) if @ldap_user_info.empty?
|
69
|
+
@user_info = self.class.map_user(@@config, @ldap_user_info)
|
70
|
+
|
71
|
+
@env['omniauth.auth'] = auth_hash
|
72
|
+
|
73
|
+
rescue Exception => e
|
74
|
+
return fail!(:invalid_credentials, e)
|
75
|
+
end
|
76
|
+
call_app!
|
77
|
+
end
|
78
|
+
|
79
|
+
def auth_hash
|
80
|
+
OmniAuth::Utils.deep_merge(super, {
|
81
|
+
'uid' => @user_info["uid"],
|
82
|
+
'user_info' => @user_info,
|
83
|
+
'extra' => @ldap_user_info
|
84
|
+
})
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.map_user(mapper, object)
|
88
|
+
user = {}
|
89
|
+
mapper.each do |key, value|
|
90
|
+
case value
|
91
|
+
when String
|
92
|
+
user[key] = object[value.downcase.to_sym].to_s if object[value.downcase.to_sym]
|
93
|
+
when Array
|
94
|
+
value.each {|v| (user[key] = object[v.downcase.to_sym].to_s; break;) if object[v.downcase.to_sym]}
|
95
|
+
when Hash
|
96
|
+
value.map do |key1, value1|
|
97
|
+
pattern = key1.dup
|
98
|
+
value1.each_with_index do |v,i|
|
99
|
+
part = '';
|
100
|
+
v.each {|v1| (part = object[v1.downcase.to_sym].to_s; break;) if object[v1.downcase.to_sym]}
|
101
|
+
pattern.gsub!("%#{i}",part||'')
|
102
|
+
end
|
103
|
+
user[key] = pattern
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
user
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,279 @@
|
|
1
|
+
#this code boughts pieces from activeldap and net-ldap
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
require 'net/ldap'
|
5
|
+
require 'net/ntlm'
|
6
|
+
require 'uri'
|
7
|
+
|
8
|
+
module OmniAuth
|
9
|
+
module Strategies
|
10
|
+
class LDAP
|
11
|
+
class Adaptor
|
12
|
+
class LdapError < StandardError; end
|
13
|
+
class ConfigurationError < StandardError; end
|
14
|
+
class AuthenticationError < StandardError; end
|
15
|
+
class ConnectionError < StandardError; end
|
16
|
+
|
17
|
+
VALID_ADAPTER_CONFIGURATION_KEYS = [:host, :port, :method, :bind_dn, :password,
|
18
|
+
:try_sasl, :sasl_mechanisms, :uid, :base, :allow_anonymous]
|
19
|
+
|
20
|
+
MUST_HAVE_KEYS = [:host, :port, :method, :uid, :base]
|
21
|
+
|
22
|
+
METHOD = {
|
23
|
+
:ssl => :simple_tls,
|
24
|
+
:tls => :start_tls,
|
25
|
+
:plain => nil,
|
26
|
+
}
|
27
|
+
|
28
|
+
attr_accessor :bind_dn, :password
|
29
|
+
attr_reader :connection, :uid, :base
|
30
|
+
|
31
|
+
def initialize(configuration={})
|
32
|
+
@connection = nil
|
33
|
+
@disconnected = false
|
34
|
+
@bound = false
|
35
|
+
@configuration = configuration.dup
|
36
|
+
@configuration[:allow_anonymous] ||= false
|
37
|
+
@logger = @configuration.delete(:logger)
|
38
|
+
message = []
|
39
|
+
MUST_HAVE_KEYS.each do |name|
|
40
|
+
message << name if configuration[name].nil?
|
41
|
+
end
|
42
|
+
raise ArgumentError.new(message.join(",") +" MUST be provided") unless message.empty?
|
43
|
+
VALID_ADAPTER_CONFIGURATION_KEYS.each do |name|
|
44
|
+
instance_variable_set("@#{name}", configuration[name])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def connect(options={})
|
49
|
+
host = options[:host] || @host
|
50
|
+
method = ensure_method(options[:method] || @method || :plain)
|
51
|
+
port = options[:port] || @port || ensure_port(method)
|
52
|
+
@disconnected = false
|
53
|
+
@bound = false
|
54
|
+
@bind_tried = false
|
55
|
+
|
56
|
+
config = {
|
57
|
+
:host => host,
|
58
|
+
:port => port,
|
59
|
+
}
|
60
|
+
|
61
|
+
config[:encryption] = {:method => method} if method
|
62
|
+
|
63
|
+
@connection, @uri, @with_start_tls = begin
|
64
|
+
uri = construct_uri(host, port, method == :simple_tls)
|
65
|
+
with_start_tls = method == :start_tls
|
66
|
+
puts ({:uri => uri, :with_start_tls => with_start_tls}).inspect
|
67
|
+
[Net::LDAP::Connection.new(config), uri, with_start_tls]
|
68
|
+
rescue Net::LDAP::LdapError
|
69
|
+
raise ConnectionError, $!.message
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def unbind(options={})
|
74
|
+
@connection.close # Net::LDAP doesn't implement unbind.
|
75
|
+
end
|
76
|
+
|
77
|
+
def bind(options={})
|
78
|
+
connect(options) unless connecting?
|
79
|
+
begin
|
80
|
+
@bind_tried = true
|
81
|
+
|
82
|
+
bind_dn = (options[:bind_dn] || @bind_dn).to_s
|
83
|
+
try_sasl = options.has_key?(:try_sasl) ? options[:try_sasl] : @try_sasl
|
84
|
+
if options.has_key?(:allow_anonymous)
|
85
|
+
allow_anonymous = options[:allow_anonymous]
|
86
|
+
else
|
87
|
+
allow_anonymous = @allow_anonymous
|
88
|
+
end
|
89
|
+
# Rough bind loop:
|
90
|
+
# Attempt 1: SASL if available
|
91
|
+
# Attempt 2: SIMPLE with credentials if password block
|
92
|
+
# Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail and allow anonymous is set to true
|
93
|
+
if try_sasl and sasl_bind(bind_dn, options)
|
94
|
+
puts "bound with sasl"
|
95
|
+
elsif simple_bind(bind_dn, options)
|
96
|
+
puts "bound with simple"
|
97
|
+
elsif allow_anonymous and bind_as_anonymous(options)
|
98
|
+
puts "bound as anonymous"
|
99
|
+
else
|
100
|
+
message = yield if block_given?
|
101
|
+
message ||= ('All authentication methods for %s exhausted.') % target
|
102
|
+
raise AuthenticationError, message
|
103
|
+
end
|
104
|
+
@bound = true
|
105
|
+
rescue Net::LDAP::LdapError
|
106
|
+
raise AuthenticationError, $!.message
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def disconnect!(options={})
|
111
|
+
unbind(options)
|
112
|
+
@connection = @uri = @with_start_tls = nil
|
113
|
+
@disconnected = true
|
114
|
+
end
|
115
|
+
|
116
|
+
def rebind(options={})
|
117
|
+
unbind(options) if bound?
|
118
|
+
connect(options)
|
119
|
+
end
|
120
|
+
|
121
|
+
def connecting?
|
122
|
+
!@connection.nil? and !@disconnected
|
123
|
+
end
|
124
|
+
|
125
|
+
def bound?
|
126
|
+
connecting? and @bound
|
127
|
+
end
|
128
|
+
|
129
|
+
def search(options={}, &block)
|
130
|
+
base = options[:base]
|
131
|
+
filter = options[:filter]
|
132
|
+
limit = options[:limit]
|
133
|
+
|
134
|
+
args = {
|
135
|
+
:base => @base,
|
136
|
+
:filter => filter,
|
137
|
+
:size => limit
|
138
|
+
}
|
139
|
+
|
140
|
+
attributes = {}
|
141
|
+
execute(:search, args) do |entry|
|
142
|
+
entry.attribute_names.each do |name|
|
143
|
+
attributes[name] = entry[name]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
attributes
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def execute(method, *args, &block)
|
152
|
+
result = @connection.send(method, *args, &block)
|
153
|
+
message = nil
|
154
|
+
|
155
|
+
if result.is_a?(Hash)
|
156
|
+
message = result[:errorMessage]
|
157
|
+
result = result[:resultCode]
|
158
|
+
end
|
159
|
+
|
160
|
+
unless result.zero?
|
161
|
+
message = [Net::LDAP.result2string(result), message].compact.join(": ")
|
162
|
+
raise LdapError, message
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def ensure_port(method)
|
167
|
+
if method == :ssl
|
168
|
+
URI::LDAPS::DEFAULT_PORT
|
169
|
+
else
|
170
|
+
URI::LDAP::DEFAULT_PORT
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def prepare_connection(options)
|
175
|
+
end
|
176
|
+
|
177
|
+
def ensure_method(method)
|
178
|
+
method ||= "plain"
|
179
|
+
normalized_method = method.to_s.downcase.to_sym
|
180
|
+
return METHOD[normalized_method] if METHOD.has_key?(normalized_method)
|
181
|
+
|
182
|
+
available_methods = METHOD.keys.collect {|m| m.inspect}.join(", ")
|
183
|
+
format = "%s is not one of the available connect methods: %s"
|
184
|
+
raise ConfigurationError, format % [method.inspect, available_methods]
|
185
|
+
end
|
186
|
+
|
187
|
+
def sasl_bind(bind_dn, options={})
|
188
|
+
sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms
|
189
|
+
sasl_mechanisms.each do |mechanism|
|
190
|
+
begin
|
191
|
+
normalized_mechanism = mechanism.downcase.gsub(/-/, '_')
|
192
|
+
sasl_bind_setup = "sasl_bind_setup_#{normalized_mechanism}"
|
193
|
+
next unless respond_to?(sasl_bind_setup, true)
|
194
|
+
initial_credential, challenge_response = send(sasl_bind_setup, bind_dn, options)
|
195
|
+
|
196
|
+
args = {
|
197
|
+
:method => :sasl,
|
198
|
+
:initial_credential => initial_credential,
|
199
|
+
:mechanism => mechanism,
|
200
|
+
:challenge_response => challenge_response,
|
201
|
+
}
|
202
|
+
|
203
|
+
info = {
|
204
|
+
:name => "bind: SASL", :dn => bind_dn, :mechanism => mechanism,
|
205
|
+
}
|
206
|
+
|
207
|
+
execute(:bind, args)
|
208
|
+
return true
|
209
|
+
|
210
|
+
rescue Exception => e
|
211
|
+
puts e.message
|
212
|
+
end
|
213
|
+
end
|
214
|
+
false
|
215
|
+
end
|
216
|
+
|
217
|
+
def sasl_bind_setup_digest_md5(bind_dn, options)
|
218
|
+
initial_credential = ""
|
219
|
+
challenge_response = Proc.new do |cred|
|
220
|
+
pref = SASL::Preferences.new :digest_uri => "ldap/#{@host}", :username => bind_dn, :has_password? => true, :password => options[:password]||@password
|
221
|
+
sasl = SASL.new("DIGEST-MD5", pref)
|
222
|
+
response = sasl.receive("challenge", cred)
|
223
|
+
response[1]
|
224
|
+
end
|
225
|
+
[initial_credential, challenge_response]
|
226
|
+
end
|
227
|
+
|
228
|
+
def sasl_bind_setup_gss_spnego(bind_dn, options)
|
229
|
+
puts options.inspect
|
230
|
+
user,psw = [bind_dn, options[:password]||@password]
|
231
|
+
raise LdapError.new( "invalid binding information" ) unless (user && psw)
|
232
|
+
|
233
|
+
nego = proc {|challenge|
|
234
|
+
t2_msg = Net::NTLM::Message.parse( challenge )
|
235
|
+
user, domain = user.split('\\').reverse
|
236
|
+
t2_msg.target_name = Net::NTLM::encode_utf16le(domain) if domain
|
237
|
+
t3_msg = t2_msg.response( {:user => user, :password => psw}, {:ntlmv2 => true} )
|
238
|
+
t3_msg.serialize
|
239
|
+
}
|
240
|
+
[Net::NTLM::Message::Type1.new.serialize, nego]
|
241
|
+
end
|
242
|
+
|
243
|
+
def simple_bind(bind_dn, options={})
|
244
|
+
args = {
|
245
|
+
:method => :simple,
|
246
|
+
:username => bind_dn,
|
247
|
+
:password => (options[:password]||@password).to_s,
|
248
|
+
}
|
249
|
+
begin
|
250
|
+
raise AuthenticationError if args[:password] == ""
|
251
|
+
execute(:bind, args)
|
252
|
+
true
|
253
|
+
rescue Exception
|
254
|
+
false
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def bind_as_anonymous(options={})
|
259
|
+
execute(:bind, {:method => :anonymous})
|
260
|
+
true
|
261
|
+
end
|
262
|
+
|
263
|
+
def construct_uri(host, port, ssl)
|
264
|
+
protocol = ssl ? "ldaps" : "ldap"
|
265
|
+
URI.parse("#{protocol}://#{host}:#{port}").to_s
|
266
|
+
end
|
267
|
+
|
268
|
+
def target
|
269
|
+
return nil if @uri.nil?
|
270
|
+
if @with_start_tls
|
271
|
+
"#{@uri}(StartTLS)"
|
272
|
+
else
|
273
|
+
@uri
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|