Peter,

I've added schema awareness. You can now do $filter->match($entry,$schema) and it will filter based on schema, however currently I only included caseIgnore matching.
I couldn't get the function prototyping to work with the substrings match in combination with schema lookup while it works with $schema=undef  (I'm running Active State 5.8.7).

At the end is a small extension to Net::LDAP::Schema, which probably does not belong here ;-)

Feedback is welcome,

Hans


Peter Marschall schreef:
Hi Hans,

On Sunday, 18. December 2005 21:53, [EMAIL PROTECTED] wrote:
  
attached is Net::LDAP::FilterMatch, a module to match Net::LDAP::Entry
objects against a Net::LDAP::Filter filter.
It can be used to filter out entries from an LDIF, but also as a simple
solution for searching while using Net::LDAP::Server.
    

I made a few changes to the file you sent.

Fixes compared to your version:
- conjunctions (&...) and disjunctions (|...) may comprise any number of
  elements.
- characters with special meaning in Perl regexes are quoted in equality
  and substring filters
  i.e. "(cn=Peter?)" matches "Peter?"  but not "Pete" or "Peter".
- Net::LDAP::FilterMatch is now an extension of Net::LDAP::Filter
  Thus loading Net::LDAP::FilterMatch after Net::LDAP::Filter
  allows you to write $filter->match($entry)
- If possible greateOrEqual and lessOrEqual filters do numerical comparison.

See the attached file.

If you do not mind I'd like to include it into the perl-ldap SVN so that it 
will be in the next release.

Please test if it still matches your idea how this module should behave.

Regards
Peter

  
# ===========================================================================
# Net::LDAP::FilterMatch
# 
# LDAP entry matching
# 
# Hans Klunder <[EMAIL PROTECTED]>
# Peter Marschall <[EMAIL PROTECTED]>
#  Copyright (c) 2005.
# 
# See below for documentation.
# 

package Net::LDAP::Filter;

require Exporter;
[EMAIL PROTECTED]       = qw(Net::LDAP::Filter Exporter);
@ISA       = qw(Exporter);
@EXPORT_OK = qw(filterMatch);
$VERSION   = '0.12';

sub filterMatch($@);
sub _cis_equalityMatch($@);
sub _cis_greaterOrEqual($@);
sub _cis_lessOrEqual($@);
sub _cis_approxMatch($@);
sub _cis_substrings;

sub _caseIgnoreMatch { return _cis_equalityMatch(@_)};
sub _caseIgnoreSubstringsMatch { return _cis_substrings(@_) };

sub match
{
  my $self = shift;
  my $entry = shift;
  my $schema =shift;

  return filterMatch($self, $entry, $schema);
}
# map Ops to schema matches 
my %op2schema = qw(
        equalityMatch  equality
        greaterOrEqual equality
        lessOrEqual        ordering
        approxMatch        ordering
        substrings         substr
);

sub filterMatch($@)
{
  my $filter = shift;
  my $entry = shift;
  my $schema = shift;


  keys(%{$filter}); # re-initialize each() operator
  my ($op, $args) = each(%{$filter});
  #print Dumper($args);

  # handle combined filters
  if ($op eq 'and') {   # '(&()...)' => fail on 1st mismatch
    foreach my $subfilter (@{$args}) {
      return 0  if (!filterMatch($subfilter, $entry));
    }  
    return 1;   # all matched or '(&)' => succeed
  }  
  if ($op eq 'or') {    # '(|()...)' => succeed on 1st match
    foreach my $subfilter (@{$args}) {
      return 1  if (filterMatch($subfilter, $entry));
    }  
    return 0;   # none matched or '(|)' => fail
  }  
  if ($op eq 'not') {
    return (! filterMatch($args, $entry));
  }  
  if ($op eq 'present') {
    #return 1  if (lc($args) eq 'objectclass'); # "all match" filter
    return ($entry->exists($args));
  }  

  # handle basic filters
  if ($op =~ 
/^(equalityMatch|greaterOrEqual|lessOrEqual|approxMatch|substrings)/o) {
    my $attr=($op eq 'substrings') ? $args->{'type'} : $args->{'attributeDesc'} 
;
    #print $op,'##', $op2schema{$op},'##',$attr,"##";
    my @values = $entry->get_value($attr);
    my $match;
    # approx match is not standardized in schema
    if ($schema and ($op ne 'approxMatch') ){
             # get matchingrule from schema, be sure that matching subs exist 
for every MR in your schema
              $match='_' . $schema->matchingruleforattribute( $attr, 
$op2schema{$op}) or return undef;
    }
    else{
       # fall back on build-in logic
       $match='_cis_' . $op;
    }
    #print $match,"## ";
    return &$match($args, @values);
  }
  
  return undef; # all other filters => fail with error
}

sub _cis_equalityMatch($@)
{
my $args=shift;
my $assertion = $args->{'assertionValue'};

  return grep(/^\Q$assertion\E$/i, @_) ? 1 : 0;
}


sub _cis_greaterOrEqual($@)
{
my $args=shift;
my $assertion = $args->{'assertionValue'};

  if (grep(!/^-?\d+$/o, $assertion, @_)) {      # numerical values only => 
compare numerically
    return (grep { $_ ge $assertion } @_) ? 1 : 0;
  }
  else {
    return (grep { lc($_) >= lc($assertion) } @_) ? 1 : 0;
  }  
}


sub _cis_lessOrEqual($@)
{
my $args=shift;
my $assertion = $args->{'assertionValue'};

  if (grep(!/^-?\d+$/o, $assertion, @_)) {      # numerical values only => 
compare numerically
    return (grep { $_ le $assertion } @_) ? 1 : 0;
  }
  else {
    return (grep { lc($_) <= lc($assertion) } @_) ? 1 : 0;
  }  
}


sub _cis_approxMatch($@)
{
my $args=shift;
my $assertion = $args->{'assertionValue'};

  # kludge: treat assertion as regex
  $assertion =~ s/\./\\./go;
  $assertion =~ s/\*/.*/go;
  #print "$assertion\n";  

  return grep(/^$assertion$/i, @_) ? 1 : 0;
  # better: by use String::Approx or similar
}


sub _cis_substrings
{
  my $args=shift;
  my $regex = join('.*', map { "\Q$_\E" } map { values %$_ } 
@{$args->{'substrings'}});

  $regex =  '^'.$regex  if (exists $args->{'substrings'}[0]{initial});
  $regex .= '$'         if (exists $args->{'substrings'}[-1]{final});
      
  #print "RegEx: ".$regex."\n";

  return grep(/$regex/i, @_) ? 1 : 0;
}

package Net::LDAP::Schema;

# given the attribute and a matchtype (e.g. equality) this will return the name 
of the rule to apply

sub matchingruleforattribute($@)
{
  my $schema = shift;
  my $attr = shift;
  my $matchtype = shift;
  
  my $attrtype = $schema->attribute( $attr );
  if (exists $attrtype->{$matchtype}){
    return $attrtype->{$matchtype} ;
  }
  elsif (exists $attrtype->{'sup'}){
    # the assumption is that all superiors result in the same ruleset
    return matchingruleforattribute( $schema, $attrtype->{'sup'}[0], 
$matchtype);
  }
  return undef;
}


1;
  
  
__END__

=head1 NAME

Net::LDAP::FilterMatch - LDAP entry matching

=head1 SYNOPSIS

  use Net::LDAP::Entry;
  use Net::LDAP::Filter;
  use Net::LDAP::FilterMatch;

  my $entry = new Net::LDAP::Entry;
  $entry->dn("cn=dummy entry");
  $entry->add (
   'cn' => 'dummy entry',
   'street' => [ '1 some road','nowhere' ] );
   
  my @filters = (qw/(cn=dummy*)
                 (ou=*)
                 (&(cn=dummy*)(street=*road))
                 (&(cn=dummy*)(!(street=nowhere)))/);


  for (@filters) {
    my $filter = Net::LDAP::Filter->new($_);
    print $_,' : ', $filter->match($entry) ? 'match' : 'no match' ,"\n";
  }

=head1 ABSTRACT

This extension of the class Net::LDAP::Filter provides entry matching
functionality on the Perl side.

Given an entry it will tell whether the entry matches the filter object.

It can be used on its own or as part of a Net::LDAP::Server based LDAP server.

=head1 METHOD

=over 4

=item match ( ENTRY [ ,SCHEMA ] )

Return whether ENTRY matches the filter object. If a schema object is provided, 
the selection of matching algorithms will be derived from schema.

In case of error undef is returned.

=back


=head1 SEE ALSO

L<Net::LDAP::Filter> 

=head1 COPYRIGHT

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=head1 AUTHORS

Hans Klunder E<lt>[EMAIL PROTECTED]<gt>
Peter Marschall E<lt>[EMAIL PROTECTED]<gt>

=cut

# EOF
  

Reply via email to