From b70362045e3a4745a21d8b0fe4ba0310010bb263 Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Fri, 27 Jul 2012 12:03:43 -0700 Subject: [PATCH 1/6] Translate scopes of 0, 1, and 2 into base, one, and sub, as expected Scope is enumerated as 0, 1, or 2 in the request data, but the auto_schema search method expected words. Additionally, when writing tests I discovered that a scope of 'one' doesn't work when the DN has spaces in it due to an incomplete regex pattern. Fix to follow. --- lib/Net/LDAP/Server/Test.pm | 4 ++- t/05-scope.t | 53 +++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 t/05-scope.t diff --git a/lib/Net/LDAP/Server/Test.pm b/lib/Net/LDAP/Server/Test.pm index 4f0317c..f9cd390 100644 --- a/lib/Net/LDAP/Server/Test.pm +++ b/lib/Net/LDAP/Server/Test.pm @@ -74,6 +74,7 @@ Only one user-level method is implemented: new(). our %Data; # package data lasts as long as $$ does. our $Cookies = 0; our %Searches; + my @Scopes = qw(base one sub); # constructor sub new { @@ -128,7 +129,8 @@ Only one user-level method is implemented: new(). my @results; my $base = $reqData->{baseObject}; - my $scope = $reqData->{scope} || 'sub'; + # $reqData->{scope} is a enum but we want a word + my $scope = $Scopes[defined $reqData->{scope} ? $reqData->{scope} : 0]; my @attrs = @{$reqData->{attributes} || []}; my @filters = (); diff --git a/t/05-scope.t b/t/05-scope.t new file mode 100644 index 0000000..2195290 --- /dev/null +++ b/t/05-scope.t @@ -0,0 +1,53 @@ +use strict; +use warnings; + +use Test::More; + +use Net::LDAP; +use Net::LDAP::Server::Test; + +my $port = 1024 + int rand(10000) + $$ % 1024; + +ok( my $server = Net::LDAP::Server::Test->new( $port, auto_schema => 1 ), "spawn new server" ); +ok( my $ldap = Net::LDAP->new("localhost:$port"), "new LDAP connection" ); +ok( my $rc = $ldap->bind(), "LDAP bind()" ); + +my @scopes = qw(base one sub); + +# Add our nested DNs +my $dn = my $base = "dc=example,dc=com"; +for my $level (@scopes) { + $dn = "cn=$level group,$dn"; + $ldap->add( + $dn, + attr => [ + cn => "$level group", + objectClass => 'Group', + ], + ); +} + +# Do scopes work? +my %expected = ( + 'base' => 1, + 'one' => 2, + 'sub' => 3, +); + +for my $scope (@scopes) { + my $count = $expected{$scope}; + my $msg = $ldap->search( + base => "cn=base group,$base", + scope => $scope, + filter => '(objectClass=group)', + ); + ok $msg, "searched with scope $scope"; + TODO: { + local $TODO = "scope of 'one' doesn't work with spaces in the DN (yet)" + if $scope eq 'one'; + is $msg->count, $count, "found $count"; + } +} + +ok $ldap->unbind, "unbound"; +done_testing; From c1440a7c331ceb2cfc0069010153c7a29c282e25 Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Fri, 27 Jul 2012 12:07:35 -0700 Subject: [PATCH 2/6] More robust depth checking for scopes of 'one' in auto_schema search DNs may contain spaces which the depth checking regex didn't expect. Use a Net::LDAP utility method to count the depth level properly and compare against the base's depth. t/05-scope.t now passes the previously TODOed tests. --- lib/Net/LDAP/Server/Test.pm | 8 +++++++- t/05-scope.t | 6 +----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/Net/LDAP/Server/Test.pm b/lib/Net/LDAP/Server/Test.pm index f9cd390..320038b 100644 --- a/lib/Net/LDAP/Server/Test.pm +++ b/lib/Net/LDAP/Server/Test.pm @@ -55,6 +55,7 @@ Only one user-level method is implemented: new(). LDAP_OPERATIONS_ERROR LDAP_UNWILLING_TO_PERFORM ); + use Net::LDAP::Util qw(ldap_explode_dn); use Net::LDAP::Entry; use Net::LDAP::Filter; use Net::LDAP::FilterMatch; @@ -199,7 +200,12 @@ Only one user-level method is implemented: new(). next unless $dn eq $base; } elsif ( $scope eq 'one' ) { - next unless $dn =~ m/^(\w+=\w+,)?$base$/; + my $dn_depth = scalar @{ ldap_explode_dn($dn) }; + my $base_depth = scalar @{ ldap_explode_dn($base) }; + + # If we're deeper, we're guaranteed to be under $base thanks to + # the m// above + next unless $dn eq $base or $dn_depth == $base_depth + 1; } my $entry = $Data{$dn}; diff --git a/t/05-scope.t b/t/05-scope.t index 2195290..00bc98c 100644 --- a/t/05-scope.t +++ b/t/05-scope.t @@ -42,11 +42,7 @@ for my $scope (@scopes) { filter => '(objectClass=group)', ); ok $msg, "searched with scope $scope"; - TODO: { - local $TODO = "scope of 'one' doesn't work with spaces in the DN (yet)" - if $scope eq 'one'; - is $msg->count, $count, "found $count"; - } + is $msg->count, $count, "found $count"; } ok $ldap->unbind, "unbound"; From 0a744c612a1f575949358cd44ab005438b22a731 Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Fri, 27 Jul 2012 12:51:34 -0700 Subject: [PATCH 3/6] Restore the default scope of 'sub' b7036204 accidentally changed the default scope from 'sub' (2) to 'base' (0). --- lib/Net/LDAP/Server/Test.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Net/LDAP/Server/Test.pm b/lib/Net/LDAP/Server/Test.pm index 320038b..23f3495 100644 --- a/lib/Net/LDAP/Server/Test.pm +++ b/lib/Net/LDAP/Server/Test.pm @@ -131,7 +131,7 @@ Only one user-level method is implemented: new(). my @results; my $base = $reqData->{baseObject}; # $reqData->{scope} is a enum but we want a word - my $scope = $Scopes[defined $reqData->{scope} ? $reqData->{scope} : 0]; + my $scope = $Scopes[defined $reqData->{scope} ? $reqData->{scope} : 2]; my @attrs = @{$reqData->{attributes} || []}; my @filters = (); From d1bb1449815489beb7f88e424799b6de1a79aee3 Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Fri, 27 Jul 2012 13:18:56 -0700 Subject: [PATCH 4/6] Filters should still apply with a scope of 'base' From RFC 4511: 4.5.1.2. SearchRequest.scope Specifies the scope of the Search to be performed. The semantics (as described in [X.511]) of the defined values of this field are: baseObject: The scope is constrained to the entry named by baseObject. and 4.5.1.7. SearchRequest.filter A filter that defines the conditions that must be fulfilled in order for the Search to match a given entry. Nowhere does the RFC specify that the baseObject is excluded from the filters, and doing so would render the 'base' scope not nearly as useful. --- lib/Net/LDAP/Server/Test.pm | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/Net/LDAP/Server/Test.pm b/lib/Net/LDAP/Server/Test.pm index 23f3495..8b8821f 100644 --- a/lib/Net/LDAP/Server/Test.pm +++ b/lib/Net/LDAP/Server/Test.pm @@ -135,13 +135,11 @@ Only one user-level method is implemented: new(). my @attrs = @{$reqData->{attributes} || []}; my @filters = (); - if ( $scope ne 'base' ) { - if ( exists $reqData->{filter} ) { + if ( exists $reqData->{filter} ) { - push( @filters, - bless( $reqData->{filter}, 'Net::LDAP::Filter' ) ); + push( @filters, + bless( $reqData->{filter}, 'Net::LDAP::Filter' ) ); - } } #warn "stored Data: " . Data::Dump::dump \%Data; @@ -223,7 +221,7 @@ Only one user-level method is implemented: new(). } #warn "matched $match"; - if ( $match == scalar(@filters) ) { # or $dn eq $base ) { + if ( $match == scalar(@filters) ) { # clone the entry so that client cannot modify %Data my $result = $entry->clone; From 5f4222556af910673a4e3f86289b7665c195c30f Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Fri, 27 Jul 2012 14:00:41 -0700 Subject: [PATCH 5/6] A scope of 'one' shouldn't include the base entry From RFC 4511 section 4.5.1.2: singleLevel: The scope is constrained to the immediate subordinates of the entry named by baseObject. --- lib/Net/LDAP/Server/Test.pm | 5 ++--- t/05-scope.t | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/Net/LDAP/Server/Test.pm b/lib/Net/LDAP/Server/Test.pm index 8b8821f..c4c5a75 100644 --- a/lib/Net/LDAP/Server/Test.pm +++ b/lib/Net/LDAP/Server/Test.pm @@ -201,9 +201,8 @@ Only one user-level method is implemented: new(). my $dn_depth = scalar @{ ldap_explode_dn($dn) }; my $base_depth = scalar @{ ldap_explode_dn($base) }; - # If we're deeper, we're guaranteed to be under $base thanks to - # the m// above - next unless $dn eq $base or $dn_depth == $base_depth + 1; + # We're guaranteed to be at or under $base thanks to the m// above + next unless $dn_depth == $base_depth + 1; } my $entry = $Data{$dn}; diff --git a/t/05-scope.t b/t/05-scope.t index 00bc98c..0e48b0b 100644 --- a/t/05-scope.t +++ b/t/05-scope.t @@ -18,24 +18,26 @@ my @scopes = qw(base one sub); my $dn = my $base = "dc=example,dc=com"; for my $level (@scopes) { $dn = "cn=$level group,$dn"; - $ldap->add( + my $result = $ldap->add( $dn, attr => [ cn => "$level group", objectClass => 'Group', ], ); + ok !$result->code, "added $dn: " . $result->error; } # Do scopes work? my %expected = ( - 'base' => 1, - 'one' => 2, - 'sub' => 3, + 'base' => [qw(base)], + 'one' => [qw(one)], + 'sub' => [qw(base one sub)], ); for my $scope (@scopes) { - my $count = $expected{$scope}; + my $cns = $expected{$scope}; + my $count = scalar @$cns; my $msg = $ldap->search( base => "cn=base group,$base", scope => $scope, @@ -43,6 +45,11 @@ for my $scope (@scopes) { ); ok $msg, "searched with scope $scope"; is $msg->count, $count, "found $count"; + + my %want = map { ("$_ group" => 1) } @$cns; + my %found = map { ($_->get_value('cn') => 1) } $msg->entries; + is((scalar grep { !$found{$_} } keys %want), 0, "found all expected CNs"); + is((scalar grep { !$want{$_} } keys %found), 0, "expected all found CNs"); } ok $ldap->unbind, "unbound"; From d559ae443a87f2b5ca1b2adf86b93d274a5fcf0a Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Fri, 27 Jul 2012 14:06:14 -0700 Subject: [PATCH 6/6] Test that filters apply to all scopes under auto_schema --- t/05-scope.t | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/t/05-scope.t b/t/05-scope.t index 0e48b0b..246e56d 100644 --- a/t/05-scope.t +++ b/t/05-scope.t @@ -50,6 +50,15 @@ for my $scope (@scopes) { my %found = map { ($_->get_value('cn') => 1) } $msg->entries; is((scalar grep { !$found{$_} } keys %want), 0, "found all expected CNs"); is((scalar grep { !$want{$_} } keys %found), 0, "expected all found CNs"); + + # test that filters apply correctly on all scopes + $msg = $ldap->search( + base => "cn=base group,$base", + scope => $scope, + filter => '(objectClass=404)', + ); + ok $msg, "searched with scope $scope with a non-matching filter"; + is $msg->count, 0, "found no entries"; } ok $ldap->unbind, "unbound";