| Net-LDAP-Class documentation | Contained in the Net-LDAP-Class distribution. |
Net::LDAP::Class - object-relational mapper for Net::LDAP
# define your class
package MyLDAPClass;
use base qw( Net::LDAP::Class );
__PACKAGE__->metadata->setup(
attributes => [qw( name address phone email )],
unique_attributes => [qw( email )],
base_dn => 'dc=mycompany,dc=com',
);
1;
# then use your class
use MyLDAPClass;
use Net::LDAP;
my $ldap = create_Net_LDAP_object_and_bind(); # you write this
# create an instance of your class
my $person = MyLDAPClass->new( ldap => $ldap, email => 'foo@bar.com' );
# load from LDAP or write if not yet existing
$person->read or $person->create;
# set the 'name' attribute
$person->name( 'Joe Foo' );
# write your changes
$person->update;
# change your mind?
$person->delete;
Net::LDAP::Class (NLC) is an object-relational mapping for LDAP.
I know, it's all wrong to confuse the ORM model with LDAP since LDAP is not relational in the same way that a RDBMS is. But the ORM APIs of projects like DBIx::Class and Rose::DB::Object are so fun and easy to use, it seemed like LDAP management should be just as fun and easy.
The architecture of this package is based on Rose::DB::Object, which the author uses to great effect for RDBMS management.
NLC uses the Rose::Object package to create methods and handle the mundane get/set features. In addition, Net::LDAP::Class::MethodMaker implements a new method type called related_objects which handles the get/set/fetch of NLC objects related to a given NLC object. Typically these are Users and Groups. A User is typically related to one or more Groups, and a Group is typically related to one or more Users. See Net::LDAP::Class::User and Net::LDAP::Class::Group for examples.
There are some methods which every NLC subclass must implement. See SUBCLASSING for details.
Override this in a subclass. Be sure to call SUPER::init in your subclass.
Returns 'Net::LDAP::Class::Metadata' by default.
Returns an instance of the metadata_class() containing all the metadata for the NLC class. May be called as a class or object method.
If you do not pass a Net::LDAP object to new(), you may instead set the ldap_uri() class method to a URI string and init_ldap() will create a Net::LDAP object and bind() it for you.
Returns a Net::LDAP object.
Sets the default debug flag to whatever the PERL_DEBUG or LDAP_DEBUG env variable is set to.
Returns undef by default.
Stringify the error message for the ldap_msg object.
Returns the first unique attribute value that is not undef. If no such value is found, returns the object.
By default all NLC-derived objects are overloaded with this method.
Returns array (or array ref if called in scalar context) of objects matching opts.
opts may include:
If not present, the ldap() method is called instead.
If not present, the base_dn() method is called instead.
Any other opts are passed directly to the Net::LDAP search() method.
Returns undef if no results matching opts are found.
Write a new object to the database. Calls the action_for_create() method -- see SUBCLASSING.
Read an object's attribute values from the database. You must have previously set at least one unique attribute in the object in order for the read() to work.
Returns the object on success, undef if the object was not found.
Write changes to the database. Calls action_for_update() -- see SUBCLASSING.
If no changes are detected, aborts and returns undef.
On successful write, returns the value of read().
Remove the object from the database. You must call read() first.
Returns the value of do_batch().
Convenience method. If read() returns undef, create() is called. Returns the object in any case.
Convenience method. If ldap_entry() is set, update() is called. Otherwise, read_or_create() is called. The NLC object is returned in any case.
Called by MethodMaker every time an attribute is set with a MethodMaker-created method.
If validate() returns true, attr_value is set. If validate() returns false, a fatal error is thrown and error() set.
This method should be overriden in your subclass to provide schema-specific validation. The default behaviour is a no-op (always returns true).
Creates (if batch() is not already set) and runs a Net::LDAP::Batch object, passing it the array_of_actions to run. Will croak on any error.
Returns the Net::LDAP::Batch object on success.
Initializes (if necessary) and adds array_of_actions to the Net::LDAP::Batch object in batch().
Will call the rollback() method on the Net::LDAP::Batch object returned by batch(). If there is not batch() set, will croak.
See SUBCLASSING.
See SUBCLASSING.
See SUBCLASSING.
Returns true (1) if any unique attribute is set with a defined value.
Returns false (0) if no unique attributes are set.
Will croak() with a helpful message if you call a method that does not exist. Mostly useful for catching cases where you forget to predefine an attribute.
Returns Data::Dump::dump output for the NLC object. Useful for debugging. See also the Net::LDAP::Entry dump() method which can be called on the ldap_entry value.
$nlc->dump; # same as Data::Dump::dump( $nlc ) $nlc->ldap_entry->dump; # see Net::LDAP::Entry dump() method
Convenience method. Returns true if the object has had any values set since the last time it was written to the server.
Get/set the Net::LDAP::Batch object for the current transaction. Typically you don't want to mess with this but documented for completeness.
Get/set the Net::LDAP::Batch object for the just-completed transaction. Typically you don't want to mess with this but documented for completeness.
Performs coderef sub reference on all records in LDAP. The coderef should expect one argument: a Net::LDAP::Class-derived object.
act_on_all() operates using Net::LDAP::Control::Paged, performing a search() using a filter based on unique_attributes() and iterating over all matches in groups of (by default) 500. You may set the pager size in opts. opts should be a hash ref. The following key/value pairs are supported:
Default: 500. Sets the Net::LDAP::Control::Paged size.
Default: unique_atttributes->[0] = '*'
Set the filter for the search.
A Net::LDAP object. Required if you call act_on_all() as a class method.
Returns the number of Net::LDAP::Class results acted upon.
Convenience method. Just returns shift->isa('Net::LDAP::Class::User').
Just like isa_user() but checks the Net::LDAP::Class::Group.
NLC is designed as a base class with basic default behaviours for most common usage. However, every subclass must implement some methods, usually because such methods are specific to the particular LDAP schema you are using with the subclass.
The following methods are required by every NLC subclass. These action_for_* methods should return either a Net::LDAP::Batch::Action-based object or an array of values that can be passed to the add_actions() method of the Net::LDAP::Batch class.
See Net::LDAP::Class::User::POSIX and Net::LDAP::Class::Group::POSIX for examples.
action_for_create
action_for_update
action_for_delete
In addition, if you use the related_objects MethodMaker feature, then your subclass must implement a fetch_method_name method for each related_objects method name. Again, see Net::LDAP::Class::User::POSIX and Net::LDAP::Class::Group::POSIX for examples.
Peter Karman, <karman at cpan.org>
Please report any bugs or feature requests to
bug-net-ldap-class at rt.cpan.org, or through the web interface at
http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Net-LDAP-Class.
I will be notified, and then you'll automatically be notified of progress on
your bug as I make changes.
You can find documentation for this module with the perldoc command.
perldoc Net::LDAP::Class
You can also look for information at:
The Minnesota Supercomputing Institute http://www.msi.umn.edu/
sponsored the development of this software.
Copyright 2008 by the Regents of the University of Minnesota. All rights reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
Net::LDAP, Net::LDAP::Batch
| Net-LDAP-Class documentation | Contained in the Net-LDAP-Class distribution. |
package Net::LDAP::Class; use strict; use warnings; use base qw( Rose::Object ); use Carp; use Data::Dump (); use Net::LDAP; use Net::LDAP::Entry; use Net::LDAP::Control::Paged; use Net::LDAP::Constant qw(LDAP_CONTROL_PAGED); use Net::LDAP::Batch; use Net::LDAP::Class::Metadata; use Net::LDAP::Class::Iterator; use Net::LDAP::Class::MultiIterator; use Net::LDAP::Class::SimpleIterator; use Net::LDAP::Class::MethodMaker ( 'scalar --get_set_init' => [qw( ldap ldap_entry debug error )], 'scalar' => [qw( batch prev_batch )], 'object_or_class_meta' => [qw( attributes unique_attributes base_dn )], ); use overload '""' => 'stringify', 'bool' => sub {1}, 'fallback' => 1; our $VERSION = '0.26';
sub init { my $self = shift; $self->SUPER::init(@_); my $meta = $self->metadata; if ( !$meta or !$meta->is_initialized ) { croak "must initialize Metadata class before instantiating a new object"; } $self->{ldap} ||= $self->init_ldap; if ( !$self->ldap->isa('Net::LDAP') ) { croak "ldap value is not a Net::LDAP-derived object"; } return $self; }
sub metadata_class {'Net::LDAP::Class::Metadata'}
sub metadata { my ($self) = shift; # object method if ( ref $self ) { return $self->{_meta} ||= $self->metadata_class->for_class( ref $self ); } # class method return $Net::LDAP::Class::Metadata::Objects{$self} || $self->metadata_class->for_class($self); }
sub init_ldap { my $self = shift; if ( !$self->ldap_uri ) { croak "must set ldap_uri() or override init_ldap()"; } my $ldap = Net::LDAP->new( $self->ldap_uri ) or croak "can't create new Net::LDAP: $!"; my $msg = $ldap->bind() or croak "can't do anonymous LDAP bind: $!"; if ( $msg->code ) { croak "LDAP bind failed: " . $self->get_ldap_error($msg); } return $ldap; }
sub init_debug { $ENV{PERL_DEBUG} || $ENV{LDAP_DEBUG} }
sub init_ldap_entry { return undef }
sub get_ldap_error { my $self = shift; my $msg = shift or croak "ldap_msg required"; my $str = "\n" . join( "\n", "Return code: " . $msg->code, "Message: " . $msg->error_name, " :" . $msg->error_text, "MessageID: " . $msg->mesg_id, "DN: " . $msg->dn, ) . "\n"; return $str; }
sub stringify { my $self = shift; for my $key ( @{ $self->unique_attributes } ) { my $val = $self->$key; return $val if defined $val; } return $self; }
sub find { my $self = shift; my $class = ref($self) || $self; my %opts = @_; my $ldap = delete $opts{ldap} || $self->ldap; if ( !$ldap ) { croak "Net::LDAP object required"; } my $base = delete $opts{base_dn} || $self->base_dn; if ( !$base ) { croak "must indicate base_dn in opts or call as object method"; } my $attr = delete $opts{attrs} || $self->attributes; my $msg = $ldap->search( base => $base, attrs => $attr, %opts, ); if ( $msg->code ) { croak $self->get_ldap_error($msg); } my @results; for my $entry ( $msg->entries() ) { push( @results, $class->new( ldap => $ldap, ldap_entry => $entry, ) ); } return unless @results; return wantarray ? @results : \@results; }
sub create { my $self = shift; unless ( $self->check_unique_attributes_set ) { croak "at least one unique attribute must be set in order to create()"; } my @action = $self->action_for_create(@_) or return; $self->do_batch(@action); $self->read or croak "cannot read newly created $self"; return $self; }
sub read { my $self = shift; my %opts = @_; my ( $filter, $value ); if ( !$opts{filter} && !$opts{value} ) { unless ( $self->check_unique_attributes_set ) { croak "cannot read() without unique attribute set. " . "Unique attributes include: " . join( ', ', @{ $self->unique_attributes } ); } # get first unique key set for filter for my $key ( @{ $self->unique_attributes } ) { if ( defined $self->$key ) { $filter = $key; $value = $self->$key; last; } } } else { $filter = delete $opts{filter}; $value = delete $opts{value}; } if ( !defined $filter ) { croak "could not find a unique filter to read() on"; } if ( !defined $value ) { croak "could not find a unique value to read() on"; } my $base_dn = delete $opts{base_dn} || $self->base_dn; $self->debug && warn "read() within $base_dn : $filter=$value\n"; my $msg = $self->ldap->search( base => $base_dn, scope => "sub", filter => "($filter=$value)", attrs => $self->attributes, ); if ( $msg->count() > 0 ) { carp "$filter $value exists" if $self->debug; my $entry = $msg->entry(0); # set any entry attributes we've got cached in $self for my $attr ( keys %{ $self->{_not_yet_set} } ) { my $new = $self->{_not_yet_set}->{$attr}; my $old = $entry->get_value($attr) || ''; if ( $new ne $old ) { $entry->replace( $attr, delete $self->{_not_yet_set}->{$attr} ); $self->{_was_set}->{$attr}->{new} = $new; $self->{_was_set}->{$attr}->{old} = $old; } else { delete $self->{_not_yet_set}->{$attr}; } } # this will cause any existing entry to be DESTROYed $self->ldap_entry($entry); return $self; } else { return; } }
sub update { my $self = shift; $self->check_unique_attributes_set; unless ( $self->ldap_entry ) { croak "can't update() without first having a Net::LDAP::Entry loaded"; } my @action = $self->action_for_update(@_) or return; # clear, since action_for_update() has already used them. $self->{_was_set} = {}; $self->do_batch(@action); return $self->read; }
sub delete { my $self = shift; $self->check_unique_attributes_set; unless ( $self->ldap_entry ) { croak "can't delete() without having a Net::LDAP::Entry loaded"; } my @action = $self->action_for_delete or return; return $self->do_batch(@action); }
sub read_or_create { my $self = shift; if ( !$self->read(@_) ) { $self->create(@_); } return $self; }
sub save { my $self = shift; if ( $self->ldap_entry ) { $self->update; } else { $self->read_or_create; } return $self; }
sub validate { my ( $self, $attr, $value ) = @_; if ( $self->debug ) { warn "validate $attr: $value\n"; } return 1; }
sub do_batch { my $self = shift; my @actions = @_; if ( !@actions ) { warn "no actions to execute\n"; return; } my $batch = $self->batch || Net::LDAP::Batch->new( ldap => $self->ldap, debug => $self->debug, ); if ( $self->debug ) { warn "Batch: " . Data::Dump::dump($batch); } $batch->add_actions(@actions); $self->prev_batch($batch); $batch->do or croak $batch->error; return $batch; }
sub add_to_batch { my $self = shift; my $batch = $self->batch || Net::LDAP::Batch->new( ldap => $self->ldap, debug => $self->debug ); $batch->add_actions(@_); $self->batch($batch); }
sub rollback { my $self = shift; if ( $self->prev_batch ) { $self->prev_batch->rollback or croak $self->prev_batch->error; } else { croak "no prev_batch to rollback"; } return 1; }
sub action_for_create { croak "must override action_for_create()"; }
sub action_for_update { croak "must override action_for_update()"; }
sub action_for_delete { croak "must override action_for_delete()"; }
sub check_unique_attributes_set { my $self = shift; my $uk = $self->unique_attributes; if ( !ref($uk) eq 'ARRAY' ) { croak "unique_attributes must be an ARRAY ref"; } for my $key (@$uk) { if ( defined $self->$key ) { return 1; } } return 0; }
sub AUTOLOAD { my ( $self, @args ) = @_; my ($attribute) = ( our $AUTOLOAD =~ /([^:]+)$/ ); # carp "AUTOLOAD called for " # . ref($self) # . " -> $attribute " # . Data::Dump::dump( \@args ); if ( $attribute eq 'DESTROY' ) { #Data::Dump::dump($self); return; } croak qq[no such attribute or method "$attribute" defined for package "] . ref($self) . qq[ -- do you need to add '$attribute' to your setup() call?"]; }
sub dump { my $self = shift; return Data::Dump::dump($self); }
sub has_local_changes { my $self = shift; return scalar keys %{ $self->{_was_set} }; }
sub act_on_all { my $self = shift; my $coderef = shift or croak "coderef required"; my $opts = shift || {}; my $class = ref($self) || $self; if ( ref $coderef ne 'CODE' ) { croak "coderef is not a CODE reference"; } my $filter = $opts->{filter} || $self->metadata->unique_attributes->[0] . '=*'; my $page_size = $opts->{page_size} || 500; my $ldap = $opts->{ldap} || $self->ldap; my $page = Net::LDAP::Control::Paged->new( size => $page_size ); my $cookie; my @args = ( 'base' => $self->metadata->base_dn, 'filter' => "($filter)", 'attrs' => $self->metadata->attributes, 'control' => [$page], ); my $count = 0; PAGE: while ( my $ldap_search = $ldap->search(@args) ) { # fatal on search error croak "error searching ldap: ", $self->get_ldap_error($ldap_search) if ( $ldap_search->code ); ENTRY: while ( my $ldap_entry = $ldap_search->shift_entry ) { $count++; my $nlc = $class->new( ldap => $ldap, ldap_entry => $ldap_entry ); $self->debug and warn sprintf( "%6d %s\n", $count, $nlc ); $coderef->($nlc); } # handle next search page my ($resp) = $ldap_search->control(LDAP_CONTROL_PAGED) or last PAGE; $cookie = $resp->cookie; if ( !$cookie ) { last PAGE; } $page->cookie($cookie); } # be nice to the server and stop the search if we still have a cookie if ($cookie) { $page->cookie($cookie); $page->size(0); $ldap->ldap->search(@args); croak "LDAP seach ended prematurely."; } return $count; }
sub isa_user { return shift->isa('Net::LDAP::Class::User'); }
sub isa_group { return shift->isa('Net::LDAP::Class::Group'); } 1; __END__