rt 4.0.23
[freeside.git] / rt / lib / RT / User.pm
index 2a14cd1..1859d3f 100755 (executable)
@@ -2,7 +2,7 @@
 #
 # COPYRIGHT:
 #
-# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
 #                                          <sales@bestpractical.com>
 #
 # (Except where explicitly superseded by other copyright notices)
@@ -81,7 +81,6 @@ use Digest::MD5;
 use RT::Principals;
 use RT::ACE;
 use RT::Interface::Email;
-use Encode;
 use Text::Password::Pronounceable;
 
 sub _OverlayAccessible {
@@ -166,18 +165,10 @@ sub Create {
         return ( 0, $self->loc("Must specify 'Name' attribute") );
     }
 
-    #SANITY CHECK THE NAME AND ABORT IF IT'S TAKEN
-    if (RT->SystemUser) {   #This only works if RT::SystemUser has been defined
-        my $TempUser = RT::User->new(RT->SystemUser);
-        $TempUser->Load( $args{'Name'} );
-        return ( 0, $self->loc('Name in use') ) if ( $TempUser->Id );
-
-        my ($val, $message) = $self->ValidateEmailAddress( $args{'EmailAddress'} );
-        return (0, $message) unless ( $val );
-    } else {
-        $RT::Logger->warning( "$self couldn't check for pre-existing users");
-    }
-
+    my ( $val, $msg ) = $self->ValidateName( $args{'Name'} );
+    return ( 0, $msg ) unless $val;
+    ( $val, $msg ) = $self->ValidateEmailAddress( $args{'EmailAddress'} );
+    return ( 0, $msg ) unless ($val);
 
     $RT::Handle->BeginTransaction();
     # Groups deal with principal ids, rather than user ids.
@@ -269,6 +260,30 @@ sub Create {
     return ( $id, $self->loc('User created') );
 }
 
+=head2 ValidateName STRING
+
+Returns either (0, "failure reason") or 1 depending on whether the given
+name is valid.
+
+=cut
+
+sub ValidateName {
+    my $self = shift;
+    my $name = shift;
+
+    return ( 0, $self->loc('empty name') ) unless defined $name && length $name;
+
+    my $TempUser = RT::User->new( RT->SystemUser );
+    $TempUser->Load($name);
+
+    if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) ) {
+        return ( 0, $self->loc('Name in use') );
+    }
+    else {
+        return 1;
+    }
+}
+
 =head2 ValidatePassword STRING
 
 Returns either (0, "failure reason") or 1 depending on whether the given
@@ -571,6 +586,25 @@ sub ValidateEmailAddress {
     }
 }
 
+=head2 SetName
+
+Check to make sure someone else isn't using this name already
+
+=cut
+
+sub SetName {
+    my $self  = shift;
+    my $Value = shift;
+
+    my ( $val, $message ) = $self->ValidateName($Value);
+    if ($val) {
+        return $self->_Set( Field => 'Name', Value => $Value );
+    }
+    else {
+        return ( 0, $message );
+    }
+}
+
 =head2 SetEmailAddress
 
 Check to make sure someone else isn't using this email address already
@@ -844,7 +878,7 @@ sub _GeneratePassword_sha512 {
 
     my $sha = Digest::SHA->new(512);
     $sha->add($salt);
-    $sha->add(encode_utf8($password));
+    $sha->add(Encode::encode( 'UTF-8', $password));
     return join("!", "", "sha512", $salt, $sha->b64digest);
 }
 
@@ -921,18 +955,18 @@ sub IsPassword {
         my $hash = MIME::Base64::decode_base64($stored);
         # Decoding yields 30 byes; first 4 are the salt, the rest are substr(SHA256,0,26)
         my $salt = substr($hash, 0, 4, "");
-        return 0 unless substr(Digest::SHA::sha256($salt . Digest::MD5::md5($value)), 0, 26) eq $hash;
+        return 0 unless substr(Digest::SHA::sha256($salt . Digest::MD5::md5(Encode::encode( "UTF-8", $value))), 0, 26) eq $hash;
     } elsif (length $stored == 32) {
         # Hex nonsalted-md5
-        return 0 unless Digest::MD5::md5_hex(encode_utf8($value)) eq $stored;
+        return 0 unless Digest::MD5::md5_hex(Encode::encode( "UTF-8", $value)) eq $stored;
     } elsif (length $stored == 22) {
         # Base64 nonsalted-md5
-        return 0 unless Digest::MD5::md5_base64(encode_utf8($value)) eq $stored;
+        return 0 unless Digest::MD5::md5_base64(Encode::encode( "UTF-8", $value)) eq $stored;
     } elsif (length $stored == 13) {
         # crypt() output
-        return 0 unless crypt(encode_utf8($value), $stored) eq $stored;
+        return 0 unless crypt(Encode::encode( "UTF-8", $value), $stored) eq $stored;
     } else {
-        $RT::Logger->warn("Unknown password form");
+        $RT::Logger->warning("Unknown password form");
         return 0;
     }
 
@@ -1019,8 +1053,7 @@ sub GenerateAuthString {
     my $self = shift;
     my $protect = shift;
 
-    my $str = $self->AuthToken . $protect;
-    utf8::encode($str);
+    my $str = Encode::encode( "UTF-8", $self->AuthToken . $protect );
 
     return substr(Digest::MD5::md5_hex($str),0,16);
 }
@@ -1037,8 +1070,7 @@ sub ValidateAuthString {
     my $auth_string = shift;
     my $protected = shift;
 
-    my $str = $self->AuthToken . $protected;
-    utf8::encode( $str );
+    my $str = Encode::encode( "UTF-8", $self->AuthToken . $protected );
 
     return $auth_string eq substr(Digest::MD5::md5_hex($str),0,16);
 }
@@ -1206,6 +1238,37 @@ sub HasRight {
     return $self->PrincipalObj->HasRight(@_);
 }
 
+=head2 CurrentUserCanSee [FIELD]
+
+Returns true if the current user can see the user, based on if it is
+public, ourself, or we have AdminUsers
+
+=cut
+
+sub CurrentUserCanSee {
+    my $self = shift;
+    my ($what) = @_;
+
+    # If it's public, fine.  Note that $what may be "transaction", which
+    # doesn't have an Accessible value, and thus falls through below.
+    if ( $self->_Accessible( $what, 'public' ) ) {
+        return 1;
+    }
+
+    # Users can see their own properties
+    elsif ( defined($self->Id) && $self->CurrentUser->Id == $self->Id ) {
+        return 1;
+    }
+
+    # If the user has the admin users right, that's also enough
+    elsif ( $self->CurrentUser->HasRight( Right => 'AdminUsers', Object => $RT::System) ) {
+        return 1;
+    }
+    else {
+        return 0;
+    }
+}
+
 =head2 CurrentUserCanModify RIGHT
 
 If the user has rights for this object, either because
@@ -1279,10 +1342,8 @@ sub Preferences {
     my $name = _PrefName (shift);
     my $default = shift;
 
-    my $attr = RT::Attribute->new( $self->CurrentUser );
-    $attr->LoadByNameAndObject( Object => $self, Name => $name );
-
-    my $content = $attr->Id ? $attr->Content : undef;
+    my ($attr) = $self->Attributes->Named( $name );
+    my $content = $attr ? $attr->Content : undef;
     unless ( ref $content eq 'HASH' ) {
         return defined $content ? $content : $default;
     }
@@ -1311,9 +1372,8 @@ sub SetPreferences {
     return (0, $self->loc("No permission to set preferences"))
         unless $self->CurrentUserCanModify('Preferences');
 
-    my $attr = RT::Attribute->new( $self->CurrentUser );
-    $attr->LoadByNameAndObject( Object => $self, Name => $name );
-    if ( $attr->Id ) {
+    my ($attr) = $self->Attributes->Named( $name );
+    if ( $attr ) {
         my ($ok, $msg) = $attr->SetContent( $value );
         return (1, "No updates made")
             if $msg eq "That is already the current value";
@@ -1323,6 +1383,26 @@ sub SetPreferences {
     }
 }
 
+=head2 DeletePreferences NAME/OBJ VALUE
+
+Delete user preferences associated with given object or name.
+
+=cut
+
+sub DeletePreferences {
+    my $self = shift;
+    my $name = _PrefName( shift );
+
+    return (0, $self->loc("No permission to set preferences"))
+        unless $self->CurrentUserCanModify('Preferences');
+
+    my ($attr) = $self->DeleteAttribute( $name );
+    return (0, $self->loc("Preferences were not found"))
+        unless $attr;
+
+    return 1;
+}
+
 =head2 Stylesheet
 
 Returns a list of valid stylesheets take from preferences.
@@ -1334,12 +1414,13 @@ sub Stylesheet {
 
     my $style = RT->Config->Get('WebDefaultStylesheet', $self->CurrentUser);
 
+    if (RT::Interface::Web->ComponentPathIsSafe($style)) {
+        my @css_paths = map { $_ . '/NoAuth/css' } RT::Interface::Web->ComponentRoots;
 
-    my @css_paths = map { $_ . '/NoAuth/css' } RT::Interface::Web->ComponentRoots;
-
-    for my $css_path (@css_paths) {
-        if (-d "$css_path/$style") {
-            return $style
+        for my $css_path (@css_paths) {
+            if (-d "$css_path/$style") {
+                return $style
+            }
         }
     }
 
@@ -1360,7 +1441,7 @@ $user->WatchedQueues('Cc', 'AdminCc');
 sub WatchedQueues {
 
     my $self = shift;
-    my @roles = @_ || ('Cc', 'AdminCc');
+    my @roles = @_ ? @_ : ('Cc', 'AdminCc');
 
     $RT::Logger->debug('WatcheQueues got user ' . $self->Name);
 
@@ -1409,6 +1490,12 @@ sub WatchedQueues {
                             FIELD => 'MemberId',
                             VALUE => $self->PrincipalId,
                           );
+    $watched_queues->Limit(
+                            ALIAS => $queues_alias,
+                            FIELD => 'Disabled',
+                            VALUE => 0,
+                          );
+
 
     $RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues");
 
@@ -1447,7 +1534,9 @@ sub _Set {
     if ( $ret == 0 ) { return ( 0, $msg ); }
 
     if ( $args{'RecordTransaction'} == 1 ) {
-
+        if ($args{'Field'} eq "Password") {
+            $args{'Value'} = $Old = '********';
+        }
         my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
                                                Type => $args{'TransactionType'},
                                                Field     => $args{'Field'},
@@ -1473,25 +1562,9 @@ sub _Value {
     my $self  = shift;
     my $field = shift;
 
-    #if the field is public, return it.
-    if ( $self->_Accessible( $field, 'public' ) ) {
-        return ( $self->SUPER::_Value($field) );
-
-    }
-
-    #If the user wants to see their own values, let them
-    # TODO figure ouyt a better way to deal with this
-    elsif ( defined($self->Id) && $self->CurrentUser->Id == $self->Id ) {
-        return ( $self->SUPER::_Value($field) );
-    }
-
-    #If the user has the admin users right, return the field
-    elsif ( $self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
-        return ( $self->SUPER::_Value($field) );
-    } else {
-        return (undef);
-    }
-
+    # Defer to the abstraction above to know if the field can be read
+    return $self->SUPER::_Value($field) if $self->CurrentUserCanSee($field);
+    return undef;
 }
 
 =head2 FriendlyName
@@ -1570,7 +1643,8 @@ sub SetPrivateKey {
     my $self = shift;
     my $key = shift;
 
-    unless ($self->CurrentUserCanModify('PrivateKey')) {
+    # Users should not be able to change their own PrivateKey values
+    unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
         return (0, $self->loc("Permission Denied"));
     }