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

Craig Kaiser craig at bestpractical.com
Tue Dec 18 08:30:00 EST 2018


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

- Log -----------------------------------------------------------------
commit 9df047a66f611136287d548beac7a89e7f98db46
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Thu Dec 13 09:10:47 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 d5e331c294..9281661d46 100644
--- a/share/html/Admin/Users/Modify.html
+++ b/share/html/Admin/Users/Modify.html
@@ -125,6 +125,13 @@
 
 <& /Elements/EditCustomFields, Object => $UserObj, Grouping => 'Access control' &>
 
+</&>
+<table width="100%" border="0">
+    <tr><td valign="top" class="boxcontainer">
+            <&| /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>
+    </td></tr>
+</table>
 </&>
 % $m->callback( %ARGS, CallbackName => 'LeftColumnBottom', UserObj => $UserObj );
 </td>
@@ -203,6 +210,7 @@
 
 </table>
 </&>
+<& /User/Elements/BasicUserData, UserObj => $UserObj &>
 <br />
 
 <& /Elements/EditCustomFieldCustomGroupings, Object => $UserObj &>
@@ -211,9 +219,6 @@
 </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,7 +237,6 @@
 % }
 </form>
 
-<& /User/Elements/RelatedData, UserObj => $UserObj &>
 <%INIT>
 
 my $UserObj = RT::User->new($session{'CurrentUser'});
diff --git a/share/html/Prefs/AboutMe.html b/share/html/Prefs/AboutMe.html
index e2e046711f..4aef37295a 100644
--- a/share/html/Prefs/AboutMe.html
+++ b/share/html/Prefs/AboutMe.html
@@ -56,7 +56,7 @@
 
 </form>
 
-<& /User/Elements/RelatedData,
+<& /User/Elements/BasicUserData,
     UserObj           => $UserObj,
     UserDataButton    => loc( 'Download My Data' ),
     UserTicketsButton => loc( 'Download My Tickets' ),
diff --git a/share/html/SelfService/Prefs.html b/share/html/SelfService/Prefs.html
index 19644c9818..3105f5e32f 100644
--- a/share/html/SelfService/Prefs.html
+++ b/share/html/SelfService/Prefs.html
@@ -94,7 +94,7 @@
 % }
 
 % if( RT->Config->Get('SelfServiceDownloadUserData') ) {
-<& /SelfService/User/Elements/RelatedData, UserObj => $user &>
+<& /SelfService/User/Elements/BasicUserData, UserObj => $user &>
 % }
 
 <%INIT>
diff --git a/share/html/SelfService/User/Elements/RelatedData b/share/html/SelfService/User/Elements/RelatedData
index 4ad2469cda..4e9087176d 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/User/BasicUserData.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/BasicUserData.tsv?Type=Transaction&id=<% $UserObj->id %>" class="button"><% $UserTxnButton %></a>
 </div>
 </&>
 
diff --git a/share/html/SelfService/User/RelatedData.tsv b/share/html/SelfService/User/RelatedData.tsv
index e0222e11c4..bb61d6ff5b 100644
--- a/share/html/SelfService/User/RelatedData.tsv
+++ b/share/html/SelfService/User/RelatedData.tsv
@@ -46,5 +46,5 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 <%INIT>
-$m->comp('/User/RelatedData.tsv', %ARGS);
+$m->comp('/User/BasicUserData.tsv', %ARGS);
 </%INIT>
diff --git a/share/html/User/RelatedData.tsv b/share/html/User/BasicUserData.tsv
similarity index 100%
rename from share/html/User/RelatedData.tsv
rename to share/html/User/BasicUserData.tsv
diff --git a/share/html/User/Elements/RelatedData b/share/html/User/Elements/BasicUserData
similarity index 88%
rename from share/html/User/Elements/RelatedData
rename to share/html/User/Elements/BasicUserData
index 051112d969..bc824f5307 100644
--- a/share/html/User/Elements/RelatedData
+++ b/share/html/User/Elements/BasicUserData
@@ -50,11 +50,17 @@
     title => loc("User related info"),
 &>
 
-<div>
-    <a href="/User/RelatedData.tsv?Type=User&id=<% $UserObj->id %>" class="button"><% $UserDataButton %></a>
+<table>
+    <tr><td>
+        <a href="/User/BasicUserData.tsv?Type=User&id=<% $UserObj->id %>" class="button"><% $UserDataButton %></a>
+    </td></tr>
+    <tr><td>
     <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>
-</div>
+    </td></tr>
+    <tr><td>
+    <a href="/User/BasicUserData.tsv?Type=Transaction&id=<% $UserObj->id %>" class="button"><% $UserTxnButton %></a>
+    </td></tr>
+</table>
 </&>
 
 <%INIT>

commit 2fbff4077b551ae43ac3243b1ba528d62f2c7426
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..92830fc3c5 100644
--- a/lib/RT/User.pm
+++ b/lib/RT/User.pm
@@ -281,6 +281,93 @@ sub ValidateName {
     }
 }
 
+=head2 GenerateAnonymousName INT
+
+Generate a random username proceeded by 'anon_' and then a
+random string, Returns the AnonymousName string. The length of the
+random string can be set by providing an integer for character length.
+
+=cut
+
+sub GenerateAnonymousName {
+    my $self = shift;
+    my $length = shift;
+
+    my $invalid = 1;
+    my $name = '';
+
+    while ( $invalid ) {
+        my @Chars = ('a'..'z', 'A'..'Z', '0'..'9');
+        for (1..$length || 9) {
+            $name .= $Chars[int rand @Chars];
+        }
+        $invalid = !$self->ValidateName('anon_' . $name);
+    }
+    return 'anon_' . $name;
+}
+
+=head2 AnonymizeUser { clear_customfields }
+
+Remove all personal identifying information on the user record, but keep
+the user record alive. Additionally replace the username with an anonymous name.
+Submit clear_customfields 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 @user_idenifying_info = qw (
+        Address1 Address2 City Comments Country EmailAddress
+        FreeformContactInfo Gecos HomePhone MobilePhone NickName Organization
+        PagerPhone RealName Signature SMIMECertificate State Timezone WorkPhone Zip
+    );
+
+    $RT::Handle->BeginTransaction();
+    # Remove identifying user information from record
+    foreach my $attr (@user_idenifying_info) {
+        if ( defined $self->$attr && length $self->$attr) {
+                my $method = 'Set' . $attr;
+                my ($ret, $msg) = $self->$method('');
+                RT::Logger->error($msg) unless $ret;
+                return ($ret, $msg) unless $ret;
+        }
+    }
+
+    # Do not do anything if password is already unset
+    if ( $self->HasPassword ) {
+        my ($ret, $msg) = $self->_Set(Field => 'Password', Value => '*NO-PASSWORD*' );
+        RT::Logger->error($msg) unless $ret;
+    }
+
+    # Generate the random anon username
+    my ($ret, $msg) = $self->SetName($self->GenerateAnonymousName);
+    RT::Logger->error($msg) unless $ret;
+
+    # Remove user customfield values
+    if ( $args{'ClearCustomFields'} ) {
+        my $customfields = RT::CustomFields->new(RT->SystemUser);
+        ($ret, $msg) = $customfields->LimitToLookupType('RT::User');
+        RT::Logger->error($msg) unless $ret;
+
+        while (my $customfield = $customfields->Next) {
+            if ( $self->FirstCustomFieldValue( $customfield->Name ) ) {
+                ($ret, $msg) = $self->DeleteCustomFieldValue( Field => $customfield->Id, Value =>  $self->FirstCustomFieldValue( $customfield->Name ) );
+                RT::Logger->error($msg) unless $ret;
+                $RT::Handle->Rollback() unless $ret;
+            }
+        }
+    }
+    $RT::Handle->Commit();
+
+    return(1, 'User successfully anonymized');
+}
+
 =head2 ValidatePassword STRING
 
 Returns either (0, "failure reason") or 1 depending on whether the given

commit b5bd61f46378b96b25616bf31dde2cd2f78bbcfe
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..ed81cb7efd
--- /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"><&|/l, $Cancel &>[_1]</&></a>
+        <button type="Submit" class="button"><&|/l, $Accept &>[_1]</&></button>
+    </form>
+</div>
+
+<%ARGS>
+$Fields   => undef
+$Name     => undef
+$ModalId  => undef
+$Class    => 'modal'
+$Action   => undef
+$Method   => 'GET'
+$Accept   => 'Ok'
+$Cancel   => 'Cancel'
+</%ARGS>

commit d179f129231ba0248080f154d99335ee8921797c
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 6707c1835862cec119a8cd9488e2e5accee88421
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Mon Dec 17 12:10:20 2018 -0500

    Add Timezone to user column map

diff --git a/share/html/Elements/RT__User/ColumnMap b/share/html/Elements/RT__User/ColumnMap
index b4746e39b4..9753b3b29e 100644
--- a/share/html/Elements/RT__User/ColumnMap
+++ b/share/html/Elements/RT__User/ColumnMap
@@ -146,6 +146,10 @@ my $COLUMN_MAP = {
         title     => 'Status', # loc
         value     => sub { return $_[0]->Disabled? $_[0]->loc('Disabled'): $_[0]->loc('Enabled') },
     },
+    Timezone => {
+        title     => 'Timezone', # loc
+        value     => sub { return $_[0]->Timezone },
+    },
 };
 
 </%ONCE>

commit e3228e26314782908fc05f0b05d93c63f5059d21
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Mon Dec 17 15:27:41 2018 -0500

    Set TSV column header based on column maps
    
    Previously the header for a TSV column did not take into
    concideration column map values.

diff --git a/share/html/Elements/TSVExport b/share/html/Elements/TSVExport
index 3f6d5d8136..1d16ec6d74 100644
--- a/share/html/Elements/TSVExport
+++ b/share/html/Elements/TSVExport
@@ -75,8 +75,15 @@ my $col_entry = sub {
     # in tsv output, "#" is often a comment character but we use it for "id"
     delete $col->{title}
         if $col->{title} and $col->{title} =~ /^\s*#\s*$/;
+    my $header = $m->comp(
+            "/Elements/ColumnMap",
+            Name  => $col->{attribute},
+            Attr  => 'title',
+            Class => $Class,
+        );
+    $header = 'id' if $header && $header eq '#';
     return {
-        header => loc($col->{title} || $col->{attribute}),
+        header => $header || $col->{title} || $col->{attribute},
         map    => $m->comp(
             "/Elements/ColumnMap",
             Name  => $col->{attribute},

commit 8837269fb3da1f346991dd0d988517f142aafa7a
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Mon Dec 17 15:26:18 2018 -0500

    Add column map for ObjectIdTicketTitle
    
    ObjectId isn't a useful title for a column because someone
    unfamiliar with RT will not know that the object ID is for
    a RT::Ticket object.

diff --git a/share/html/Elements/RT__Transaction/ColumnMap b/share/html/Elements/RT__Transaction/ColumnMap
index bcf6e229c0..59fc2b0c8a 100644
--- a/share/html/Elements/RT__Transaction/ColumnMap
+++ b/share/html/Elements/RT__Transaction/ColumnMap
@@ -61,6 +61,11 @@ my $COLUMN_MAP = {
         attribute => 'ObjectId',
         value     => sub { return $_[0]->ObjectId() },
     },
+    ObjectIdTicketTitle => {
+        title     => 'Ticket Id', # loc
+        attribute => 'ObjectId',
+        value     => sub { return $_[0]->ObjectId() },
+    },
     Type => {
         title     => 'Type', # loc
         attribute => 'Type',

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

    Update download user data default format

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 17d912e698..4038020986 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>
 
@@ -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, "'__ObjectIdTicketTitle__', '__id__', '__Created__', '__Description__',\
                                         '__OldValue__', '__NewValue__', '__Content__'");
 
 

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

    Create portlet for removing user information
    
    Mason template that has three buttons for removing user information. The
    buttons are 'Anonymize user', 'Replace User' and 'Remove User'.
    Anonymize user will call the 'AnonymizeUser' method to clear identifying
    information from the user record. 'Replace User' and 'Remove User' will
    link to the shredder page with a pre formatted search.

diff --git a/share/html/User/BasicUserData.tsv b/share/html/User/BasicUserData.tsv
index 25804686dd..016912a5e7 100644
--- a/share/html/User/BasicUserData.tsv
+++ b/share/html/User/BasicUserData.tsv
@@ -50,6 +50,7 @@ $PreserveNewLines => 0
 $Type             => 'User'
 $Format           => undef
 $id
+$Filename         => undef
 </%ARGS>
 
 <%INIT>
@@ -81,5 +82,7 @@ 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>
diff --git a/share/html/User/Elements/BasicUserData b/share/html/User/Elements/BasicUserData
index bc824f5307..f533f4064e 100644
--- a/share/html/User/Elements/BasicUserData
+++ b/share/html/User/Elements/BasicUserData
@@ -47,20 +47,32 @@
 %# END BPS TAGGED BLOCK }}}
 <&|/Widgets/TitleBox,
     class => 'user-related-info',
-    title => loc("User related info"),
+    title => loc("Manage User Data"),
 &>
 
-<table>
-    <tr><td>
-        <a href="/User/BasicUserData.tsv?Type=User&id=<% $UserObj->id %>" class="button"><% $UserDataButton %></a>
-    </td></tr>
-    <tr><td>
-    <a href="/Search/Results.tsv?Query=Requestor.id=<% $UserObj->id %>&Format=<% $Format | un %>" class="button"><% $UserTicketsButton %></a>
-    </td></tr>
-    <tr><td>
-    <a href="/User/BasicUserData.tsv?Type=Transaction&id=<% $UserObj->id %>" class="button"><% $UserTxnButton %></a>
-    </td></tr>
-</table>
+<div id="manage-user-data">
+    <div name="download-user-data-title"><b>Download User Information:</b></div>
+    <div name="download-user-data-buttons" class="inline-row">
+        <div class="inline-cell"><a class="button" href="/User/BasicUserData.tsv?Type=User&id=<% $UserObj->id %>&Filename=UserData.tsv"><% $UserDataButton %></a></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></div>
+        <div class="inline-cell"><a class="button" href="/User/BasicUserData.tsv?Type=Transaction&id=<% $UserObj->id %>&Filename=UserTransactionData.tsv"><% $UserTxnButton %></a></div>
+    </div>
+    <div name="remove-user-data-title"><b>Remove User Information:</b></div>
+    <div name="remove-user-data-buttons" 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>
+        <div class="inline-cell">
+            <a class="button" href="<%RT->Config->Get('WebPath')%>/Admin/Tools/Shredder/index.html?Plugin=Users&Users%3Astatus=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=enabled&Users%3Aname=<% $UserObj->Name %>&Search=Search&remove_user" name="remove-user"><&|/l&>Remove User</&></a>
+            <i class="label"><&|/l&>Remove all references to user and links to user</&></i>
+        </div>
+    </div>
+</div>
 </&>
 
 <%INIT>
@@ -69,7 +81,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/misc.css b/share/static/css/base/misc.css
index 25ec20c699..91bb95d56d 100644
--- a/share/static/css/base/misc.css
+++ b/share/static/css/base/misc.css
@@ -127,3 +127,24 @@ td.current-recipients {
     vertical-align: top;
     padding-left: 50px;
 }
+
+.inline-row {
+    margin-bottom: 10px;
+    display: inline-flex;
+    width: 100%;
+}
+
+div.inline-row div {
+    width: 175px;
+    display: block;
+}
+
+div.inline-row a {
+    text-align: center;
+    width: 85%;
+}
+
+div.inline-row i {
+    text-align: left;
+    width: 85%;
+}

commit 7ffb48dfd05ad8c779b292fe9703ae53cdcb89a6
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Thu Dec 13 09:51:34 2018 -0500

    Add remove user info portlet to user modify page

diff --git a/share/html/Admin/Users/Modify.html b/share/html/Admin/Users/Modify.html
index 9281661d46..88c87dc96a 100644
--- a/share/html/Admin/Users/Modify.html
+++ b/share/html/Admin/Users/Modify.html
@@ -237,6 +237,21 @@
 % }
 </form>
 
+% if ( $UserObj->Id ) {
+    <& /Elements/Modal, ModalId => "user-info-modal", Method => 'POST', Action => RT->Config->Get('WebPath') . '/Admin/Users/Modify.html', Fields => [
+    { Label   => "Are you sure you want to anonymize user: ". $UserObj->Name . "?" },
+    { Input => 'Hidden', Value => $UserObj->Id, Name => 'id' },
+    { Input => 'Hidden', Value => 1, Name => 'Anonymize' },
+    {
+        Label    => "Check to clear user customfields:",
+        Input    => 'checkbox',
+        Class    => 'checkbox',
+        Name     => 'clear_customfields',
+        Value    => 'On',
+    },
+]
+&>
+% }
 <%INIT>
 
 my $UserObj = RT::User->new($session{'CurrentUser'});
@@ -300,6 +315,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(

commit 01c518d0002af101af1360cab449852a6083e91b
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..cfb95a3a15
--- /dev/null
+++ b/t/web/remove_user_info.t
@@ -0,0 +1,156 @@
+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 $user = RT::Test->load_or_create_user( Name => 'Test User' );
+    ok $user && $user->id;
+
+    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" );
+
+    $user->Load($user_id);
+    is $user->EmailAddress, '', 'User Email removed';
+
+# UserId is still the same, but all other records should be anonimyzed for TestUser
+    my ( $ret, $msg ) = $user->Load($user_id);
+    ok $ret;
+
+    is $user->Name =~ /anon_/, 1, 'Username replaced with anon name';
+
+    my @user_idenifying_info = qw (
+        Address1 Address2 City Comments Country EmailAddress
+        FreeformContactInfo Gecos HomePhone MobilePhone NickName Organization
+        PagerPhone RealName Signature SMIMECertificate State Timezone WorkPhone Zip
+        );
+    $user->Load($user_id);
+
+    # Ensure that all other user fields are blank
+    foreach my $attr (@user_idenifying_info) {
+        my $check = grep { not defined $_ or $_ eq '' or $_ eq 0 } $user->$attr;
+        is $check, 1, 'Attribute ' . $attr . ' is blank';
+    }
+
+    # 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;
+
+    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($user->Id);
+
+    is $ret, 0,
+        'User successfully deleted with replace';
+}
+
+# Test Remove user
+{
+    my $user = RT::Test->load_or_create_user(
+        Name       => 'user',
+        Password   => 'password',
+        Privileged => 1
+    );
+    ok $user && $user->id;
+
+    ok( RT::Test->set_rights(
+            { Principal => $user, Right => [qw(SuperUser)] },
+        ),
+        'set rights'
+      );
+
+    $agent->logout;
+    $agent->login( 'root' => 'password' );
+
+    $agent->get_ok( $url . "Admin/Users/Modify.html?id=" . $user->id );
+    $agent->follow_link_ok( { text => 'Remove User' } );
+
+    $agent->submit_form_ok(
+        {   form_id => 'shredder-search-form',
+            fields  => { WipeoutObject => 'RT::User-' . $user->Name, },
+            button  => 'Wipeout'
+        },
+        "Remove user"
+    );
+
+    my ($ret, $msg) = $user->Load($user->Id);
+
+    is $ret, 0,
+        'User successfully deleted with remove';
+}
+
+done_testing();

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

    Update download user info tests
    
    Changes to how the column header for the TSV file means the
    tests need to be updated accordingly.

diff --git a/t/web/download_user_info.t b/t/web/download_user_info.t
index cc100d686e..ea7d03a629 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\tEmail Address\tReal Name\tNickname\tOrganization\tHome Phone\tWork Phone\tMobile Phone\tPager Phone\tAddress\tAddress 2\tCity\tState\tZip\tCountry\tUnix login\tLanguage\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\tOld Value\tNew Value\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,10 +72,10 @@ 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
+id\tSubject\tStatus\tQueue\tOwner\tPriority\tRequestor
 1\tTest\topen\tGeneral\tNobody in particular\t0\troot (Enoch Root)
 EOF
 

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


More information about the rt-commit mailing list