[Rt-commit] rt branch, 4.4/remove-user-info, created. rt-4.4.3-166-g1986cd4e7d

Jim Brandt jbrandt at bestpractical.com
Thu Jan 3 11:37:47 EST 2019


The branch, 4.4/remove-user-info has been created
        at  1986cd4e7d2936ac571bde10c59eced5389ce150 (commit)

- Log -----------------------------------------------------------------
commit 7683adbe21ed7328a07698522c8241de5364149f
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Dec 28 22:29:40 2018 +0800

    Remove user related data download links on "About Me" page of priviged users
    
    These download links are created for GDPR, privileged users usually
    don't have this issue.

diff --git a/share/html/Prefs/AboutMe.html b/share/html/Prefs/AboutMe.html
index e2e046711f..df90996e8b 100644
--- a/share/html/Prefs/AboutMe.html
+++ b/share/html/Prefs/AboutMe.html
@@ -56,13 +56,6 @@
 
 </form>
 
-<& /User/Elements/RelatedData,
-    UserObj           => $UserObj,
-    UserDataButton    => loc( 'Download My Data' ),
-    UserTicketsButton => loc( 'Download My Tickets' ),
-    UserTxnButton     => loc( 'Download My Transaction Data' ),
-&>
-
 <%INIT>
 
 my $UserObj = RT::User->new( $session{'CurrentUser'} );

commit a73494e8e616c8a86e1e923e61ddab1892d596f1
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Thu Dec 13 11:33:06 2018 -0500

    Allow TSVExport filename to be set through Filename arg

diff --git a/share/html/Elements/TSVExport b/share/html/Elements/TSVExport
index df7105f794..3f6d5d8136 100644
--- a/share/html/Elements/TSVExport
+++ b/share/html/Elements/TSVExport
@@ -50,6 +50,7 @@ $Class => undef
 $Collection
 $Format
 $PreserveNewLines => 0
+$Filename  => undef
 </%ARGS>
 <%ONCE>
 my $no_html = HTML::Scrubber->new( deny => '*' );
@@ -59,6 +60,7 @@ require HTML::Entities;
 $Class ||= $Collection->ColumnMapClassName;
 
 $r->content_type('application/vnd.ms-excel');
+$r->header_out( 'Content-disposition' => "attachment; filename=$Filename" ) if $Filename;
 
 my $DisplayFormat = $m->comp('/Elements/ScrubHTML', Content => $Format);
 

commit 5a8673412fcb55fe8fe22fafed864cd5b2fda6f2
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Thu Dec 13 11:33:06 2018 -0500

    Set filename of downloaded user related tsv data

diff --git a/share/html/Search/Results.tsv b/share/html/Search/Results.tsv
index 1bbdded741..367c2a6a3a 100644
--- a/share/html/Search/Results.tsv
+++ b/share/html/Search/Results.tsv
@@ -51,6 +51,7 @@ $Query => ''
 $OrderBy => 'id'
 $Order => 'ASC'
 $PreserveNewLines => 0
+$Filename => ''
 </%ARGS>
 <%INIT>
 my $Tickets = RT::Tickets->new( $session{'CurrentUser'} );
@@ -68,5 +69,5 @@ else {
     $Tickets->OrderBy( FIELD => $OrderBy, ORDER => $Order );
 }
 
-$m->comp( "/Elements/TSVExport", Collection => $Tickets, Format => $Format, PreserveNewLines => $PreserveNewLines );
+$m->comp( "/Elements/TSVExport", Collection => $Tickets, Format => $Format, PreserveNewLines => $PreserveNewLines, Filename => $Filename );
 </%INIT>
diff --git a/share/html/SelfService/User/Elements/RelatedData b/share/html/SelfService/User/Elements/RelatedData
index 4ad2469cda..acd17bb350 100644
--- a/share/html/SelfService/User/Elements/RelatedData
+++ b/share/html/SelfService/User/Elements/RelatedData
@@ -51,9 +51,9 @@
 &>
 
 <div>
-    <a href="/SelfService/User/RelatedData.tsv?Type=User&id=<% $UserObj->id %>" class="button"><% $UserDataButton %></a>
-    <a href="/SelfService/Search/Results.tsv?Query=Requestor.id=<% $UserObj->id %>&Format=<% $Format | un %>" class="button"><% $UserTicketsButton %></a>
-    <a href="/SelfService/User/RelatedData.tsv?Type=Transaction&id=<% $UserObj->id %>" class="button"><% $UserTxnButton %></a>
+    <a href="/SelfService/User/RelatedData.tsv?Type=User&id=<% $UserObj->id %>&Filename=UserData.tsv" class="button"><% $UserDataButton %></a>
+    <a href="/SelfService/Search/Results.tsv?Query=Requestor.id=<% $UserObj->id %>&Format=<% $Format | un %>&Filename=UserTicketData.tsv" class="button"><% $UserTicketsButton %></a>
+    <a href="/SelfService/User/RelatedData.tsv?Type=Transaction&id=<% $UserObj->id %>&Filename=UserTransactionData.tsv" class="button"><% $UserTxnButton %></a>
 </div>
 </&>
 
diff --git a/share/html/User/Elements/RelatedData b/share/html/User/Elements/RelatedData
index 051112d969..2e16fdcefe 100644
--- a/share/html/User/Elements/RelatedData
+++ b/share/html/User/Elements/RelatedData
@@ -51,9 +51,9 @@
 &>
 
 <div>
-    <a href="/User/RelatedData.tsv?Type=User&id=<% $UserObj->id %>" class="button"><% $UserDataButton %></a>
-    <a href="/Search/Results.tsv?Query=Requestor.id=<% $UserObj->id %>&Format=<% $Format | un %>" class="button"><% $UserTicketsButton %></a>
-    <a href="/User/RelatedData.tsv?Type=Transaction&id=<% $UserObj->id %>" class="button"><% $UserTxnButton %></a>
+    <a href="/User/RelatedData.tsv?Type=User&id=<% $UserObj->id %>&Filename=UserData.tsv" class="button"><% $UserDataButton %></a>
+    <a href="/Search/Results.tsv?Query=Requestor.id=<% $UserObj->id %>&Format=<% $Format | un %>&Filename=UserTicketData.tsv" class="button"><% $UserTicketsButton %></a>
+    <a href="/User/RelatedData.tsv?Type=Transaction&id=<% $UserObj->id %>&Filename=UserTransactionData.tsv" class="button"><% $UserTxnButton %></a>
 </div>
 </&>
 
diff --git a/share/html/User/RelatedData.tsv b/share/html/User/RelatedData.tsv
index 25804686dd..ce906de010 100644
--- a/share/html/User/RelatedData.tsv
+++ b/share/html/User/RelatedData.tsv
@@ -50,6 +50,7 @@ $PreserveNewLines => 0
 $Type             => 'User'
 $Format           => undef
 $id
+$Filename         => undef
 </%ARGS>
 
 <%INIT>
@@ -81,5 +82,5 @@ if ( $Type eq 'User' ) {
     $Collection->Limit( FIELD => 'Type',       VALUE => 'Comment' );
 }
 
-$m->comp( "/Elements/TSVExport", Collection => $Collection, Format => $Format, PreserveNewLines => $PreserveNewLines );
+$m->comp( "/Elements/TSVExport", Collection => $Collection, Format => $Format, PreserveNewLines => $PreserveNewLines, Filename => $Filename );
 </%INIT>

commit 20e809325a1437e38599798fbc70b1a5ef9a6a99
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Mon Dec 17 12:10:20 2018 -0500

    Add Timezone info to user related data

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 17d912e698..6ab8463bbb 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1403,7 +1403,7 @@ Set($UserDataResultFormat, "'__id__', '__Name__', '__EmailAddress__', '__RealNam
                             '__NickName__', '__Organization__', '__HomePhone__', '__WorkPhone__',\
                             '__MobilePhone__', '__PagerPhone__', '__Address1__', '__Address2__',\
                             '__City__', '__State__','__Zip__', '__Country__', '__Gecos__', '__Lang__',\
-                            '__FreeFormContactInfo__'");
+                            '__Timezone__', '__FreeFormContactInfo__'");
 
 =item C<$UserTransactionDataResultFormat>
 
diff --git a/share/html/Elements/RT__User/ColumnMap b/share/html/Elements/RT__User/ColumnMap
index b4746e39b4..1e6d268c1b 100644
--- a/share/html/Elements/RT__User/ColumnMap
+++ b/share/html/Elements/RT__User/ColumnMap
@@ -146,6 +146,11 @@ my $COLUMN_MAP = {
         title     => 'Status', # loc
         value     => sub { return $_[0]->Disabled? $_[0]->loc('Disabled'): $_[0]->loc('Enabled') },
     },
+    Timezone => {
+        title     => 'Timezone', # loc
+        attribute => 'Timezone',
+        value     => sub { return $_[0]->Timezone },
+    },
 };
 
 </%ONCE>

commit fca10a982093d5da5884f0a9b31af9761b0038b9
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Tue Dec 18 08:28:49 2018 -0500

    Update title of ObjectId in $UserTransactionDataResultFormat

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 6ab8463bbb..56d16c9f8b 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1411,7 +1411,7 @@ This is the format of the user transaction search result for "Download User Tran
 
 =cut
 
-Set($UserTransactionDataResultFormat, "'__ObjectId__', '__id__', '__Created__', '__Description__',\
+Set($UserTransactionDataResultFormat, "'__ObjectId__/TITLE:Ticket Id', '__id__', '__Created__', '__Description__',\
                                         '__OldValue__', '__NewValue__', '__Content__'");
 
 

commit a4d10d41cb511d625c43d06af908a80128f77438
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Dec 21 21:40:52 2018 +0800

    No need to show "Download User ..." buttons on user create page

diff --git a/share/html/Admin/Users/Modify.html b/share/html/Admin/Users/Modify.html
index d5e331c294..ef06d3d381 100644
--- a/share/html/Admin/Users/Modify.html
+++ b/share/html/Admin/Users/Modify.html
@@ -232,7 +232,9 @@
 % }
 </form>
 
+% unless ( $Create ) {
 <& /User/Elements/RelatedData, UserObj => $UserObj &>
+% }
 <%INIT>
 
 my $UserObj = RT::User->new($session{'CurrentUser'});

commit 4f8878effb8852c88f30d4f04f3fddef50213f47
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Fri Dec 21 16:24:09 2018 -0500

    Move User related info portlet into side column

diff --git a/share/html/Admin/Users/Modify.html b/share/html/Admin/Users/Modify.html
index ef06d3d381..4b1181fd28 100644
--- a/share/html/Admin/Users/Modify.html
+++ b/share/html/Admin/Users/Modify.html
@@ -125,6 +125,9 @@
 
 <& /Elements/EditCustomFields, Object => $UserObj, Grouping => 'Access control' &>
 
+</&>
+<&| /Widgets/TitleBox, title => loc('Comments about this user'), class => 'user-info-comments' &>
+<textarea class="comments" name="Comments" cols="80" rows="5" wrap="virtual"><%$UserObj->Comments//$ARGS{Comments}//''%></textarea>
 </&>
 % $m->callback( %ARGS, CallbackName => 'LeftColumnBottom', UserObj => $UserObj );
 </td>
@@ -207,13 +210,14 @@
 
 <& /Elements/EditCustomFieldCustomGroupings, Object => $UserObj &>
 
+% unless ( $Create ) {
+<& /User/Elements/RelatedData, UserObj => $UserObj &>
+% }
+
 % $m->callback( %ARGS, CallbackName => 'RightColumnBottom', UserObj => $UserObj );
 </td></tr>
 <tr>
 <td colspan="2">
-<&| /Widgets/TitleBox, title => loc('Comments about this user'), class => 'user-info-comments' &>
-<textarea class="comments" name="Comments" cols="80" rows="5" wrap="virtual"><%$UserObj->Comments//$ARGS{Comments}//''%></textarea>
-</&>
 %if (!$Create && $UserObj->Privileged) {
 <br />
 <&| /Widgets/TitleBox, title => loc('Signature'), class => 'user-info-signature' &>
@@ -232,9 +236,6 @@
 % }
 </form>
 
-% unless ( $Create ) {
-<& /User/Elements/RelatedData, UserObj => $UserObj &>
-% }
 <%INIT>
 
 my $UserObj = RT::User->new($session{'CurrentUser'});

commit eaca3ea66ddad1bbe7296417e611e7430de73284
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Thu Dec 13 09:47:30 2018 -0500

    Create method AnonymizeUser in User.pm
    
    Create method 'AnonymizeUser' that will remove the personal
    identifying information from a user record, but keep the record alive.

diff --git a/lib/RT/User.pm b/lib/RT/User.pm
index daae994e75..2d6a8cb357 100644
--- a/lib/RT/User.pm
+++ b/lib/RT/User.pm
@@ -281,6 +281,109 @@ sub ValidateName {
     }
 }
 
+=head2 GenerateAnonymousName
+
+Generate a random username proceeded by 'anon_' and then a
+random string, Returns the AnonymousName string.
+
+=cut
+
+sub GenerateAnonymousName {
+    my $self = shift;
+
+    my $name;
+    do {
+        $name = 'anon_' . Digest::MD5::md5_hex( time . {} . rand() );
+    } while !$self->ValidateName($name);
+
+    return $name;
+}
+
+=head2 AnonymizeUser { ClearCustomfields => 1|0 }
+
+Remove all personal identifying information on the user record, but keep
+the user record alive. Additionally replace the username with an
+anonymous name.  Submit ClearCustomfields in a paramhash, if true all
+customfield values applied to the user record will be cleared.
+
+=cut
+
+sub AnonymizeUser {
+    my $self = shift;
+    my %args = (
+        ClearCustomFields => undef,
+        @_,
+    );
+
+    my %skip_clear = map { $_ => 1 } qw/Name Password AuthToken/;
+    my @user_identifying_info
+      = grep { !$skip_clear{$_} && $self->_Accessible( $_, 'write' ) } keys %{ $self->_CoreAccessible() };
+
+    $RT::Handle->BeginTransaction();
+
+    # Remove identifying user information from record
+    foreach my $attr (@user_identifying_info) {
+        if ( defined $self->$attr && length $self->$attr ) {
+            my $method = 'Set' . $attr;
+            my ( $ret, $msg ) = $self->$method('');
+            unless ($ret) {
+                RT::Logger->error( "Could not clear user $attr: " . $msg );
+                $RT::Handle->Rollback();
+                return ( $ret, $self->loc( "Couldn't clear user [_1]", $self->loc($attr) ) );
+            }
+        }
+    }
+
+    # Do not do anything if password is already unset
+    if ( $self->HasPassword ) {
+        my ( $ret, $msg ) = $self->_Set( Field => 'Password', Value => '*NO-PASSWORD*' );
+        unless ($ret) {
+            RT::Logger->error("Could not clear user password: $msg");
+            $RT::Handle->Rollback();
+            return ( $ret, "Could not clear user Password" );
+        }
+    }
+
+    # Generate the random anon username
+    my ( $ret, $msg ) = $self->SetName( $self->GenerateAnonymousName );
+    unless ($ret) {
+        RT::Logger->error( "Could not anonymize user Name: " . $msg );
+        $RT::Handle->Rollback();
+        return ( $ret, $self->loc( "Could not anonymize user [_1]", $self->loc('Name') ) );
+    }
+
+    # Clear AuthToken
+    if ( $self->_Value('AuthToken') ) {
+        my ( $ret, $msg ) = $self->SetAuthToken('');
+        unless ($ret) {
+            RT::Logger->error( "Could not clear user AuthToken: " . $msg );
+            $RT::Handle->Rollback();
+            return ( $ret, $self->loc( "Couldn't clear user [_1]", $self->loc('AuthToken') ) );
+        }
+    }
+
+    # Remove user customfield values
+    if ( $args{'ClearCustomFields'} ) {
+        my $cfs = RT::CustomFields->new( RT->SystemUser );
+        $cfs->LimitToLookupType('RT::User');
+
+        while ( my $cf = $cfs->Next ) {
+            my $ocfvs = $self->CustomFieldValues($cf);
+            while ( my $ocfv = $ocfvs->Next ) {
+                my ( $ret, $msg ) = $ocfv->Delete;
+                unless ($ret) {
+                    RT::Logger->error( "Could not delete ocfv #" . $ocfv->id . ": $msg" );
+                    $RT::Handle->Rollback();
+                    return ( $ret, $self->loc( "Could not clear user custom field [_1]", $cf->Name ) );
+                }
+            }
+        }
+    }
+    $RT::Handle->Commit();
+
+    return ( 1, $self->loc('User successfully anonymized') );
+}
+
 =head2 ValidatePassword STRING
 
 Returns either (0, "failure reason") or 1 depending on whether the given

commit 6758ab11f39985d752a27c31399811b6957a31d1
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Thu Dec 13 09:49:00 2018 -0500

    Create modal mason component

diff --git a/share/html/Elements/Modal b/share/html/Elements/Modal
new file mode 100644
index 0000000000..2c36e8af00
--- /dev/null
+++ b/share/html/Elements/Modal
@@ -0,0 +1,71 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2018 Best Practical Solutions, LLC
+%#                                          <sales at bestpractical.com>
+%#
+%# (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
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%#
+%# 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., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# 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 BPS TAGGED BLOCK }}}
+<div id="<% $ModalId %>" class="<% $Class %>" align="center">
+    <form action="<% $Action %>" method="<% $Method %>" id="<% $ModalId %>" name="<% $Name %>" >
+% foreach my $field (@Fields) {
+        <p><% $field->{'Label'} %>
+%   if ( $field->{'Input'} ) {
+        <input type="<% $field->{'Input'} %>" class="<% $field->{'Class'} %>" name="<% $field->{'Name'} %>" value="<% $field->{'Value'} %>">
+%   }
+        </p>
+% }
+        <a href="#" rel="modal:close" class="button"><% $Cancel %></a>
+        <button type="Submit" class="button"><% $Accept %></button>
+    </form>
+</div>
+
+<%ARGS>
+ at Fields   => ()
+$Name     => ''
+$ModalId  => ''
+$Class    => 'modal'
+$Action   => ''
+$Method   => 'GET'
+$Accept   => loc('OK')
+$Cancel   => loc('Cancel')
+</%ARGS>

commit d258b7725c6ed6ef7b0cb089cc284650e0363dd1
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Thu Dec 13 09:50:15 2018 -0500

    Enhance "User related info" with actions of clearing user info
    
    The added actions are 'Anonymize user' and 'Replace User'.  Anonymize
    user will call the 'AnonymizeUser' method to clear identifying
    information from the user record. 'Replace User' will link to the
    shredder page with a pre formatted search.
    
    With added actions, "Manage user data" is a more appropriate name.
    Portlet

diff --git a/share/html/Admin/Users/Modify.html b/share/html/Admin/Users/Modify.html
index 4b1181fd28..569b263168 100644
--- a/share/html/Admin/Users/Modify.html
+++ b/share/html/Admin/Users/Modify.html
@@ -236,6 +236,21 @@
 % }
 </form>
 
+% if ( $UserObj->Id ) {
+    <& /Elements/Modal, ModalId => "user-info-modal", Method => 'POST', Action => RT->Config->Get('WebPath') . '/Admin/Users/Modify.html', Fields => [
+    { Label   => loc("Are you sure you want to anonymize user: [_1]?", $UserObj->Name) },
+    { Input => 'Hidden', Value => $UserObj->Id, Name => 'id' },
+    { Input => 'Hidden', Value => 1, Name => 'Anonymize' },
+    {
+        Label    => loc("Check to clear user custom fields") . ":",
+        Input    => 'checkbox',
+        Class    => 'checkbox',
+        Name     => 'clear_customfields',
+        Value    => 'On',
+    },
+]
+&>
+% }
 <%INIT>
 
 my $UserObj = RT::User->new($session{'CurrentUser'});
@@ -299,6 +314,11 @@ if ($Create) {
     }
 }
 
+if ( $ARGS{'Anonymize'} and $UserObj->Id ) {
+    my ($ret, $msg) = $UserObj->AnonymizeUser(ClearCustomFields => $ARGS{'clear_customfields'});
+    push @results, $msg;
+}
+
 if ( $UserObj->Id ) {
     # Deal with Password field
     my ($status, $msg) = $UserObj->SafeSetPassword(
diff --git a/share/html/User/Elements/RelatedData b/share/html/User/Elements/RelatedData
index 2e16fdcefe..caaf0adaf8 100644
--- a/share/html/User/Elements/RelatedData
+++ b/share/html/User/Elements/RelatedData
@@ -47,13 +47,45 @@
 %# END BPS TAGGED BLOCK }}}
 <&|/Widgets/TitleBox,
     class => 'user-related-info',
-    title => loc("User related info"),
+    title => loc("Manage user data"),
 &>
 
-<div>
-    <a href="/User/RelatedData.tsv?Type=User&id=<% $UserObj->id %>&Filename=UserData.tsv" class="button"><% $UserDataButton %></a>
-    <a href="/Search/Results.tsv?Query=Requestor.id=<% $UserObj->id %>&Format=<% $Format | un %>&Filename=UserTicketData.tsv" class="button"><% $UserTicketsButton %></a>
-    <a href="/User/RelatedData.tsv?Type=Transaction&id=<% $UserObj->id %>&Filename=UserTransactionData.tsv" class="button"><% $UserTxnButton %></a>
+<div id="manage-user-data">
+    <div class="title"><&|/l&>Download User Information</&></div>
+    <div class="download-user-data-buttons inline-row">
+        <div class="inline-cell">
+            <a class="button" href="/User/RelatedData.tsv?Type=User&id=<% $UserObj->id %>&Filename=UserData.tsv"><% $UserDataButton %></a>
+            <i class="label"><&|/l&>The basic user info</&></i>
+        </div>
+        <div class="inline-cell">
+            <a class="button" href="/Search/Results.tsv?Query=Requestor.id=<% $UserObj->id %>&Format=<% $Format | un %>&Filename=UserTicketData.tsv"><% $UserTicketsButton %></a>
+            <i class="label"><&|/l&>Tickets with the user as a requestor</&></i>
+        </div>
+        <div class="inline-cell">
+            <a class="button" href="/User/RelatedData.tsv?Type=Transaction&id=<% $UserObj->id %>&Filename=UserTransactionData.tsv"><% $UserTxnButton %></a>
+            <i class="label"><&|/l&>Ticket transactions the user created</&></i>
+        </div>
+    </div>
+
+% if ( $session{'CurrentUser'}->HasRight( Object => RT->System, Right => 'AdminUsers' ) ) {
+    <div class="title"><&|/l&>Remove User Information</&></div>
+    <div class="inline-row">
+        <div class="inline-cell">
+            <a class="button" href="#user-info-modal" rel="modal:open" name="anonymize_user"><&|/l&>Anonymize User</&></a>
+            <i class="label"><&|/l&>Remove user information with anonymous username</&></i>
+        </div>
+% if ( $session{'CurrentUser'}->HasRight( Object => RT->System, Right => 'SuperUser' ) ) {
+        <div class="inline-cell">
+            <a class="button" href="<%RT->Config->Get('WebPath')%>/Admin/Tools/Shredder/index.html?Plugin=Users&Users%3Astatus=<% $UserObj->Disabled ? 'disabled' : 'enabled' %>&Users%3Aname=<% $UserObj->Name %>&Users%3Areplace_relations=Nobody&Search=Search" name="replace-user"><&|/l&>Replace User</&></a>
+            <i class="label"><&|/l&>Replace user links in the database with "Nobody" user</&></i>
+        </div>
+        <div class="inline-cell">
+            <a class="button" href="<%RT->Config->Get('WebPath')%>/Admin/Tools/Shredder/index.html?Plugin=Users&Users%3Astatus=<% $UserObj->Disabled ? 'disabled' : 'enabled' %>&Users%3Aname=<% $UserObj->Name %>&Users&Search=Search" name="replace-user"><&|/l&>Replace User</&></a>
+            <i class="label"><&|/l&>Remove all references to user (Tickets linked to this user must be shredded first)</&></i>
+        </div>
+% }
+    </div>
+% }
 </div>
 </&>
 
@@ -63,7 +95,7 @@ my $Format = RT->Config->Get('UserTicketDataResultFormat') || RT->Config->Get('D
 
 <%ARGS>
 $UserObj
-$UserDataButton    => loc( 'Download User Data' )
-$UserTicketsButton => loc( 'Download User Tickets' )
-$UserTxnButton     => loc( 'Download User Transaction Data' )
+$UserDataButton    => loc( 'User Data' )
+$UserTicketsButton => loc( 'User Tickets' )
+$UserTxnButton     => loc( 'User Transactions' )
 </%ARGS>
diff --git a/share/static/css/base/admin.css b/share/static/css/base/admin.css
index 95c5878031..eaa185c368 100644
--- a/share/static/css/base/admin.css
+++ b/share/static/css/base/admin.css
@@ -82,3 +82,29 @@ table.upgrade-history .upgrade-history-parent .widget a {
 table.upgrade-history .upgrade-history-parent .widget a.rolled-up {
     background-image: url(../../../static/images/css/rolldown-arrow.gif);
 }
+
+#manage-user-data div.title {
+    margin-bottom: 5px;
+    font-weight: bold;
+}
+
+div.inline-row {
+    margin-bottom: 10px;
+    display: inline-flex;
+    width: 100%;
+}
+
+div.inline-row div {
+    max-width: 175px;
+    display: block;
+}
+
+div.inline-row a {
+    text-align: center;
+    width: 85%;
+}
+
+div.inline-row i {
+    text-align: left;
+    width: 85%;
+}

commit 9385aece79d7ec47bdbcd24934a944b4f9ec0043
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Thu Dec 13 09:52:42 2018 -0500

    Create test for remove user information

diff --git a/t/web/remove_user_info.t b/t/web/remove_user_info.t
new file mode 100644
index 0000000000..4003f32d99
--- /dev/null
+++ b/t/web/remove_user_info.t
@@ -0,0 +1,121 @@
+use strict;
+use warnings;
+
+use RT::Test tests => undef;
+
+RT::Config->Set( 'ShredderStoragePath', RT::Test->temp_directory . '' );
+
+my ( $baseurl, $agent ) = RT::Test->started_ok;
+
+diag("Test server running at $baseurl");
+my $url = $agent->rt_base_url;
+
+# Login
+$agent->login( 'root' => 'password' );
+
+# Anonymize User
+{
+    my %skip_clear = map { $_ => 1 } qw/Name Password AuthToken/;
+    my @user_identifying_info
+      = grep { !$skip_clear{$_} && RT::User->_Accessible( $_, 'write' ) } keys %{ RT::User->_CoreAccessible() };
+
+    my $user = RT::Test->load_or_create_user(
+        map( { $_ => 'test_string' } @user_identifying_info, 'AuthToken' ),
+        Name         => 'Test User',
+        EmailAddress => 'test at example.com',
+    );
+    ok( $user && $user->id );
+
+    foreach my $attr (@user_identifying_info) {
+        ok( $user->$attr, 'Attribute ' . $attr . ' is set' );
+    }
+
+    my $user_id = $user->id;
+
+    $agent->get_ok( $url . "Admin/Users/Modify.html?id=" . $user_id );
+    $agent->follow_link_ok( { text => 'Anonymize User' } );
+
+    $agent->submit_form_ok( { form_id => 'user-info-modal', }, "Anonymize user" );
+
+    # UserId is still the same, but all other records should be anonimyzed for TestUser
+    my ( $ret, $msg ) = $user->Load($user_id);
+    ok($ret);
+
+    like( $user->Name, qr/anon_/, 'Username replaced with anon name' );
+
+    $user->Load($user_id);
+
+    # Ensure that all other user fields are unset
+    foreach my $attr (@user_identifying_info) {
+        ok( !$user->$attr, 'Attribute ' . $attr . ' is unset' );
+    }
+
+    ok( !$user->HasPassword, 'Password is unset' );
+    # Can't call AuthToken here because it creates new one automatically
+    ok( !$user->_Value('AuthToken'), 'Authtoken is unset' );
+
+    # Test that customfield values are removed with anonymize user action
+    my $customfield = RT::CustomField->new( RT->SystemUser );
+    ( $ret, $msg ) = $customfield->Create(
+        Name       => 'TestCustomfield',
+        LookupType => 'RT::User',
+        Type       => 'FreeformSingle',
+    );
+    ok( $ret, $msg );
+
+    ( $ret, $msg ) = $customfield->AddToObject($user);
+    ok( $ret, "Added CF to user object - " . $msg );
+
+    ( $ret, $msg ) = $user->AddCustomFieldValue(
+        Field => 'TestCustomfield',
+        Value => 'Testing'
+    );
+    ok( $ret, $msg );
+
+    is( $user->FirstCustomFieldValue('TestCustomfield'), 'Testing', 'Customfield exists and has value for user.' );
+
+    $agent->get_ok( $url . "Admin/Users/Modify.html?id=" . $user->id );
+    $agent->follow_link_ok( { text => 'Anonymize User' } );
+
+    $agent->submit_form_ok(
+        {   form_id => 'user-info-modal',
+            fields  => { clear_customfields => 'On' },
+        },
+        "Anonymize user and customfields"
+    );
+
+    is( $user->FirstCustomFieldValue('TestCustomfield'), undef, 'Customfield value cleared' );
+}
+
+# Test replace user
+{
+    my $user = RT::Test->load_or_create_user(
+        Name       => 'user',
+        Password   => 'password',
+        Privileged => 1
+    );
+    ok( $user && $user->id );
+    my $id = $user->id;
+
+    ok( RT::Test->set_rights( { Principal => $user, Right => [qw(SuperUser)] }, ), 'set rights' );
+
+    ok( $agent->logout );
+    ok( $agent->login( 'root' => 'password' ) );
+
+    $agent->get_ok( $url . "Admin/Users/Modify.html?id=" . $user->id );
+    $agent->follow_link_ok( { text => 'Replace User' } );
+
+    $agent->submit_form_ok(
+        {   form_id => 'shredder-search-form',
+            fields  => { WipeoutObject => 'RT::User-' . $user->Name, },
+            button  => 'Wipeout'
+        },
+        "Replace user"
+    );
+
+    my ( $ret, $msg ) = $user->Load($id);
+
+    is( $ret, 0, 'User successfully deleted with replace' );
+}
+
+done_testing();

commit a30efb95bced243a7d47a61329e208476dee5d01
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Mon Dec 17 16:36:51 2018 -0500

    Update download user info tests
    
    Adding Timzeone and ObjectId for transactions requires tests to be
    updated.

diff --git a/t/web/download_user_info.t b/t/web/download_user_info.t
index cc100d686e..44155d49a7 100644
--- a/t/web/download_user_info.t
+++ b/t/web/download_user_info.t
@@ -46,11 +46,11 @@ $agent->login( 'root' => 'password' );
 
     # TSV file for user record information
     $agent->get_ok( $url . "Admin/Users/Modify.html?id=" . $root->id );
-    $agent->follow_link_ok( { text => 'Download User Data' } );
+    $agent->follow_link_ok( { text => 'User Data' } );
 
     my $user_info_tsv = <<EOF;
-id\tName\tEmailAddress\tRealName\tNickName\tOrganization\tHomePhone\tWorkPhone\tMobilePhone\tPagerPhone\tAddress1\tAddress2\tCity\tState\tZip\tCountry\tGecos\tLang\tFreeFormContactInfo
-14\troot\troot\@localhost\tEnoch Root\t\t\t\t\t\t\t\t\t\t\t\t\troot\t\t
+id\tName\tEmailAddress\tRealName\tNickName\tOrganization\tHomePhone\tWorkPhone\tMobilePhone\tPagerPhone\tAddress1\tAddress2\tCity\tState\tZip\tCountry\tGecos\tLang\tTimezone\tFreeFormContactInfo
+14\troot\troot\@localhost\tEnoch Root\t\t\t\t\t\t\t\t\t\t\t\t\troot\t\t\t
 EOF
 
     is $agent->content, $user_info_tsv,
@@ -58,10 +58,10 @@ EOF
 
     # TSV file for Transactions
     $agent->get_ok( $url . "Admin/Users/Modify.html?id=" . $root->id );
-    $agent->follow_link_ok( { text => 'Download User Transaction Data' } );
+    $agent->follow_link_ok( { text => 'User Transactions' } );
 
     my $transaction_info_tsv = <<EOF;
-ObjectId\tid\tCreated\tDescription\tOldValue\tNewValue\tContent
+Ticket Id\tid\tCreated\tDescription\tOldValue\tNewValue\tContent
 1\t30\t$date_created\tTicket created\t\t\tThis transaction appears to have no content
 1\t32\t$date_commented\tComments added\t\t\tTest - Comment
 1\t33\t$date_correspondence\tCorrespondence added\t\t\tTest - Reply
@@ -72,7 +72,7 @@ EOF
 
     # TSV file for user's Tickets
     $agent->get_ok( $url . "Admin/Users/Modify.html?id=" . $root->id );
-    $agent->follow_link_ok( { text => 'Download User Tickets' } );
+    $agent->follow_link_ok( { text => 'User Tickets' } );
 
     my $ticket_info_tsv = <<EOF;
 id\tSubject\tStatus\tQueueName\tOwner\tPriority\tRequestors

commit a488b2f99a4e4687e0c77a8aab4b9e59bd2a7a55
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Wed Jan 2 16:59:18 2019 -0500

    Refine user data button help text

diff --git a/share/html/SelfService/User/Elements/RelatedData b/share/html/SelfService/User/Elements/RelatedData
index acd17bb350..d8353dd5d3 100644
--- a/share/html/SelfService/User/Elements/RelatedData
+++ b/share/html/SelfService/User/Elements/RelatedData
@@ -47,13 +47,22 @@
 %# END BPS TAGGED BLOCK }}}
 <&|/Widgets/TitleBox,
     class => 'user-related-info',
-    title => loc("User related info"),
+    title => loc("Download My Data"),
 &>
 
-<div>
-    <a href="/SelfService/User/RelatedData.tsv?Type=User&id=<% $UserObj->id %>&Filename=UserData.tsv" class="button"><% $UserDataButton %></a>
-    <a href="/SelfService/Search/Results.tsv?Query=Requestor.id=<% $UserObj->id %>&Format=<% $Format | un %>&Filename=UserTicketData.tsv" class="button"><% $UserTicketsButton %></a>
-    <a href="/SelfService/User/RelatedData.tsv?Type=Transaction&id=<% $UserObj->id %>&Filename=UserTransactionData.tsv" class="button"><% $UserTxnButton %></a>
+<div id="download-my-data" class="inline-row">
+    <div class="inline-cell">
+        <a href="/SelfService/User/RelatedData.tsv?Type=User&id=<% $UserObj->id %>&Filename=UserData.tsv" class="button"><% $UserDataButton %></a>
+        <i class="label"><&|/l&>Base user data</&></i>
+    </div>
+    <div class="inline-cell">
+        <a href="/SelfService/Search/Results.tsv?Query=Requestor.id=<% $UserObj->id %>&Format=<% $Format | un %>&Filename=UserTicketData.tsv" class="button"><% $UserTicketsButton %></a>
+        <i class="label"><&|/l&>Tickets with you as a requestor</&></i>
+    </div>
+    <div class="inline-cell">
+        <a href="/SelfService/User/RelatedData.tsv?Type=Transaction&id=<% $UserObj->id %>&Filename=UserTransactionData.tsv" class="button"><% $UserTxnButton %></a>
+        <i class="label"><&|/l&>Replies you sent</&></i>
+    </div>
 </div>
 </&>
 
@@ -63,7 +72,7 @@ my $Format = RT->Config->Get('UserTicketDataResultFormat') || RT->Config->Get('D
 
 <%ARGS>
 $UserObj
-$UserDataButton    => loc( 'Download My Data' )
-$UserTicketsButton => loc( 'Download My Tickets' )
-$UserTxnButton     => loc( 'Download My Transaction Data' )
+$UserDataButton    => loc( 'My Personal Data' )
+$UserTicketsButton => loc( 'My Tickets' )
+$UserTxnButton     => loc( 'My Transactions' )
 </%ARGS>
diff --git a/share/html/User/Elements/RelatedData b/share/html/User/Elements/RelatedData
index caaf0adaf8..08c6f8b72a 100644
--- a/share/html/User/Elements/RelatedData
+++ b/share/html/User/Elements/RelatedData
@@ -55,15 +55,15 @@
     <div class="download-user-data-buttons inline-row">
         <div class="inline-cell">
             <a class="button" href="/User/RelatedData.tsv?Type=User&id=<% $UserObj->id %>&Filename=UserData.tsv"><% $UserDataButton %></a>
-            <i class="label"><&|/l&>The basic user info</&></i>
+            <i class="label"><&|/l&>Core user data</&></i>
         </div>
         <div class="inline-cell">
             <a class="button" href="/Search/Results.tsv?Query=Requestor.id=<% $UserObj->id %>&Format=<% $Format | un %>&Filename=UserTicketData.tsv"><% $UserTicketsButton %></a>
-            <i class="label"><&|/l&>Tickets with the user as a requestor</&></i>
+            <i class="label"><&|/l&>Tickets with this user as a requestor</&></i>
         </div>
         <div class="inline-cell">
             <a class="button" href="/User/RelatedData.tsv?Type=Transaction&id=<% $UserObj->id %>&Filename=UserTransactionData.tsv"><% $UserTxnButton %></a>
-            <i class="label"><&|/l&>Ticket transactions the user created</&></i>
+            <i class="label"><&|/l&>Ticket transactions this user created</&></i>
         </div>
     </div>
 
@@ -72,16 +72,16 @@
     <div class="inline-row">
         <div class="inline-cell">
             <a class="button" href="#user-info-modal" rel="modal:open" name="anonymize_user"><&|/l&>Anonymize User</&></a>
-            <i class="label"><&|/l&>Remove user information with anonymous username</&></i>
+            <i class="label"><&|/l&>Clear core user data, set anonymous username</&></i>
         </div>
 % if ( $session{'CurrentUser'}->HasRight( Object => RT->System, Right => 'SuperUser' ) ) {
         <div class="inline-cell">
             <a class="button" href="<%RT->Config->Get('WebPath')%>/Admin/Tools/Shredder/index.html?Plugin=Users&Users%3Astatus=<% $UserObj->Disabled ? 'disabled' : 'enabled' %>&Users%3Aname=<% $UserObj->Name %>&Users%3Areplace_relations=Nobody&Search=Search" name="replace-user"><&|/l&>Replace User</&></a>
-            <i class="label"><&|/l&>Replace user links in the database with "Nobody" user</&></i>
+            <i class="label"><&|/l&>Replace this user's activity records with "Nobody" user</&></i>
         </div>
         <div class="inline-cell">
-            <a class="button" href="<%RT->Config->Get('WebPath')%>/Admin/Tools/Shredder/index.html?Plugin=Users&Users%3Astatus=<% $UserObj->Disabled ? 'disabled' : 'enabled' %>&Users%3Aname=<% $UserObj->Name %>&Users&Search=Search" name="replace-user"><&|/l&>Replace User</&></a>
-            <i class="label"><&|/l&>Remove all references to user (Tickets linked to this user must be shredded first)</&></i>
+            <a class="button" href="<%RT->Config->Get('WebPath')%>/Admin/Tools/Shredder/index.html?Plugin=Users&Users%3Astatus=<% $UserObj->Disabled ? 'disabled' : 'enabled' %>&Users%3Aname=<% $UserObj->Name %>&Users&Search=Search" name="replace-user"><&|/l&>Delete User</&></a>
+            <i class="label"><&|/l&>Delete this user, tickets associated with this user must be shredded first</&></i>
         </div>
 % }
     </div>
diff --git a/share/static/css/base/admin.css b/share/static/css/base/admin.css
index eaa185c368..fb8a17862d 100644
--- a/share/static/css/base/admin.css
+++ b/share/static/css/base/admin.css
@@ -95,7 +95,7 @@ div.inline-row {
 }
 
 div.inline-row div {
-    max-width: 175px;
+    max-width: 150px;
     display: block;
 }
 

commit 1986cd4e7d2936ac571bde10c59eced5389ce150
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Thu Jan 3 11:19:36 2019 -0500

    Clarify self service download config docs

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 56d16c9f8b..6daff76fd2 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1881,9 +1881,10 @@ Set($SelfServiceRequestUpdatePortlet, 0);
 
 =item C<$SelfServiceDownloadUserData>
 
-Allow Self Service users to download their user information, ticket data
-and transaction data as a .tsv file. When enabled, these three options
-will appear on the /SelfService/Prefs.html page.
+Allow Self Service users to download their user information, ticket data,
+and transaction data as a .tsv file. When enabled, these options
+will appear in the self service interface at Logged in as > Preferences.
+Users also need the ModifySelf right to have access to this page.
 
 =cut
 

-----------------------------------------------------------------------


More information about the rt-commit mailing list