import rt 3.2.2
[freeside.git] / rt / lib / RT / User_Overlay.pm
index e828ebd..c4ef340 100644 (file)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# {{{ BEGIN BPS TAGGED BLOCK
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
+#  
+# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC 
+#                                          <jesse@bestpractical.com>
 # 
-# (Except where explictly superceded by other copyright notices)
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
 # 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
 # 
-# END LICENSE BLOCK
+# }}} END BPS TAGGED BLOCK
 =head1 NAME
 
   RT::User - RT User object
@@ -53,85 +75,34 @@ use vars qw(%_USERS_KEY_CACHE);
 use Digest::MD5;
 use RT::Principals;
 use RT::ACE;
+use RT::EmailParser;
 
 
 # {{{ sub _Accessible 
 
 
-sub _ClassAccessible {
+sub _OverlayAccessible {
     {
-     
-        id =>
-                {read => 1, type => 'int(11)', default => ''},
-        Name => 
-                {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(120)', default => ''},
-        Password => 
-                { write => 1, type => 'varchar(40)', default => ''},
-        Comments => 
-                {read => 1, write => 1, admin => 1, type => 'blob', default => ''},
-        Signature => 
-                {read => 1, write => 1, type => 'blob', default => ''},
-        EmailAddress => 
-                {read => 1, write => 1, public => 1,  type => 'varchar(120)', default => ''},
-        FreeformContactInfo => 
-                {read => 1, write => 1, type => 'blob', default => ''},
-        Organization => 
-                {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(200)', default => ''},
-        RealName => 
-                {read => 1, write => 1, public => 1, type => 'varchar(120)', default => ''},
-        NickName => 
-                {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''},
-        Lang => 
-                {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''},
-        EmailEncoding => 
-                {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''},
-        WebEncoding => 
-                {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''},
-        ExternalContactInfoId => 
-                {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(100)', default => ''},
-        ContactInfoSystem => 
-                {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(30)', default => ''},
-        ExternalAuthId => 
-                {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(100)', default => ''},
-        AuthSystem => 
-                {read => 1, write => 1, public => 1, admin => 1,type => 'varchar(30)', default => ''},
-        Gecos => 
-                {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(16)', default => ''},
-
-        PGPKey => {
-                {read => 1, write => 1, public => 1, admin => 1, type => 'text', default => ''},
-        },
-        HomePhone => 
-                {read => 1, write => 1, type => 'varchar(30)', default => ''},
-        WorkPhone => 
-                {read => 1, write => 1, type => 'varchar(30)', default => ''},
-        MobilePhone => 
-                {read => 1, write => 1, type => 'varchar(30)', default => ''},
-        PagerPhone => 
-                {read => 1, write => 1, type => 'varchar(30)', default => ''},
-        Address1 => 
-                {read => 1, write => 1, type => 'varchar(200)', default => ''},
-        Address2 => 
-                {read => 1, write => 1, type => 'varchar(200)', default => ''},
-        City => 
-                {read => 1, write => 1, type => 'varchar(100)', default => ''},
-        State => 
-                {read => 1, write => 1, type => 'varchar(100)', default => ''},
-        Zip => 
-                {read => 1, write => 1, type => 'varchar(16)', default => ''},
-        Country => 
-                {read => 1, write => 1, type => 'varchar(50)', default => ''},
-        Creator => 
-                {read => 1, auto => 1, type => 'int(11)', default => ''},
-        Created => 
-                {read => 1, auto => 1, type => 'datetime', default => ''},
-        LastUpdatedBy => 
-                {read => 1, auto => 1, type => 'int(11)', default => ''},
-        LastUpdated => 
-                {read => 1, auto => 1, type => 'datetime', default => ''},
-
- }
-};
+
+        Name                    => { public => 1,  admin => 1 },
+          Password              => { read   => 0 },
+          EmailAddress          => { public => 1 },
+          Organization          => { public => 1,  admin => 1 },
+          RealName              => { public => 1 },
+          NickName              => { public => 1 },
+          Lang                  => { public => 1 },
+          EmailEncoding         => { public => 1 },
+          WebEncoding           => { public => 1 },
+          ExternalContactInfoId => { public => 1,  admin => 1 },
+          ContactInfoSystem     => { public => 1,  admin => 1 },
+          ExternalAuthId        => { public => 1,  admin => 1 },
+          AuthSystem            => { public => 1,  admin => 1 },
+          Gecos                 => { public => 1,  admin => 1 },
+          PGPKey                => { public => 1,  admin => 1 },
+
+    }
+}
+
 
 
 # }}}
@@ -203,15 +174,23 @@ sub Create {
         @_    # get the real argumentlist
     );
 
-
-    $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'});
-
     #Check the ACL
     unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
         return ( 0, $self->loc('No permission to create users') );
     }
 
 
+    unless ($self->CanonicalizeUserInfo(\%args)) {
+        return ( 0, $self->loc("Could not set user info") );
+    }
+
+    $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'});
+
+    # if the user doesn't have a name defined, set it to the email address
+    $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'});
+
+
+
     # Privileged is no longer a column in users
     my $privileged = $args{'Privileged'};
     delete $args{'Privileged'};
@@ -234,7 +213,9 @@ sub Create {
 
     #TODO Specify some sensible defaults.
 
-    unless ( defined( $args{'Name'} ) ) {
+    unless ( $args{'Name'} ) {
+       use Data::Dumper;
+       $RT::Logger->crit(Dumper \%args);
         return ( 0, $self->loc("Must specify 'Name' attribute") );
     }
 
@@ -259,14 +240,15 @@ sub Create {
     my $principal_id = $principal->Create(PrincipalType => 'User',
                                 Disabled => $args{'Disabled'},
                                 ObjectId => '0');
-    $principal->__Set(Field => 'ObjectId', Value => $principal_id);
     # If we couldn't create a principal Id, get the fuck out.
     unless ($principal_id) {
         $RT::Handle->Rollback();
-        $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
+        $RT::Logger->crit("Couldn't create a Principal on new user create.");
+        $RT::Logger->crit("Strange things are afoot at the circle K");
         return ( 0, $self->loc('Could not create user') );
     }
 
+    $principal->__Set(Field => 'ObjectId', Value => $principal_id);
     delete $args{'Disabled'};
 
     $self->SUPER::Create(id => $principal_id , %args);
@@ -274,50 +256,65 @@ sub Create {
 
     #If the create failed.
     unless ($id) {
+        $RT::Handle->Rollback();
         $RT::Logger->error("Could not create a new user - " .join('-'. %args));
 
         return ( 0, $self->loc('Could not create user') );
     }
 
-
-    #TODO post 2.0
-    #if ($args{'SendWelcomeMessage'}) {
-    #  #TODO: Check if the email exists and looks valid
-    #  #TODO: Send the user a "welcome message" 
-    #}
-
-
-
     my $aclstash = RT::Group->new($self->CurrentUser);
     my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
 
     unless ($stash_id) {
         $RT::Handle->Rollback();
-        $RT::Logger->crit("Couldn't stash the user in groumembers");
+        $RT::Logger->crit("Couldn't stash the user in groupmembers");
         return ( 0, $self->loc('Could not create user') );
     }
 
-    $RT::Handle->Commit;
 
-    #$RT::Logger->debug("Adding the user as a member of everyone"); 
     my $everyone = RT::Group->new($self->CurrentUser);
     $everyone->LoadSystemInternalGroup('Everyone');
-    $everyone->AddMember($self->PrincipalId);
+    unless ($everyone->id) {
+        $RT::Logger->crit("Could not load Everyone group on user creation.");
+        $RT::Handle->Rollback();
+        return ( 0, $self->loc('Could not create user') );
+    }
+
+
+    my ($everyone_id, $everyone_msg) = $everyone->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
+    unless ($everyone_id) {
+        $RT::Logger->crit("Could not add user to Everyone group on user creation.");
+        $RT::Logger->crit($everyone_msg);
+        $RT::Handle->Rollback();
+        return ( 0, $self->loc('Could not create user') );
+    }
 
+
+    my $access_class = RT::Group->new($self->CurrentUser);
     if ($privileged)  {
-        my $priv = RT::Group->new($self->CurrentUser);
-        #$RT::Logger->debug("Making ".$self->Id." a privileged user");
-        $priv->LoadSystemInternalGroup('Privileged');
-        $priv->AddMember($self->PrincipalId);  
+        $access_class->LoadSystemInternalGroup('Privileged');
     } else {
-        my $unpriv = RT::Group->new($self->CurrentUser);
-        #$RT::Logger->debug("Making ".$self->Id." an unprivileged user");
-        $unpriv->LoadSystemInternalGroup('Unprivileged');
-        $unpriv->AddMember($self->PrincipalId);  
+        $access_class->LoadSystemInternalGroup('Unprivileged');
+    }
+
+    unless ($access_class->id) {
+        $RT::Logger->crit("Could not load Privileged or Unprivileged group on user creation");
+        $RT::Handle->Rollback();
+        return ( 0, $self->loc('Could not create user') );
+    }
+
+
+    my ($ac_id, $ac_msg) = $access_class->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);  
+
+    unless ($ac_id) {
+        $RT::Logger->crit("Could not add user to Privileged or Unprivileged group on user creation. Aborted");
+        $RT::Logger->crit($ac_msg);
+        $RT::Handle->Rollback();
+        return ( 0, $self->loc('Could not create user') );
     }
 
 
-   #  $RT::Logger->debug("Finished creating the user");
+    $RT::Handle->Commit;
     return ( $id, $self->loc('User created') );
 }
 
@@ -355,6 +352,10 @@ sub SetPrivileged {
     my $self = shift;
     my $val = shift;
 
+    #Check the ACL
+    unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
+        return ( 0, $self->loc('Permission Denied') );
+    }
     my $priv = RT::Group->new($self->CurrentUser);
     $priv->LoadSystemInternalGroup('Privileged');
    
@@ -376,7 +377,7 @@ sub SetPrivileged {
             return (0,$self->loc("That user is already privileged"));
         }
         if ($unpriv->HasMember($self->PrincipalObj)) {
-            $unpriv->DeleteMember($self->PrincipalId);
+            $unpriv->_DeleteMember($self->PrincipalId);
         } else {
         # if we had layered transactions, life would be good
         # sadly, we have to just go ahead, even if something
@@ -384,7 +385,7 @@ sub SetPrivileged {
             $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
                 "unprivileged. something is drastically wrong.");
         }
-        my ($status, $msg) = $priv->AddMember($self->PrincipalId);  
+        my ($status, $msg) = $priv->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);  
         if ($status) {
             return (1, $self->loc("That user is now privileged"));
         } else {
@@ -397,7 +398,7 @@ sub SetPrivileged {
             return (0,$self->loc("That user is already unprivileged"));
         }
         if ($priv->HasMember($self->PrincipalObj)) {
-            $priv->DeleteMember($self->PrincipalId);
+            $priv->_DeleteMember( $self->PrincipalId);
         } else {
         # if we had layered transactions, life would be good
         # sadly, we have to just go ahead, even if something
@@ -405,7 +406,7 @@ sub SetPrivileged {
             $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
                 "unprivileged. something is drastically wrong.");
         }
-        my ($status, $msg) = $unpriv->AddMember($self->PrincipalId);  
+        my ($status, $msg) = $unpriv->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);  
         if ($status) {
             return (1, $self->loc("That user is now unprivileged"));
         } else {
@@ -571,13 +572,17 @@ sub LoadOrCreateByEmail {
 
         my ($val, $message);
 
+       my ( $Address, $Name ) =
+               RT::EmailParser::ParseAddressFromHeader('', $email);
+       $email = $Address;
+
         $self->LoadByEmail($email);
         $message = $self->loc('User loaded');
         unless ($self->Id) {
             ( $val, $message ) = $self->Create(
                 Name => $email,
                 EmailAddress => $email,
-                RealName     => $email,
+                RealName     => $Name,
                 Privileged   => 0,
                 Comments     => 'Autocreated when added as a watcher');
             unless ($val) {
@@ -667,6 +672,32 @@ sub CanonicalizeEmailAddress {
 
 # }}}
 
+# {{{ sub CanonicalizeUserInfo
+
+
+
+=item CanonicalizeUserInfo HASH of ARGS
+
+# CanonicalizeUserInfo can convert all User->Create options.
+# it takes a hashref of all the params sent to User->Create and
+# returns that same hash, by default nothing is done.
+
+# This function is intended to allow users to have their info looked up via
+# an outside source and modified upon creation.
+
+=cut
+
+sub CanonicalizeUserInfo {
+    my $self = shift;
+    my $args = shift;
+    my $success = 1;
+
+    return ($success);
+}
+
+
+# }}}
+
 
 # {{{ Password related functions
 
@@ -729,7 +760,7 @@ sub ResetPassword {
 
     my $template = RT::Template->new( $self->CurrentUser );
 
-    if ( $self->IsPrivileged ) {
+    if ( $self->Privileged ) {
         $template->LoadGlobalTemplate('RT_PasswordChange_Privileged');
     }
     else {
@@ -752,7 +783,7 @@ sub ResetPassword {
         Argument    => $pass
     );
 
-    $notification->SetTo( $self->EmailAddress );
+    $notification->SetHeader( 'To', $self->EmailAddress );
 
     my ($ret);
     $ret = $notification->Prepare();
@@ -929,7 +960,7 @@ sub GenerateRandomPassword {
 
     my $length = $min_length + int( rand( $max_length - $min_length ) );
 
-    my $char = $self->GenerateRandomNextChar( $total_sum, $start_freq );
+    my $char = $self->_GenerateRandomNextChar( $total_sum, $start_freq );
     my @word = ( $char + $a );
     for ( 2 .. $length ) {
         $char =