LiCoM::Person: Encode/Decode UTF-8 sen[dt] to/from LDAP-server.
[licom.git] / lib / LiCoM / Person.pm
index cbc2eab..efc37c2 100644 (file)
@@ -3,6 +3,12 @@ package LiCoM::Person;
 use strict;
 use warnings;
 
+use Carp (qw(cluck confess));
+use Encode (qw(encode decode is_utf8));
+
+use LiCoM::Config (qw(get_config));
+use LiCoM::Connection (qw($Ldap));
+
 use Net::LDAP;
 use Net::LDAP::Filter;
 
@@ -12,11 +18,6 @@ Person - High level interface for address books using an LDAP-backend.
 
 =cut
 
-our %Config =
-(
-       base_dn         => undef
-);
-
 our %ValidFields =
 (
        telephoneNumber                 => 1,
@@ -29,7 +30,7 @@ our %ValidFields =
        labeledURI                      => 1,
        mail                            => 1,
        mobile                          => 1,
-       o                               => 1
+       userPassword                    => 0
 );
 
 our %ExternalNames =
@@ -44,67 +45,11 @@ our %ExternalNames =
        uri             => 'labeledURI',
        mail            => 'mail',
        cellphone       => 'mobile',
-       group           => 'o'
+       password        => 'userPassword'
 );
 
-our $Ldap;
-
 return (1);
 
-=head1 METHODS
-
-=over 4
-
-=item Person-E<gt>B<connect> (I<$server>, I<$bind_dn>, I<$password>, I<$base_dn>, [I<$port>])
-
-Connects to the LDAP-Server given.
-
-=cut
-
-sub connect
-{
-       my $pkg = shift;
-       my %opts = @_;
-
-       my $bind_dn = $opts{'bind_dn'};
-       my $base_dn = $opts{'base_dn'};
-       my $uri     = $opts{'uri'};
-       my $passwd  = $opts{'password'};
-
-       my $msg;
-
-       $Ldap = Net::LDAP->new ($uri);
-
-       $msg = $Ldap->bind ($bind_dn, password => $passwd);
-       if ($msg->is_error ())
-       {
-               warn ('LDAP bind failed: ' . $msg->error_text ());
-               return (0);
-       }
-
-       $Config{'base_dn'} = $base_dn;
-
-       return (1);
-}
-
-=item Person-E<gt>B<disconnect> ()
-
-Disconnect from the LDAP-Server.
-
-=cut
-
-sub disconnect
-{
-       $Ldap->unbind ();
-       $Ldap = undef;
-}
-
-=item Person-E<gt>B<new> (I<$ldap_entry>)
-
-Created a new I<Person>-object from the passed I<Net::LDAP::Entry>-object.
-
-=cut
-
 sub new
 {
        my $pkg = shift;
@@ -117,13 +62,26 @@ sub new
        for (keys %ValidFields)
        {
                my $key = $_;
-               $obj->{$key} = $entry->get_value ($key, asref => $ValidFields{$key});
+               my $val = $entry->get_value ($key, asref => $ValidFields{$key});
+
+               if (ref ($val))
+               {
+                       $obj->{$key} = [map { decode ('UTF-8', $_) } (@$val)];
+               }
+               else
+               {
+                       $obj->{$key} = decode ('UTF-8', $val);
+               }
        }
 
        return (bless ($obj, $pkg));
 }
 
-=item Person-E<gt>B<load> (I<$cn>)
+=head1 STATIC FUNCTIONS
+
+=over 4
+
+=item LiCoM::Person-E<gt>B<load> (I<$cn>)
 
 Loads the given CN and returns the B<Person>-object.
 
@@ -138,14 +96,14 @@ sub load
 
        if (!$retval)
        {
-               warn ("CN '$cn' could not be found");
-               return (undef);
+               cluck ("CN '$cn' could not be found");
+               return;
        }
        
        return ($retval);
 }
 
-=item Person-E<gt>B<create> (B<lastname> =E<gt> I<$lastname>, B<firstname> =E<gt> I<$firstname>, ...)
+=item LiCoM::Person-E<gt>B<create> (B<lastname> =E<gt> I<$lastname>, B<firstname> =E<gt> I<$firstname>, ...)
 
 Create a new I<Net::LDAP::Entry>-object and return it's corresponding
 I<Person>-object.
@@ -161,7 +119,7 @@ sub create
        my $dn;
        my $ou;
 
-       $entry->add (objectClass => [qw(top organizationalUnit person organizationalPerson inetOrgPerson)]);
+       $entry->add (objectClass => [qw(person organizationalPerson inetOrgPerson)]);
 
        for (keys %hash)
        {
@@ -179,11 +137,11 @@ sub create
                {
                        if (ref ($val) eq 'ARRAY')
                        {
-                               $entry->add ($field => [@$val]) if (@$val);
+                               $entry->add ($field => [map { encode ('UTF-8', $_) } (@$val)]) if (@$val);
                        }
                        elsif (!ref ($val))
                        {
-                               $entry->add ($field => [$val]) if ($val);
+                               $entry->add ($field => [encode ('UTF-8', $val)]) if ($val);
                        }
                        else
                        {
@@ -195,53 +153,51 @@ sub create
                        my $temp;
                        if (ref ($val) eq 'ARRAY')
                        {
-                               $temp = $val->[0];
+                               $temp = encode ('UTF-8', $val->[0]);
                        }
                        elsif (!ref ($val))
                        {
-                               $temp = $val;
+                               $temp = encode ('UTF-8', $val);
                        }
                        else
                        {
                                warn ("You cannot pass ref-type " . ref ($val));
                        }
 
-                       $entry->add ($field => $val) if (defined ($val) and $val);
+                       $entry->add ($field => $temp) if (defined ($temp) and $temp);
                }
        }
 
+       # $sn and $gn are UTF-8
        my $sn = $entry->get_value ('sn');
        my $gn = $entry->get_value ('givenName');
 
        if (!defined ($sn) or !defined ($gn))
        {
                warn ("sn or givenName not given");
-               return (undef);
+               return;
        }
 
-       $dn = "cn=$sn $gn," . $Config{'base_dn'};
-       ($ou) = $Config{'base_dn'} =~ m/\bou\s*=\s*([^,]+)/i;
+       $ou = encode ('UTF-8', 'Person');
+       $dn = "cn=$sn $gn,ou=$ou," . encode ('UTF-8', get_config ('base_dn'));
        
        $entry->add (cn => "$sn $gn", ou => $ou);
        $entry->dn ($dn);
 
-       print "<!--\n";
-       $entry->dump (*STDOUT);
-       print "-->\n";
-
        $entry->changetype ('add');
        my $mesg = $entry->update ($Ldap);
 
        if ($mesg->is_error ())
        {
-               warn ("Error while creating entry '$dn' on LDAP server: " . $mesg->error_text ());
-               return (undef);
+               my $tmp = decode ('UTF-8', $dn);
+               warn ("Error while creating entry '$tmp' on LDAP server: " . $mesg->error_text ());
+               return;
        }
 
        return (new ($pkg, $entry));
 }
 
-=item Person-E<gt>B<search> (B<firstname> =E<gt> I<"Flor*">)
+=item LiCoM::Person-E<gt>B<search> (B<firstname> =E<gt> I<"Flor*">)
 
 Search for the given patterns. Returns a list of I<Person>-objects.
 
@@ -286,12 +242,23 @@ sub search
 
                        $value =~ s/([\(\)\\])/\\$1/g;
 
+                       confess ("Value is not UTF-8 encoded: `$value'") if (!is_utf8 ($value));
+
                        push (@disjunc, "($field=$value)");
                }
                        
                if (@disjunc)
                {
-                       push (@konjunct, join ('', '(|', @disjunc, ')'));
+                       my $tmp;
+                       if (scalar (@disjunc) == 1)
+                       {
+                               $tmp = $disjunc[0];
+                       }
+                       else
+                       {
+                               $tmp = join ('', '(|', @disjunc, ')');
+                       }
+                       push (@konjunct, $tmp);
                }
        }
 
@@ -304,12 +271,10 @@ sub search
                $filter = '(objectclass=inetOrgPerson)';
        }
 
-       #print STDERR "Debug: using filter: $filter";
-       
        $mesg = $Ldap->search
        (
-               base   => $Config{'base_dn'},
-               filter => $filter
+               base   => 'ou=Person,' . get_config ('base_dn'),
+               filter => encode ('UTF-8', $filter)
        );
 
        if ($mesg->is_error ())
@@ -329,6 +294,57 @@ sub search
        return (@retval);
 }
 
+=item LiCoM::Person-E<gt>B<get_user> (I<$dn>)
+
+Returns the cn and, if defined, the user-id of this dn.
+
+=cut
+
+sub get_user
+{
+       my $pkg = shift;
+       my $dn = shift;
+       my ($search) = $dn =~ m/cn\s*=\s*([^,]+)/i;
+
+       return unless ($search);
+       
+       my $cn = '';
+       my $id = '';
+
+       my $mesg = $Ldap->search
+       (
+               base   => 'ou=Person,' . get_config ('base_dn'),
+               filter => "(cn=$search)"
+       );
+
+       if ($mesg->is_error ())
+       {
+               cluck ("Error while querying LDAP server: " . $mesg->error_text ());
+               return;
+       }
+
+       for ($mesg->entries ())
+       {
+               my $e = $_;
+               my ($t_cn) = $e->get_value ('cn', asref => 0);
+               my ($t_id) = $e->get_value ('uid', asref => 0);
+
+               if (!$id or $t_id)
+               {
+                       $cn = $t_cn;
+                       $id = $t_id;
+               }
+       }
+
+       return ($cn, $id);
+}
+
+=back
+
+=head1 METHODS
+
+=over 4
+
 =item I<$obj>-E<gt>B<delete> ()
 
 Deletes the record.
@@ -355,22 +371,35 @@ Get or set the lastname.
 
 sub _update_dn
 {
+       confess ("Wrong number of arguments") if (@_ != 3);
        my $obj = shift;
+
+       my $obj_new;
+       my %hash_new;
+
+       my $sn = shift;
+       my $gn = shift;
+
        my $entry = $obj->{'ldap'};
-       my $sn = $obj->{'sn'};
-       my $gn = $obj->{'givenName'};
-       my $cn = "$sn $gn";
-       my $dn = "cn=$cn," . $Config{'base_dn'};
 
-       $obj->{'cn'} = $cn;
+       if (($sn eq $obj->{'sn'}) && ($gn eq $obj->{'givenName'}))
+       {
+               return;
+       }
 
-       print STDERR "This is _update_dn, trying to set dn=$dn";
+       $hash_new{$_} = $obj->{$_} for (keys %ValidFields);
+       $hash_new{'sn'} = $sn;
+       $hash_new{'givenName'} = $gn;
+       delete ($hash_new{'cn'});
 
-       $entry->changetype ('modify');
-       $entry->replace (sn => $sn, givenName => $gn, cn => $cn);
-       $entry->update ($Ldap);
-       $entry->dn ($dn);
-       $entry->update ($Ldap);
+       $obj_new = LiCoM::Person->create (%hash_new)
+               or confess ("Cannot duplicate LDAP entry");
+
+       $obj->delete ();
+
+       %$obj = %$obj_new;
+
+       return ($obj->{'dn'});
 }
 
 sub lastname
@@ -379,8 +408,7 @@ sub lastname
 
        if (@_)
        {
-               $obj->{'sn'} = shift;
-               _update_dn ($obj);
+               _update_dn ($obj, shift, $obj->{'givenName'});
        }
 
        return ($obj->{'sn'});
@@ -398,8 +426,7 @@ sub firstname
 
        if (@_)
        {
-               $obj->{'givenName'} = shift;
-               _update_dn ($obj);
+               _update_dn ($obj, $obj->{'sn'}, shift);
        }
 
        return ($obj->{'givenName'});
@@ -431,9 +458,8 @@ sub name
 
 =item I<$obj>-E<gt>B<uri> ([I<@uri>])
 
-=item I<$obj>-E<gt>B<group> ([I<@groups>])
-
-Get or set the attribute.
+Get or set the attribute. This is the same as calling S<I<$obj>-E<gt>B<set>
+(I<$field>, I<\@values>)> or S<I<$obj>-E<gt>B<get> (I<$field>)>.
 
 =cut
 
@@ -450,14 +476,34 @@ sub AUTOLOAD
        return (set ($obj, $field, @values ? [@values] : undef))
 }
 
+=item I<$obj>-E<gt>B<get> (I<$field>)
+
+Returs the value(s) of field I<$field>.
+
+=cut
+
 sub get
 {
        my $obj = shift;
        my $field = shift;
 
-       return (set ($obj, $field, undef));
+       if (wantarray ())
+       {
+               return (set ($obj, $field, undef));
+       }
+       else
+       {
+               return (scalar (set ($obj, $field, undef)));
+       }
 }
 
+=item I<$obj>-E<gt>B<set> (I<$field>, I<\@values>)
+
+Sets the field I<$field> to the value(s) I<\@valued>. Pass an empty array-ref
+to delete the field.
+
+=cut
+
 sub set
 {
        my $obj = shift;
@@ -471,11 +517,13 @@ sub set
        }
        if (!defined ($ValidFields{$field}))
        {
-               return (undef);
+               return;
        }
 
        if (defined ($value))
        {
+               $_ = encode ('UTF-8', $_) for (@$value);
+
                $entry->changetype ('modify');
 
                if ($ValidFields{$field})
@@ -493,7 +541,10 @@ sub set
                $entry->update ($Ldap);
        }
 
-       $obj->{$field} = [] unless (defined ($obj->{$field}));
+       if (!defined ($obj->{$field}) and $ValidFields{$field})
+       {
+               $obj->{$field} = [];
+       }
        
        if (wantarray () and $ValidFields{$field})
        {
@@ -505,62 +556,6 @@ sub set
        }
 }
 
-sub get_user
-{
-       my $pkg = shift;
-       my $dn = shift;
-       my ($search) = $dn =~ m/cn\s*=\s*([^,]+)/i;
-
-       die unless ($search);
-       
-       my $cn = '';
-       my $id = '';
-
-       my $mesg = $Ldap->search
-       (
-               base   => $Config{'base_dn'},
-               filter => "(cn=$search)"
-       );
-
-       if ($mesg->is_error ())
-       {
-               warn ("Error while querying LDAP server: " . $mesg->error_text ());
-               return ('');
-       }
-
-       for ($mesg->entries ())
-       {
-               my $e = $_;
-               my ($t_cn) = $e->get_value ('cn', asref => 0);
-               my ($t_id) = $e->get_value ('uid', asref => 0);
-
-               if (!$id or $t_id)
-               {
-                       $cn = $t_cn;
-                       $id = $t_id;
-               }
-       }
-
-       return ($cn, $id);
-}
-
-sub password
-{
-       my $obj = shift;
-       my $entry = $obj->{'ldap'};
-       my $pwd;
-
-       if (@_)
-       {
-               $pwd = shift;
-               $entry->changetype ('modify');
-               $entry->replace (userPassword => $pwd);
-               $entry->update ($Ldap);
-       }
-
-       $pwd = $entry->get_value ('userPassword');
-}
-
 =back
 
 =head1 AUTHOR