From cd4af04646facb0601ebf5a25c8fac9756f444d5 Mon Sep 17 00:00:00 2001 From: Ping Yu Date: Sat, 2 Oct 2010 21:58:14 -0500 Subject: [PATCH] added user search and user info retrieve for LDAP strategy. updated the reamde as well. --- oa-enterprise/README.rdoc | 11 ++- oa-enterprise/lib/omniauth/strategies/ldap.rb | 18 ++-- .../lib/omniauth/strategies/ldap/adaptor.rb | 90 ++++++++++--------- 3 files changed, 67 insertions(+), 52 deletions(-) diff --git a/oa-enterprise/README.rdoc b/oa-enterprise/README.rdoc index 2d9e29c..5c41156 100644 --- a/oa-enterprise/README.rdoc +++ b/oa-enterprise/README.rdoc @@ -11,10 +11,10 @@ To get just enterprise functionality: For the full auth suite: gem install omniauth - + +CAS strategy == Stand-Alone Example -CAS strategy Use the strategy as a middleware in your application: require 'omniauth/enterprise' @@ -42,13 +42,16 @@ If CAS is one of several authentication strategies, use the OmniAuth Builder: LDAP strategy - use OmniAuth::Strategies::LDAP, :host => '10.101.10.1', :port => 389, :method => :plain, :try_sasl => true, :sasl_mechanisms => "GSS-SPNEGO" + use OmniAuth::Strategies::LDAP, :host => '10.101.10.1', :port => 389, :method => :plain, :base => 'dc=intridea, dc=com', :uid => 'sAMAccountName', :try_sasl => true, :sasl_mechanisms => "GSS-SPNEGO" or use OmniAuth::Builder do - provider :LDAP, :host => '10.101.10.1', :port => 389, :method => :plain, :try_sasl => true, :sasl_mechanisms => "GSS-SPNEGO" + provider :LDAP, :host => '10.101.10.1', :port => 389, :method => :plain, :base => 'dc=intridea, dc=com', :uid => 'sAMAccountName', :try_sasl => true, :sasl_mechanisms => "GSS-SPNEGO" end LDAP server's :host and :port are required, :method is also a required field, and allowed values are :plain, :ssl, and :tls. + :base is required, it is the distinguish name (DN) for your organization, all users should be searchable under this base. + :uid is required, it is the LDAP attribute name for the user name in the login form. typically AD would be 'sAMAccountName' or 'UniquePersonalIdentifier', while + OpenLDAP is 'uid'. You can also use 'dn', if your user choose the put in the dn in the login form (but usually is too long for user to remember or know). :try_sasl and :sasl_mechanisms are optional, use it to initial SASL connection to server. mechanism supported are DIGEST-MD5 and GSS-SPNEGO. Then simply direct users to '/auth/ldap' to have them authenticated via your company's LDAP server. diff --git a/oa-enterprise/lib/omniauth/strategies/ldap.rb b/oa-enterprise/lib/omniauth/strategies/ldap.rb index afe27ef..1b0cd81 100644 --- a/oa-enterprise/lib/omniauth/strategies/ldap.rb +++ b/oa-enterprise/lib/omniauth/strategies/ldap.rb @@ -33,14 +33,17 @@ module OmniAuth def perform begin @adaptor.bind(:bind_dn => request.POST['username'], :password => request.POST['password']) - rescue + @user_info = @adaptor.search(:filter => Net::LDAP::Filter.eq(@adaptor.uid, request.POST['username']),:limit => 1) + puts @user_info + request.POST['auth'] = auth_hash + @env['REQUEST_METHOD'] = 'GET' + @env['PATH_INFO'] = "#{OmniAuth.config.path_prefix}/#{name}/callback" + + @app.call(@env) + rescue Exception => e + puts e.message fail!(:invalid_credentials) end - request.POST['auth'] = auth_hash - @env['REQUEST_METHOD'] = 'GET' - @env['PATH_INFO'] = "#{OmniAuth.config.path_prefix}/#{name}/callback" - - @app.call(@env) end def callback_phase @@ -49,7 +52,8 @@ module OmniAuth def auth_hash OmniAuth::Utils.deep_merge(super, { - 'uid' => request.POST['username'] + 'uid' => @user_info["dn"], + 'user_info' => @user_info }) end diff --git a/oa-enterprise/lib/omniauth/strategies/ldap/adaptor.rb b/oa-enterprise/lib/omniauth/strategies/ldap/adaptor.rb index 12d1068..8aa6f80 100644 --- a/oa-enterprise/lib/omniauth/strategies/ldap/adaptor.rb +++ b/oa-enterprise/lib/omniauth/strategies/ldap/adaptor.rb @@ -1,3 +1,4 @@ +#this code boughts pieces from activeldap and net-ldap require 'rack' require 'net/ldap' require 'net/ntlm' @@ -11,15 +12,15 @@ module OmniAuth class AuthenticationError < StandardError; end class ConnectionError < StandardError; end VALID_ADAPTER_CONFIGURATION_KEYS = [:host, :port, :method, :bind_dn, :password, - :try_sasl, :sasl_mechanisms, :sasl_quiet] - MUST_HAVE_KEYS = [:host, :port, :method] + :try_sasl, :sasl_mechanisms, :uid, :base] + MUST_HAVE_KEYS = [:host, :port, :method, :uid, :base] METHOD = { :ssl => :simple_tls, :tls => :start_tls, :plain => nil } - attr_reader :bind_dn - + attr_accessor :bind_dn, :password + attr_reader :connection, :uid, :base def initialize(configuration={}) @connection = nil @disconnected = false @@ -75,7 +76,6 @@ module OmniAuth # Rough bind loop: # Attempt 1: SASL if available # Attempt 2: SIMPLE with credentials if password block - # Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail (or pwblock returns '') if try_sasl and sasl_bind(bind_dn, options) puts "bind with sasl" elsif simple_bind(bind_dn, options) @@ -110,8 +110,27 @@ module OmniAuth def bound? connecting? and @bound end + + def search(options={}, &block) + base = options[:base] + filter = options[:filter] + limit = options[:limit] - + args = { + :base => @base, + :filter => filter, + :size => limit + } + puts args.inspect + attributes = {} + execute(:search, args) do |entry| + puts entry.inspect + entry.attribute_names.each do |name| + attributes[name] = entry[name] + end + end + attributes + end private def execute(method, *args, &block) result = @connection.send(method, *args, &block) @@ -137,11 +156,6 @@ module OmniAuth def prepare_connection(options) end - - def need_credential_sasl_mechanism?(mechanism) - not %(GSSAPI EXTERNAL ANONYMOUS).include?(mechanism) - end - def ensure_method(method) method ||= "plain" normalized_method = method.to_s.downcase.to_sym @@ -153,36 +167,30 @@ module OmniAuth end def sasl_bind(bind_dn, options={}) - if options.has_key?(:sasl_quiet) - sasl_quiet = options[:sasl_quiet] - else - sasl_quiet = @sasl_quiet - end - sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms - begin sasl_mechanisms.each do |mechanism| - normalized_mechanism = mechanism.downcase.gsub(/-/, '_') - sasl_bind_setup = "sasl_bind_setup_#{normalized_mechanism}" - next unless respond_to?(sasl_bind_setup, true) - initial_credential, challenge_response = - send(sasl_bind_setup, bind_dn, options) - args = { - :method => :sasl, - :initial_credential => initial_credential, - :mechanism => mechanism, - :challenge_response => challenge_response, - } - info = { - :name => "bind: SASL", :dn => bind_dn, :mechanism => mechanism, - } - puts info.inspect - return true if execute(:bind, args) + begin + normalized_mechanism = mechanism.downcase.gsub(/-/, '_') + sasl_bind_setup = "sasl_bind_setup_#{normalized_mechanism}" + next unless respond_to?(sasl_bind_setup, true) + initial_credential, challenge_response = + send(sasl_bind_setup, bind_dn, options) + args = { + :method => :sasl, + :initial_credential => initial_credential, + :mechanism => mechanism, + :challenge_response => challenge_response, + } + info = { + :name => "bind: SASL", :dn => bind_dn, :mechanism => mechanism, + } + puts info.inspect + execute(:bind, args) + return true + rescue Exception => e + puts e.message + end end - rescue Exception => e - puts e.message - false - end false end @@ -228,7 +236,7 @@ module OmniAuth :maxbuf => "65536", "digest-uri" => uri.inspect, } - a1 = "#{bind_dn}:#{realm}:#{@password}" + a1 = "#{bind_dn}:#{realm}:#{options[:password]||@password}" a1 = "#{Digest::MD5.digest(a1)}:#{nonce}:#{cnonce}" ha1 = Digest::MD5.hexdigest(a1) a2 = "AUTHENTICATE:#{uri}" @@ -244,7 +252,7 @@ module OmniAuth end def sasl_bind_setup_gss_spnego(bind_dn, options) puts options.inspect - user,psw = [bind_dn, @password] + user,psw = [bind_dn, options[:password]||@password] raise LdapError.new( "invalid binding information" ) unless (user && psw) nego = proc {|challenge| @@ -261,7 +269,7 @@ module OmniAuth args = { :method => :simple, :username => bind_dn, - :password => @password, + :password => options[:password]||@password, } execute(:bind, args) true