Taming Account Management with Net::LDAP::Class

Peter Karman

http://www.peknet.com/~karpet/slides/fp/nlc

Which do you prefer?

 my $ldap = get_my_ldap_object();          
 $result = $ldap->add( 
    'cn=Barbara Jensen, o=University of Michigan, c=US',
    attr => [
        'cn'   => ['Barbara Jensen', 'Barb Jensen'],
        'sn'   => 'Jensen',
        'mail' => 'b.jensen@umich.edu',
        'objectclass' => ['top', 'person',
                          'organizationalPerson',
                          'inetOrgPerson' ],
        ]
    );
 $result->code 
    && warn "failed to add entry: ", $result->error ;
 $mesg = $ldap->unbind;
      

Or...

 use MyLDAPUser;
 my $ldap = get_my_ldap_object();          
 my $person = MyLDAPUser->new( ldap => $ldap );
 $person->cn(['Barbara Jensen', 'Barb Jensen']);
 $person->sn('Jensen');
 $person->mail('b.jensen@umich.edu');
 $person->create;

Analogy

Net::LDAP::Class is to Net::LDAP as DBIx::Class is to DBI

Lord, save me from writing [SQL|LDAP filters].

Less code, more fun

  • Hide your oft-repeated actions away in a library.
  • No pesky Net::LDAP::Message checking.
  • Built-in relationship handlers.
  • CRUD method names.
  • Active Directory? No problem.

Transactions

  • Net::LDAP::Batch
  • Not ACID, but BTN*
  • automatic rollback



* Better Than Nothing

Introspection

 package MyLDAPUser;
 use base qw( Net::LDAP::Class );
 
 __PACKAGE__->metadata->setup(
    use_loader      => 1,
    object_classes  => [qw( posixAccount )], # optional
 );
 
 sub init_ldap {
    return Net::LDAP->new( 'ldap.mycompany.org' );
 }
 
 1;

Integration

 use MyLDAPUser;
 use MyORMClass;
 my @attrs = qw( uidNumber gidNumber lastModified );
 MyLDAPUser->act_on_all(
   sub {
     my $ldapuser = shift;
     my $dbuser = MyORMClass->new( 
        username => $ldapuser->username )
            ->load_or_insert;
     for my $attr (@attrs) { 
        $dbuser->$attr( $ldapuser->$attr ); 
     }
     $dbuser->save;
   },
   { ldap => Net::LDAP->new( 'ldap.mycompany.org' ) }
 );

Validation (Little Bobby Tables)



xkcd ftw

Validation

 package MyLDAPUser;
 sub validate {
    my ( $self, $attr, $value ) = @_;
    my $method = 'validate_' . $attr;
    return $self->can($method) 
        ? $self->$method($value) 
        : 1;
 }
 
 sub validate_uid {
    my ( $self, $value ) = @_;
    croak "invalid uid: $value" 
        unless $value =~ m/^\w+$/;
 }

Convenience

  • Passwords automatically set/encoded on save.
  • A rose by any other username...
  • NLC objects stringify to first unique attribute (username).
  • Group relationships baked in.

Groups

 use MyLDAPUser;

 my $user = MyLDAPUser->new( username => 'larry' )
    ->read;
 
 printf("user %s primary group is %s\n", 
    $user, $user->group);
  
 for my $group ($user->groups) {
    printf(" secondary group: %s\n", $group);
 }    

Groups (continued)

 use MyLDAPUser;
 use MyLDAPGroup;

 my $user  = MyLDAPUser->new( username => 'larry' )
    ->read_or_create;
 my $group = MyLDAPGroup->new( name => 'stooges' )
    ->read_or_create;
 
 $user->add_to_group( $group ) 
    unless $group->has_user( $user );
    
 $user->save;

Example code

Note: this slide added post-presentation. You can find example code in the Subversion tree for Net::LDAP::Class.

Credits

  • Bridget Kromhout for reviewing these slides
  • Minnesota Supercomputing Institute for sponsoring development.
  • https://svn.msi.umn.edu/sw/perl/