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

? sunnavy sunnavy at bestpractical.com
Thu Jan 3 15:58:10 EST 2019


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

- Log -----------------------------------------------------------------
commit e99a944de4ad68f3c07457ba924eb2aae85e0968
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 e2e046711..df90996e8 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 35526ba1fbb7d2a8729fb1b56bf49cf39c12fd8c
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 df7105f79..3f6d5d813 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 cb18fea70ab110389e74de22f1653301ec6d0204
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 1bbdded74..ad536944d 100644
--- a/share/html/Search/Results.tsv
+++ b/share/html/Search/Results.tsv
@@ -51,6 +51,7 @@ $Query => ''
 $OrderBy => 'id'
 $Order => 'ASC'
 $PreserveNewLines => 0
+$UserData => 0
 </%ARGS>
 <%INIT>
 my $Tickets = RT::Tickets->new( $session{'CurrentUser'} );
@@ -68,5 +69,6 @@ else {
     $Tickets->OrderBy( FIELD => $OrderBy, ORDER => $Order );
 }
 
-$m->comp( "/Elements/TSVExport", Collection => $Tickets, Format => $Format, PreserveNewLines => $PreserveNewLines );
+my $filename = $UserData ? 'UserTicketData.tsv' : undef;
+$m->comp( "/Elements/TSVExport", Collection => $Tickets, Format => $Format, PreserveNewLines => $PreserveNewLines, Filename => $filename );
 </%INIT>
diff --git a/share/html/User/Elements/RelatedData b/share/html/User/Elements/RelatedData
index 051112d96..cad301c71 100644
--- a/share/html/User/Elements/RelatedData
+++ b/share/html/User/Elements/RelatedData
@@ -52,7 +52,7 @@
 
 <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="/Search/Results.tsv?UserData=1&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>
 </div>
 </&>
diff --git a/share/html/User/RelatedData.tsv b/share/html/User/RelatedData.tsv
index 25804686d..e6e79d0ac 100644
--- a/share/html/User/RelatedData.tsv
+++ b/share/html/User/RelatedData.tsv
@@ -63,15 +63,18 @@ if ( $session{'CurrentUser'}->id ne $id ) {
 }
 
 my $Collection;
+my $filename;
 
 if ( $Type eq 'User' ) {
     $Format = RT->Config->Get('UserDataResultFormat') unless $Format;
+    $filename = 'UserData.tsv';
 
     $Collection = RT::Users->new( $session{'CurrentUser'} );
     $Collection->Limit( FIELD => 'id', VALUE => $id );
 
 } elsif ( $Type eq 'Transaction' ) {
     $Format = RT->Config->Get('UserTransactionDataResultFormat') unless $Format;
+    $filename = 'UserTransactionData.tsv';
 
     $Collection = RT::Transactions->new( $session{'CurrentUser'} );
     $Collection->Limit( FIELD => 'ObjectType', VALUE => 'RT::Ticket' );
@@ -81,5 +84,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 e3791e538d80745b2bc3dba29036ec8d5e2b11f5
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 17d912e69..6ab8463bb 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 b4746e39b..1e6d268c1 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 d54dc8271168f69754fec9c34e312a6289c553aa
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 6ab8463bb..56d16c9f8 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 6ae003231889ba46e5b7a861dc2d356a4e4f6d48
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 d5e331c29..ef06d3d38 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 bf81b7d00121169f633bb13d3652c147dc747bb7
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 ef06d3d38..4b1181fd2 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 788e69534fa06b1b0142af110134a6da2e8985f3
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 daae994e7..2d6a8cb35 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 693123a9c38835f97faadf99398e6eb44ee4b4c2
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 000000000..2c36e8af0
--- /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 507ca03b158608869ae6b363750100af52a7dc47
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.

diff --git a/share/html/Admin/Users/Modify.html b/share/html/Admin/Users/Modify.html
index 4b1181fd2..569b26316 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 cad301c71..4d1ed31e4 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 %>" class="button"><% $UserDataButton %></a>
-    <a href="/Search/Results.tsv?UserData=1&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>
+<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 %>"><% $UserDataButton %></a>
+            <i class="label"><&|/l&>The basic user info</&></i>
+        </div>
+        <div class="inline-cell">
+            <a class="button" href="/Search/Results.tsv?UserData=1&Query=Requestor.id=<% $UserObj->id %>&Format=<% $Format | un %>"><% $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 %>"><% $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 95c587803..eaa185c36 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 b2b3da57bd3481e8ab29d1a4fab5572a6862d3bc
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 000000000..4003f32d9
--- /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 a032377e69ad8684ea8b17eef1ed5ccb90ad47ed
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/share/html/SelfService/User/Elements/RelatedData b/share/html/SelfService/User/Elements/RelatedData
index 4ad2469cd..a58c7070c 100644
--- a/share/html/SelfService/User/Elements/RelatedData
+++ b/share/html/SelfService/User/Elements/RelatedData
@@ -47,13 +47,23 @@
 %# 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 %>" 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>
+<div id="download-my-data" class="inline-row">
+    <div class="inline-cell">
+        <a href="/SelfService/User/RelatedData.tsv?Type=User&id=<% $UserObj->id %>" class="button"><% $UserDataButton %></a>
+        <i class="label"><&|/l&>Base user data</&></i>
+    </div>
+    <div class="inline-cell">
+        <a href="/SelfService/Search/Results.tsv?UserData=1&Query=Requestor.id=<% $UserObj->id %>&Format=<% $Format | un %>" 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 %>" class="button"><% $UserTxnButton %></a>
+        <i class="label"><&|/l&>Replies you sent</&></i>
+    </div>
 </div>
 </&>
 
@@ -63,7 +73,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 4d1ed31e4..e56f8e97c 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 %>"><% $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?UserData=1&Query=Requestor.id=<% $UserObj->id %>&Format=<% $Format | un %>"><% $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 %>"><% $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 eaa185c36..fb8a17862 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;
 }
 
diff --git a/t/web/download_user_info.t b/t/web/download_user_info.t
index cc100d686..44155d49a 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 374b6879d1a7f601d775fe3f8fb36412b1f82e84
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 56d16c9f8..6daff76fd 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