Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enable ldap filters for user logins #3

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Expand Up @@ -11,6 +11,8 @@ Use the LDAP strategy as a middleware in your application:
:method => :plain,
:base => 'dc=intridea, dc=com',
:uid => 'sAMAccountName',
# Or, alternatively:
#:filter => '(&(uid=%{username})(memberOf=cn=myapp-users,ou=groups,dc=example,dc=com))'
:name_proc => Proc.new {|name| name.gsub(/@.*$/,'')}
:bind_dn => 'default_bind_dn'
:password => 'password'
Expand All @@ -29,6 +31,9 @@ Allowed values of :method are: :plain, :ssl, :tls.
:uid is the LDAP attribute name for the user name in the login form.
typically AD would be 'sAMAccountName' or 'UserPrincipalName', while OpenLDAP is 'uid'.

:filter is the LDAP filter used to search the user entry. It can be used in place of :uid for more flexibility.
`%{username}` will be replaced by the user name processed by :name_proc.

:name_proc allows you to match the user name entered with the format of the :uid attributes.
For example, value of 'sAMAccountName' in AD contains only the windows user name. If your user prefers using
email to login, a name_proc as above will trim the email string down to just the windows login name.
Expand Down
15 changes: 10 additions & 5 deletions lib/omniauth-ldap/adaptor.rb
Expand Up @@ -14,9 +14,10 @@ class ConfigurationError < StandardError; end
class AuthenticationError < StandardError; end
class ConnectionError < StandardError; end

VALID_ADAPTER_CONFIGURATION_KEYS = [:host, :port, :method, :bind_dn, :password, :try_sasl, :sasl_mechanisms, :uid, :base, :allow_anonymous]
VALID_ADAPTER_CONFIGURATION_KEYS = [:host, :port, :method, :bind_dn, :password, :try_sasl, :sasl_mechanisms, :uid, :base, :allow_anonymous, :filter]

MUST_HAVE_KEYS = [:host, :port, :method, :uid, :base]
# A list of needed keys. Possible alternatives are specified using sub-lists.
MUST_HAVE_KEYS = [:host, :port, :method, [:uid, :filter], :base]

METHOD = {
:ssl => :simple_tls,
Expand All @@ -25,11 +26,15 @@ class ConnectionError < StandardError; end
}

attr_accessor :bind_dn, :password
attr_reader :connection, :uid, :base, :auth
attr_reader :connection, :uid, :base, :auth, :filter
def self.validate(configuration={})
message = []
MUST_HAVE_KEYS.each do |name|
message << name if configuration[name].nil?
MUST_HAVE_KEYS.each do |names|
names = [names].flatten
missing_keys = names.select{|name| configuration[name].nil?}
if missing_keys == names
message << names.join(' or ')
end
end
raise ArgumentError.new(message.join(",") +" MUST be provided") unless message.empty?
end
Expand Down
10 changes: 9 additions & 1 deletion lib/omniauth/strategies/ldap.rb
Expand Up @@ -45,7 +45,7 @@ def callback_phase
raise MissingCredentialsError.new("Missing login credentials")
end

@ldap_user_info = @adaptor.bind_as(:filter => Net::LDAP::Filter.eq(@adaptor.uid, @options[:name_proc].call(request['username'])),:size => 1, :password => request['password'])
@ldap_user_info = @adaptor.bind_as(:filter => filter(@adaptor), :size => 1, :password => request['password'])
return fail!(:invalid_credentials) if !@ldap_user_info

@user_info = self.class.map_user(@@config, @ldap_user_info)
Expand All @@ -55,6 +55,14 @@ def callback_phase
end
end

def filter adaptor
if adaptor.filter and !adaptor.filter.empty?
Net::LDAP::Filter.construct(adaptor.filter % {username: @options[:name_proc].call(request['username'])})
else
Net::LDAP::Filter.eq(adaptor.uid, @options[:name_proc].call(request['username']))
end
end

uid {
@user_info["uid"]
}
Expand Down
112 changes: 56 additions & 56 deletions spec/omniauth/strategies/ldap_spec.rb
Expand Up @@ -50,62 +50,62 @@ def session
end

describe 'post /auth/ldap/callback' do
before(:each) do
@adaptor = mock(OmniAuth::LDAP::Adaptor, {:uid => 'ping'})
OmniAuth::LDAP::Adaptor.stub(:new).and_return(@adaptor)
end

context 'failure' do
before(:each) do
@adaptor.stub(:bind_as).and_return(false)
end

it 'should raise MissingCredentialsError' do
post('/auth/ldap/callback', {})
last_response.should be_redirect
last_response.headers['Location'].should =~ %r{ldap_error}
end

it 'should redirect to error page' do
post('/auth/ldap/callback', {:username => 'ping', :password => 'password'})
last_response.should be_redirect
last_response.headers['Location'].should =~ %r{invalid_credentials}
end

it 'should redirect to error page when there is exception' do
@adaptor.stub(:bind_as).and_throw(Exception.new('connection_error'))
post('/auth/ldap/callback', {:username => 'ping', :password => 'password'})
last_response.should be_redirect
last_response.headers['Location'].should =~ %r{ldap_error}
end
end

context 'success' do
let(:auth_hash){ last_request.env['omniauth.auth'] }
before(:each) do
@adaptor.stub(:bind_as).and_return({:dn => ['cn=ping, dc=intridea, dc=com'], :mail => ['ping@intridea.com'], :givenname => ['Ping'], :sn => ['Yu'],
:telephonenumber => ['555-555-5555'], :mobile => ['444-444-4444'], :uid => ['ping'], :title => ['dev'], :address =>[ 'k street'],
:l => ['Washington'], :st => ['DC'], :co => ["U.S.A"], :postofficebox => ['20001'], :wwwhomepage => ['www.intridea.com'],
:jpegphoto => ['http://www.intridea.com/ping.jpg'], :description => ['omniauth-ldap']})
post('/auth/ldap/callback', {:username => 'ping', :password => 'password'})
end

it 'should raise MissingCredentialsError' do
should_not raise_error OmniAuth::Strategies::LDAP::MissingCredentialsError
end
it 'should map user info' do
auth_hash.uid.should == 'cn=ping, dc=intridea, dc=com'
auth_hash.info.email.should == 'ping@intridea.com'
auth_hash.info.first_name.should == 'Ping'
auth_hash.info.last_name.should == 'Yu'
auth_hash.info.phone.should == '555-555-5555'
auth_hash.info.mobile.should == '444-444-4444'
auth_hash.info.nickname.should == 'ping'
auth_hash.info.title.should == 'dev'
auth_hash.info.location.should == 'k street, Washington, DC, U.S.A 20001'
auth_hash.info.url.should == 'www.intridea.com'
auth_hash.info.image.should == 'http://www.intridea.com/ping.jpg'
auth_hash.info.description.should == 'omniauth-ldap'
{:filter => '(ping=%{username})', :uid => 'ping'}.each_pair do |key, value|
context "when using :#{key}" do
before(:each) do
mocked_methods = {:filter => nil, :uid => nil}
mocked_methods[key] = value
@adaptor = mock(OmniAuth::LDAP::Adaptor, mocked_methods)
OmniAuth::LDAP::Adaptor.stub(:new).and_return(@adaptor)
end
context 'failure' do
before(:each) do
@adaptor.stub(:bind_as).and_return(false)
end
it 'should raise MissingCredentialsError' do
lambda{post('/auth/ldap/callback', {})}.should raise_error OmniAuth::Strategies::LDAP::MissingCredentialsError
end
it 'should redirect to error page' do
post('/auth/ldap/callback', {:username => 'ping', :password => 'password'})
last_response.should be_redirect
last_response.headers['Location'].should =~ %r{invalid_credentials}
end
it 'should redirect to error page when there is exception' do
@adaptor.stub(:bind_as).and_throw(Exception.new('connection_error'))
post('/auth/ldap/callback', {:username => 'ping', :password => 'password'})
last_response.should be_redirect
last_response.headers['Location'].should =~ %r{ldap_error}
end
end
context 'success' do
let(:auth_hash){ last_request.env['omniauth.auth'] }
before(:each) do
@adaptor.stub(:bind_as).and_return({:dn => ['cn=ping, dc=intridea, dc=com'], :mail => ['ping@intridea.com'], :givenname => ['Ping'], :sn => ['Yu'],
:telephonenumber => ['555-555-5555'], :mobile => ['444-444-4444'], :uid => ['ping'], :title => ['dev'], :address =>[ 'k street'],
:l => ['Washington'], :st => ['DC'], :co => ["U.S.A"], :postofficebox => ['20001'], :wwwhomepage => ['www.intridea.com'],
:jpegphoto => ['http://www.intridea.com/ping.jpg'], :description => ['omniauth-ldap']})
post('/auth/ldap/callback', {:username => 'ping', :password => 'password'})
end
it 'should raise MissingCredentialsError' do
should_not raise_error OmniAuth::Strategies::LDAP::MissingCredentialsError
end
it 'should map user info' do
auth_hash.uid.should == 'cn=ping, dc=intridea, dc=com'
auth_hash.info.email.should == 'ping@intridea.com'
auth_hash.info.first_name.should == 'Ping'
auth_hash.info.last_name.should == 'Yu'
auth_hash.info.phone.should == '555-555-5555'
auth_hash.info.mobile.should == '444-444-4444'
auth_hash.info.nickname.should == 'ping'
auth_hash.info.title.should == 'dev'
auth_hash.info.location.should == 'k street, Washington, DC, U.S.A 20001'
auth_hash.info.url.should == 'www.intridea.com'
auth_hash.info.image.should == 'http://www.intridea.com/ping.jpg'
auth_hash.info.description.should == 'omniauth-ldap'
end
end
end
end
end
Expand Down