[Rt-commit] rt branch, 4.4/remove-user-info, created. rt-4.4.2-241-g4a61058d4

Craig Kaiser craig at bestpractical.com
Tue Jun 12 17:54:49 EDT 2018


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

- Log -----------------------------------------------------------------
commit 92aa5d684dec6eaf423356e92b82e3e01b648241
Author: craig Kaiser <craig at bestpractical.com>
Date:   Mon Apr 23 09:37:18 2018 -0400

    Add keyboard shortcuts for reply and comment
    
    If the user is on a ticket page where the action link for reply or
    comment is available, then they should be able to use the keyboard
    shortcuts 'r' and 'c'.

diff --git a/share/html/Elements/ShortcutHelp b/share/html/Elements/ShortcutHelp
index d3e18e0cd..12d60f934 100644
--- a/share/html/Elements/ShortcutHelp
+++ b/share/html/Elements/ShortcutHelp
@@ -46,8 +46,10 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 <%args>
-$show_search      => 0
-$show_bulk_update => 0
+$show_bulk_update     => 0
+$show_search          => 0
+$show_ticket_reply    => 0
+$show_ticket_comment  => 0
 </%args>
 
 <div class="keyboard-shortcuts">
@@ -115,4 +117,29 @@ $show_bulk_update => 0
 
 % }
 
+% if ($show_ticket_reply || $show_ticket_comment) {
+    <div class="titlebox">
+        <div class="titlebox-title">
+            <span class="left"><&|/l&>Ticket</&></span>
+            <span class="right-empty"></span>
+        </div>
+        <div class="titlebox-content">
+            <hr class="clear">
+            <table>
+%   if ( $show_ticket_reply ) {
+                <tr>
+                    <td class="key-column"><span class="keyboard-shortcuts-key">r</span></td>
+                    <td><&|/l&>Reply to current ticket</&></td>
+                </tr>
+%   }
+%   if ( $show_ticket_comment ) {
+                <tr>
+                    <td class="key-column"><span class="keyboard-shortcuts-key">c</span></td>
+                    <td><&|/l&>Comment on current ticket</&></td>
+                </tr>
+%   }
+            </table>
+        </div>
+    </div>
+% }
 </div>
diff --git a/share/html/Helpers/ShortcutHelp b/share/html/Helpers/ShortcutHelp
index 45f4e3e77..1bfad2c78 100644
--- a/share/html/Helpers/ShortcutHelp
+++ b/share/html/Helpers/ShortcutHelp
@@ -46,8 +46,10 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 <%args>
-$show_search      => 0
-$show_bulk_update => 0
+$show_search         => 0
+$show_bulk_update    => 0
+$show_ticket_reply   => 0
+$show_ticket_comment => 0
 </%args>
 <& /Elements/ShortcutHelp, %ARGS &>
 % $m->abort;
diff --git a/share/html/SelfService/Helpers/ShortcutHelp b/share/html/SelfService/Helpers/ShortcutHelp
index 45f4e3e77..167c141a2 100644
--- a/share/html/SelfService/Helpers/ShortcutHelp
+++ b/share/html/SelfService/Helpers/ShortcutHelp
@@ -48,6 +48,8 @@
 <%args>
 $show_search      => 0
 $show_bulk_update => 0
+$show_ticket_reply => 0
+$show_ticket_comment => 0
 </%args>
 <& /Elements/ShortcutHelp, %ARGS &>
 % $m->abort;
diff --git a/share/static/js/keyboard-shortcuts.js b/share/static/js/keyboard-shortcuts.js
index a7b4cf54f..22dc962bb 100644
--- a/share/static/js/keyboard-shortcuts.js
+++ b/share/static/js/keyboard-shortcuts.js
@@ -34,10 +34,14 @@ jQuery(function() {
 
         var is_search = jQuery('body#comp-Search-Results').length > 0;
         var is_bulk_update = jQuery('body#comp-Search-Bulk').length > 0;
+        var is_ticket_reply = jQuery('a#page-actions-reply').length > 0;
+        var is_ticket_comment = jQuery('a#page-actions-comment').length > 0;
 
         var url = RT.Config.WebHomePath + '/Helpers/ShortcutHelp' +
                   '?show_search=' + ( is_search || is_bulk_update ? '1' : '0' ) +
-                  '&show_bulk_update=' + ( is_bulk_update ? '1' : '0' );
+                  '&show_bulk_update=' + ( is_bulk_update ? '1' : '0' ) +
+                  '&show_ticket_reply=' + ( is_ticket_reply ? '1' : '0' ) +
+                  '&show_ticket_comment=' + ( is_ticket_comment ? '1' : '0' );
 
         jQuery.ajax({
             url: url,
@@ -151,3 +155,22 @@ jQuery(function() {
     Mousetrap.bind('x', toggleTicketCheckbox);
 });
 
+jQuery(function() {
+    // Only load these shortcuts if reply or comment action is on page
+    var ticket_reply = jQuery('a#page-actions-reply');
+    var ticket_comment = jQuery('a#page-actions-comment');
+    if (!ticket_reply.length && !ticket_comment.length) return;
+
+    var replyToTicket = function() {
+        if (!ticket_reply.length) return;
+        window.location.href = ticket_reply.attr('href');
+    };
+
+    var commentOnTicket = function() {
+        if (!ticket_comment.length) return;
+        window.location.href = ticket_comment.attr('href');
+    };
+
+    Mousetrap.bind('r', replyToTicket);
+    Mousetrap.bind('c', commentOnTicket);
+});

commit b6b2777f92dabafd5862db197e4faddb984d6680
Author: Maureen E. Mirville <maureen at bestpractical.com>
Date:   Mon Apr 9 16:15:29 2018 -0400

    Allow rt-setup-fulltext-index to prompt for dba password

diff --git a/sbin/rt-setup-fulltext-index.in b/sbin/rt-setup-fulltext-index.in
index 5cab8f632..76da05dcd 100644
--- a/sbin/rt-setup-fulltext-index.in
+++ b/sbin/rt-setup-fulltext-index.in
@@ -49,6 +49,7 @@
 use strict;
 use warnings;
 no warnings 'once';
+use Term::ReadKey;
 
 # fix lib paths, some may be relative
 BEGIN { # BEGIN RT CMD BOILERPLATE
@@ -650,6 +651,17 @@ sub dba_handle {
         $ENV{'NLS_NCHAR'} = "AL32UTF8";
     }
     my $dsn = do { my $h = new RT::Handle; $h->BuildDSN; $h->DSN };
+    my $password;
+    if ( defined $DB{'admin_password'} || defined $ENV{'RT_DBA_PASSWORD'} ) {
+        $password = $DB{'admin_password'} // $ENV{'RT_DBA_PASSWORD'};
+    } else {
+        print "Please enter $DB{'type'} admin password: ";
+        ReadMode('noecho');
+        chomp($password = ReadLine(0));
+        ReadMode('normal');
+        print "\n";
+    }
+    $DB{'admin_password'} = $password;
     my $dbh = DBI->connect(
         $dsn, $DB{admin}, $DB{admin_password},
         { RaiseError => 1, PrintError => 1 },

commit 0c8b881900632dcf1d7855339dd6f7695e335f5b
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Mon May 21 11:01:48 2018 -0400

    Fix typo in POD

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index a38b14718..e8a4545de 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -928,7 +928,7 @@ sub GetWebURLFromRequest {
 
 =head2 Redirect URL
 
-This routine ells the current user's browser to redirect to URL.  
+This routine tells the current user's browser to redirect to URL.  
 Additionally, it unties the user's currently active session, helping to avoid 
 A bug in Apache::Session 1.81 and earlier which clobbers sessions if we try to use 
 a cached DBI statement handle twice at the same time.

commit d8f98d488520b33f5639de2418ce9d123a5eca6a
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Wed Jun 6 10:00:01 2018 -0400

    Add GenerateAnonymousName function for RT::User
    
    The GenerateAnonymousName function will return a random valid (unique)
    username with the format 'anon_1234abcd' where after 'anon_' is a random
    string.

diff --git a/lib/RT/User.pm b/lib/RT/User.pm
index ca47377cf..17e2d5b67 100644
--- a/lib/RT/User.pm
+++ b/lib/RT/User.pm
@@ -281,6 +281,31 @@ 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', '1'..'9');
+        for (1..$length || 9) {
+            $name .= $Chars[int rand @Chars];
+        }
+        $invalid = !$self->ValidateName('anon_' . $name);
+    }
+    return 'anon_' . $name;
+}
+
 =head2 ValidatePassword STRING
 
 Returns either (0, "failure reason") or 1 depending on whether the given

commit 628af98ad2d66d7516a820423701d2d5a564fb9d
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Wed Jun 6 14:13:09 2018 -0400

    Create method AnonymizeUser in User.pm
    
    Create a subroutine '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 17e2d5b67..fc7287fe9 100644
--- a/lib/RT/User.pm
+++ b/lib/RT/User.pm
@@ -306,6 +306,62 @@ sub GenerateAnonymousName {
     return 'anon_' . $name;
 }
 
+=head2 AnonymizeUser { clear_customfields }
+
+Remove all personal identifying information on the user record, but keep
+the user record alive. Additonally 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 = (
+        clear_customfields  => 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
+    );
+
+    # Remove identifying user information from record
+    foreach my $attr (@user_idenifying_info) {
+        if ( length $self->$attr or !defined $self->$attr) {
+                my $method = 'Set' . $attr;
+                my ($ret, $msg) = $self->$method('');
+                return ($ret, $msg) unless $ret;
+        }
+    }
+
+    # Do not do anything if password is already unset
+    if ( $self->HasPassword ) {
+        my ($ret, $msg) = $self->SetPassword('*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{'clear_customfields'} ) {
+        my $customfields = RT::CustomFields->new(RT->SystemUser);
+        ($ret, $msg) = $customfields->LimitToLookupType('RT::User');
+        RT::Logger->error($msg) unless $ret;
+
+        while (my $customfield = $customfields->Next) {
+            ($ret, $msg) = $self->AddCustomFieldValue(Field => $customfield->Name, Value => '');
+            RT::Logger->error($msg) unless $ret;
+        }
+    }
+
+    return(1, 'User successfully anonymized');
+}
+
 =head2 ValidatePassword STRING
 
 Returns either (0, "failure reason") or 1 depending on whether the given

commit ab756f37347ca7f610303a785d23f7ac8a1dad0c
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Wed Jun 6 10:00:35 2018 -0400

    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'.

diff --git a/share/html/Elements/UserRelatedInfo b/share/html/Elements/UserRelatedInfo
new file mode 100644
index 000000000..30e3b5275
--- /dev/null
+++ b/share/html/Elements/UserRelatedInfo
@@ -0,0 +1,78 @@
+%# 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 class="modal" id="user-info-modal" align="center">
+    <form name="anonymize_user_form" method="GET" action="/Admin/Users/Modify.html">
+        <p>Are you sure you want to anonymize user: <% $UserObj->Name %>?</p>
+        <p>
+           Check to clear user customfields:
+            <input name="clear_customfields" type="checkbox" class="checkbox">
+        </p>
+
+        <button type="submit" class="button">Ok</button>
+        <a href="#" rel="modal:close" class="button">Cancel</a>
+        <input type="hidden" name="id" value="<% $UserObj->id %>">
+        <input type="hidden" name="Anonymize" value=1>
+    </form>
+</div>
+<&|/Widgets/TitleBox,
+    class => 'user-related-info',
+    title => loc("User related info"),
+&>
+
+<div>
+    <div>
+        <a href="#user-info-modal" rel="modal:open" class="button" name="anonymize_user">Anonymize User</a>
+        <a href="/Admin/Tools/Shredder/index.html?Plugin=Users&Users%3Astatus=enabled&Users%3Aname=<% $UserObj->Name %>&Users%3Areplace_relations=nobody&Search=Search" name="replace-user" class="button">Replace User Information</a>
+        <a href="/Admin/Tools/Shredder/index.html?Plugin=Users&Users%3Astatus=enabled&Users%3Aname=<% $UserObj->Name %>&Search=Search&remove_user" class="button" name="remove-user">Remove User Information</a>
+    </div>
+</div>
+</&>
+
+<%ARGS>
+$UserObj   => undef
+</%ARGS>

commit ca2428c8103db4752767b225d81151c1eae564b7
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Thu Jun 7 11:44:45 2018 -0400

    Add portlet to user admin modify page

diff --git a/share/html/Admin/Users/Modify.html b/share/html/Admin/Users/Modify.html
index 3b8bc42fc..694150032 100644
--- a/share/html/Admin/Users/Modify.html
+++ b/share/html/Admin/Users/Modify.html
@@ -232,6 +232,8 @@
 % }
 </form>
 
+<& /Elements/UserRelatedInfo, UserObj => $UserObj &>
+
 <%INIT>
 
 my $UserObj = RT::User->new($session{'CurrentUser'});
@@ -308,6 +310,11 @@ if ( $UserObj->Id ) {
     }
 }
 
+if ( $ARGS{'Anonymize'} ) {
+    my ($ret, $msg) = $UserObj->AnonymizeUser(clear_customfields => $ARGS{'clear_customfields'});
+    RT::Logger->error($msg) unless $ret;
+}
+
 # This code does automatic redirection if any updates happen.
 MaybeRedirectForResults(
     Actions   => \@results,

commit 4a61058d4b757fafb01ac3384dfb67555b89e6ba
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Wed Jun 6 14:09:24 2018 -0400

    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..ce0f670d7
--- /dev/null
+++ b/t/web/remove_user_info.t
@@ -0,0 +1,118 @@
+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');
+ok $agent->login, 'logged in';
+
+# Anonymize User
+{
+    my $TestUser = RT::Test->load_or_create_user( Name => 'TestUser', EmailAddress => 'test at example.com' );
+    ok $TestUser && $TestUser->id;
+
+    my $user_id = $TestUser->id;
+
+    $agent->get_ok($url . "Admin/Users/Modify.html?id=" . $user_id . '&Anonymize=1');
+
+    is $TestUser->ValidateEmail('test at example.com'), 1, 'User Email removed';
+    is $TestUser->Load('TestUser'), 0, 'Username anonymized';
+
+    # UserId is still the same, but all other records should be anonimyzed for TestUser
+    my ($ret, $msg) = $TestUser->Load( $user_id );
+    ok $ret;
+
+    is $TestUser->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
+    );
+
+    # Ensure that all other user fields are blank
+    foreach my $attr (@user_idenifying_info) {
+        is $TestUser->$attr, '', 'Attribute ' . $attr . ' is blank';
+    }
+}
+
+# Test that customfield values are removed with anonymize user action
+{
+    # Create customfield
+    my $customfield = RT::CustomField->new(RT->SystemUser);
+    my ($ret, $msg) = $customfield->Create(
+        Name        => 'TestCustomfield',
+        LookupType  => 'RT::User',
+        Type        => 'FreeformSingle',
+    );
+    ok $ret, $msg;
+
+    my $TestUser = RT::Test->load_or_create_user( Name => 'TestUser' );
+    ok $TestUser && $TestUser->id;
+
+    my $user_id = $TestUser->id;
+
+    ($ret, $msg) = $customfield->AddToObject( $TestUser );
+    ok( $ret, "Added CF to user object - " . $msg);
+
+    ($ret, $msg) = $TestUser->AddCustomFieldValue(Field => 'TestCustomfield', Value => 'Testing');
+    ok $ret, $msg;
+
+    is $TestUser->FirstCustomFieldValue('TestCustomfield'), 'Testing', 'Customfield exists and has value for user.';
+
+    $agent->get_ok($url ."Admin/Users/Modify.html?id=" . $TestUser->id);
+
+    $agent->field( 'clear_customfields' => 'on' );
+
+    $agent->submit_form_ok({ form_name => 'anonymize_user_form', with_fields => { clear_customfields => 'on' } });
+
+    is $TestUser->FirstCustomFieldValue('TestCustomfield'), undef, 'Customfield value cleared';
+}
+
+{
+    # Test replace user
+    my $TestUser = RT::Test->load_or_create_user( Name => 'TestUser' );
+    ok $TestUser && $TestUser->id;
+
+    my $user_id = $TestUser->id;
+
+    $agent->get_ok($url ."Admin/Users/Modify.html?id=" . $TestUser->id);
+
+    $agent->follow_link_ok({ url_regex => qr/replace_relations/i });
+
+    $agent->form_id('shredder-search-form');
+    $agent->current_form->find_input('WipeoutObject')->check;
+    $agent->click_button( name => 'Wipeout' );
+
+    # TestUser should not exist
+    is $TestUser->Load($user_id), 0, 'User successfully deleted';
+}
+
+{
+    # Test Remove user
+    my $TestUser = RT::Test->load_or_create_user( Name => 'TestUser' );
+    ok $TestUser && $TestUser->id;
+
+    my $user_id = $TestUser->id;
+
+    $agent->get_ok($url ."Admin/Users/Modify.html?id=" . $TestUser->id);
+
+    $agent->follow_link_ok({ url_regex => qr/remove_user/i });
+
+    $agent->form_id('shredder-search-form');
+    $agent->current_form->find_input('WipeoutObject')->check;
+    $agent->click_button( name => 'Wipeout' );
+
+    # TestUser should not exist
+    is $TestUser->Load($user_id), 0, 'User successfully deleted';
+}
+
+done_testing();

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


More information about the rt-commit mailing list