diff --git a/app/models/auth_source_ldap.rb b/app/models/auth_source_ldap.rb index d009ae33..3536b4f6 100644 --- a/app/models/auth_source_ldap.rb +++ b/app/models/auth_source_ldap.rb @@ -21,6 +21,7 @@ class AuthSourceLdap < AuthSource validates_length_of :account, :account_password, :base_dn, :maximum => 255, :allow_nil => true validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true validates_numericality_of :port, :only_integer => true + validate :custom_filter_should_be_valid_ldap_filter_syntax before_validation :strip_ldap_attributes @@ -101,10 +102,17 @@ class AuthSourceLdap < AuthSource ldap_con = initialize_ldap_con(self.account, self.account_password) login_filter = Net::LDAP::Filter.eq( self.attr_login, login ) object_filter = Net::LDAP::Filter.eq( "objectClass", "*" ) - attrs = {} + custom_ldap_filter = custom_filter_to_ldap - ldap_con.search( :base => self.base_dn, - :filter => object_filter & login_filter, + if custom_ldap_filter.present? + search_filters = object_filter & login_filter & custom_ldap_filter + else + search_filters = object_filter & login_filter + end + attrs = {} + + ldap_con.search( :base => self.base_dn, + :filter => search_filters, :attributes=> search_attributes) do |entry| if onthefly_register? @@ -119,6 +127,27 @@ class AuthSourceLdap < AuthSource attrs end + def custom_filter_to_ldap + return nil unless custom_filter.present? + + begin + return Net::LDAP::Filter.construct(custom_filter) + rescue Net::LDAP::LdapError # Filter syntax error + logger.debug "LDAP custom filter syntax error for: #{custom_filter}" if logger && logger.debug? + return nil + end + end + + def custom_filter_should_be_valid_ldap_filter_syntax + return true unless custom_filter.present? + + begin + return Net::LDAP::Filter.construct(custom_filter) + rescue Net::LDAP::LdapError # Filter syntax error + errors.add(:custom_filter, :invalid) + end + end + def self.get_attr(entry, attr_name) if !attr_name.blank? entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name] diff --git a/app/views/ldap_auth_sources/_form.rhtml b/app/views/ldap_auth_sources/_form.rhtml index 9ffffafc..8699a2cd 100644 --- a/app/views/ldap_auth_sources/_form.rhtml +++ b/app/views/ldap_auth_sources/_form.rhtml @@ -25,6 +25,9 @@

<%= check_box 'auth_source', 'onthefly_register' %>

+ +

+<%= text_field 'auth_source', 'custom_filter', :size => 60 %>

<%=l(:label_attribute_plural)%> diff --git a/config/locales/en.yml b/config/locales/en.yml index 8560fc8f..afce4f4c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -307,6 +307,7 @@ en: field_text: Text field field_visible: Visible field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text" + field_custom_filter: Custom LDAP filter setting_app_title: Application title setting_app_subtitle: Application subtitle diff --git a/db/migrate/20100217010520_add_custom_filter_to_auth_sources.rb b/db/migrate/20100217010520_add_custom_filter_to_auth_sources.rb new file mode 100644 index 00000000..8543c297 --- /dev/null +++ b/db/migrate/20100217010520_add_custom_filter_to_auth_sources.rb @@ -0,0 +1,9 @@ +class AddCustomFilterToAuthSources < ActiveRecord::Migration + def self.up + add_column :auth_sources, :custom_filter, :string + end + + def self.down + remove_column :auth_sources, :custom_filter + end +end diff --git a/test/fixtures/ldap/backend.redmine.org.ldif b/test/fixtures/ldap/backend.redmine.org.ldif new file mode 100644 index 00000000..b08dddbc --- /dev/null +++ b/test/fixtures/ldap/backend.redmine.org.ldif @@ -0,0 +1,27 @@ +# This is a configuration for a new database to test the ldap plugin. +# It is assumed that you have a ldap server up and running. Do not +# use this file on a new openldap installation. + +# Database settings +dn: olcDatabase=hdb,cn=config +objectClass: olcDatabaseConfig +objectClass: olcHdbConfig +olcDatabase: hdb +olcSuffix: dc=redmine,dc=org +# WARNING: Do not use a directory that already contains a ldap database. +# Each database has to be stored in a seperate directory. The directory +# /var/lib/ldap is the default directory on Debian. +olcDbDirectory: /var/lib/ldap/redmine +olcRootDN: cn=Manager,dc=redmine,dc=org +olcRootPW: secret +olcDbConfig: set_cachesize 0 2097152 0 +olcDbConfig: set_lk_max_objects 1500 +olcDbConfig: set_lk_max_locks 1500 +olcDbConfig: set_lk_max_lockers 1500 +olcDbIndex: objectClass eq +olcLastMod: TRUE +olcDbCheckpoint: 512 30 +olcAccess: to attrs=userPassword by dn="cn=Manager,dc=redmine,dc=org" write by anonymous auth by self write by * none +olcAccess: to attrs=shadowLastChange by self write by * read +olcAccess: to dn.base="" by * read +olcAccess: to * by dn="cn=Manager,dc=redmine,dc=org" write by * read diff --git a/test/fixtures/ldap/test-ldap.ldif b/test/fixtures/ldap/test-ldap.ldif index 7d9e109c..8a4ce5aa 100644 --- a/test/fixtures/ldap/test-ldap.ldif +++ b/test/fixtures/ldap/test-ldap.ldif @@ -4,39 +4,11 @@ objectClass: dcObject objectClass: organization o: redmine.org dc: redmine -structuralObjectClass: organization -entryUUID: 886f5fca-0a87-102e-8d06-67c361d9bd2d -creatorsName: -createTimestamp: 20090721211642Z -entryCSN: 20090721211642.955188Z#000000#000#000000 -modifiersName: -modifyTimestamp: 20090721211642Z - -dn: cn=admin,dc=redmine,dc=org -objectClass: simpleSecurityObject -objectClass: organizationalRole -cn: admin -description: LDAP administrator -userPassword:: e2NyeXB0fWlWTU9DcUt6WWxXRDI= -structuralObjectClass: organizationalRole -entryUUID: 88704e44-0a87-102e-8d07-67c361d9bd2d -creatorsName: -createTimestamp: 20090721211642Z -entryCSN: 20090721211642.961418Z#000000#000#000000 -modifiersName: -modifyTimestamp: 20090721211642Z dn: ou=Person,dc=redmine,dc=org ou: Person objectClass: top objectClass: organizationalUnit -structuralObjectClass: organizationalUnit -entryUUID: d39dd388-0c84-102e-82fa-dff86c63a7d6 -creatorsName: cn=admin,dc=redmine,dc=org -createTimestamp: 20090724100222Z -entryCSN: 20090724100222.924226Z#000000#000#000000 -modifiersName: cn=admin,dc=redmine,dc=org -modifyTimestamp: 20090724100222Z dn: uid=example1,ou=Person,dc=redmine,dc=org objectClass: posixAccount @@ -48,16 +20,9 @@ sn: One uid: example1 homeDirectory: /home/example1 cn: Example One -structuralObjectClass: inetOrgPerson -entryUUID: 285d304e-0c8a-102e-82fc-dff86c63a7d6 -creatorsName: cn=admin,dc=redmine,dc=org -createTimestamp: 20090724104032Z uidNumber: 0 mail: example1@redmine.org userPassword:: e1NIQX1mRXFOQ2NvM1lxOWg1WlVnbEQzQ1pKVDRsQnM9 -entryCSN: 20090724105945.375801Z#000000#000#000000 -modifiersName: cn=admin,dc=redmine,dc=org -modifyTimestamp: 20090724105945Z dn: uid=edavis,ou=Person,dc=redmine,dc=org objectClass: posixAccount @@ -68,15 +33,8 @@ givenName: Eric sn: Davis uid: edavis mail: edavis@littlestreamsoftware.com -structuralObjectClass: inetOrgPerson -entryUUID: 9c5f0502-0c8b-102e-82fe-dff86c63a7d6 -creatorsName: cn=admin,dc=redmine,dc=org -createTimestamp: 20090724105056Z homeDirectory: /home/edavis cn: Eric Davis uidNumber: 0 userPassword:: e1NIQX1mRXFOQ2NvM1lxOWg1WlVnbEQzQ1pKVDRsQnM9 -entryCSN: 20090724105937.734480Z#000000#000#000000 -modifiersName: cn=admin,dc=redmine,dc=org -modifyTimestamp: 20090724105937Z diff --git a/test/unit/auth_source_ldap_test.rb b/test/unit/auth_source_ldap_test.rb index b383b906..0effa103 100644 --- a/test/unit/auth_source_ldap_test.rb +++ b/test/unit/auth_source_ldap_test.rb @@ -31,6 +31,20 @@ class AuthSourceLdapTest < ActiveSupport::TestCase assert_equal 'givenName', a.reload.attr_firstname end + context "validations" do + should "validate that custom_filter is a valid LDAP filter" do + @auth = AuthSourceLdap.new(:name => 'Validation', :host => 'localhost', :port => 389, :attr_login => 'login') + @auth.custom_filter = "(& (homeDirectory=*) (sn=O*" # Missing (( + assert @auth.invalid? + assert_equal "is invalid", @auth.errors.on(:custom_filter) + + @auth.custom_filter = "(& (homeDirectory=*) (sn=O*))" + assert @auth.valid? + assert_equal nil, @auth.errors.on(:custom_filter) + + end + end + if ldap_configured? context '#authenticate' do setup do @@ -69,6 +83,32 @@ class AuthSourceLdapTest < ActiveSupport::TestCase end end + context "using a valid custom filter" do + setup do + @auth.update_attributes(:custom_filter => "(& (homeDirectory=*) (sn=O*))") + end + + should "find a user who authenticates and matches the custom filter" do + assert_not_nil @auth.authenticate('example1', '123456') + end + + should "be nil for users who don't match the custom filter" do + assert_nil @auth.authenticate('edavis', '123456') + end + end + + context "using an invalid custom filter" do + setup do + # missing )) at the end + @auth.update_attributes(:custom_filter => "(& (homeDirectory=*) (sn=O*") + end + + should "skip the custom filter" do + assert_not_nil @auth.authenticate('example1', '123456') + assert_not_nil @auth.authenticate('edavis', '123456') + end + end + end else puts '(Test LDAP server not configured)'