From 3e89b362828f0c6b3da04ac59c0b63045c817bae Mon Sep 17 00:00:00 2001 From: Brad Embree Date: Mon, 1 May 2023 20:03:40 -0700 Subject: [PATCH] Add script to email users expiring auth tokens --- .gitignore | 1 + Makefile.in | 1 + configure.ac | 1 + etc/initialdata | 27 +++ etc/upgrade/5.0.5/content | 32 +++ sbin/rt-email-expiring-auth-tokens.in | 283 ++++++++++++++++++++++++++ 6 files changed, 345 insertions(+) create mode 100644 etc/upgrade/5.0.5/content create mode 100644 sbin/rt-email-expiring-auth-tokens.in diff --git a/.gitignore b/.gitignore index 2d66e9bda43..f07ed8350a4 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ /sbin/rt-dump-metadata /sbin/rt-email-dashboards /sbin/rt-email-digest +/sbin/rt-email-expiring-auth-tokens /sbin/rt-email-group-admin /sbin/rt-externalize-attachments /sbin/rt-fulltext-indexer diff --git a/Makefile.in b/Makefile.in index 515621aad4b..74506a9495c 100644 --- a/Makefile.in +++ b/Makefile.in @@ -146,6 +146,7 @@ SYSTEM_BINARIES = rt-attributes-viewer \ rt-dump-metadata \ rt-email-dashboards \ rt-email-digest \ + rt-email-expiring-auth-tokens \ rt-email-group-admin \ rt-externalize-attachments \ rt-fulltext-indexer \ diff --git a/configure.ac b/configure.ac index 53e5cfada49..dd346743645 100755 --- a/configure.ac +++ b/configure.ac @@ -473,6 +473,7 @@ AC_CONFIG_FILES([ sbin/rt-test-dependencies sbin/rt-email-digest sbin/rt-email-dashboards + sbin/rt-email-expiring-auth-tokens sbin/rt-externalize-attachments sbin/rt-clean-attributes sbin/rt-clean-sessions diff --git a/etc/initialdata b/etc/initialdata index 32da7e63e72..185616ad5e2 100644 --- a/etc/initialdata +++ b/etc/initialdata @@ -778,6 +778,33 @@ Hour: { $SubscriptionObj->SubValue('Hour') } } } }, + { + Queue => '0', + Name => 'Auth tokens expiring in 7 days', # loc + Description => 'Auth tokens expiring in 7 days', # loc + Content => q[Subject: [{RT->Config->Get('rtname')}] Your have auth tokens that will expire in 7 days + +Hello { $UserObj->RealName || $UserObj->Name }: + +Your following auth tokens are going to expire in 7 days: + +{ + for my $token (@AuthTokens) { + $OUT .= " * " . $token->Description . " (expires at " . $token->ExpiresObj->AsString . ")\n"; + } + + if ( $UserObj->HasRight( Right => 'ModifySelf', Object => RT->System ) + && $UserObj->HasRight( Right => 'ManageAuthTokens', Object => RT->System ) ) + { + $OUT .= "\nYou can revoke them and generate new ones on " . RT->Config->Get('WebURL') . 'Prefs/AuthTokens.html' + } + else { + $OUT .= "\nIf you are still using them, please contact your RT manager to generate new ones for you."; + } +} + +], + }, ); @Scrips = ( diff --git a/etc/upgrade/5.0.5/content b/etc/upgrade/5.0.5/content new file mode 100644 index 00000000000..01e2be8b372 --- /dev/null +++ b/etc/upgrade/5.0.5/content @@ -0,0 +1,32 @@ +use strict; +use warnings; + +our @Templates = ( + { + Queue => '0', + Name => 'Auth tokens expiring in 7 days', # loc + Description => 'Auth tokens expiring in 7 days', # loc + Content => q[Subject: [{RT->Config->Get('rtname')}] You have auth tokens that will expire in 7 days + +Hello { $UserObj->RealName || $UserObj->Name }: + +Your following auth tokens are going to expire in 7 days: + +{ + for my $token (@AuthTokens) { + $OUT .= " * " . $token->Description . " (expires at " . $token->ExpiresObj->AsString . ")\n"; + } + + if ( $UserObj->HasRight( Right => 'ModifySelf', Object => RT->System ) + && $UserObj->HasRight( Right => 'ManageAuthTokens', Object => RT->System ) ) + { + $OUT .= "\nYou can revoke them and generate new ones on " . RT->Config->Get('WebURL') . 'Prefs/AuthTokens.html' + } + else { + $OUT .= "\nIf you are still using them, please contact your RT manager to generate new ones for you."; + } +} + +], + }, +); diff --git a/sbin/rt-email-expiring-auth-tokens.in b/sbin/rt-email-expiring-auth-tokens.in new file mode 100644 index 00000000000..30ce05fcc1e --- /dev/null +++ b/sbin/rt-email-expiring-auth-tokens.in @@ -0,0 +1,283 @@ +#!@PERL@ +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2023 Best Practical Solutions, LLC +# +# +# (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 }}} +use warnings; +use strict; + +BEGIN { # BEGIN RT CMD BOILERPLATE + require File::Spec; + require Cwd; + my @libs = ( "@RT_LIB_PATH@", "@LOCAL_LIB_PATH@" ); + my $bin_path; + + for my $lib (@libs) { + unless ( File::Spec->file_name_is_absolute($lib) ) { + $bin_path ||= ( File::Spec->splitpath( Cwd::abs_path(__FILE__) ) )[1]; + $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); + } + unshift @INC, $lib; + } + +} + +use RT; +use RT::Interface::CLI qw(Init); +use RT::Interface::Email; + + +my ( $expires_by, $expires_on, $print, $help, $template ); +my %opt = ( + 'expires-by=s' => \$expires_by, + 'expires-on=s' => \$expires_on, + 'template=s' => \$template, + 'print' => \$print, + 'help' => \$help, +); +Init( %opt ); + +if ( $help ) { + Pod::Usage::pod2usage({ verbose => 2}); + exit; +} + +if ( $expires_by || $expires_on ) { + if ( $expires_by && $expires_on ) { + Pod::Usage::pod2usage( { message => "Cannot use both expires-by and expires-on parameters" } ); + } +} +else { + Pod::Usage::pod2usage( { message => "One of expires-by or expires-on parameter is required" } ); +} + +if ( !$template ) { + Pod::Usage::pod2usage( { message => "template parameter is required" } ); +} + +my $template_obj = RT::Template->new( RT->SystemUser ); +my ( $ret, $msg ) = $template_obj->Load($template); +unless ($ret) { + print "Could not load template $template"; + exit 1; +} + +my $auth_tokens = RT::AuthTokens->new( RT->SystemUser ); + +if ($expires_by) { + my $expires_by_date = RT::Date->new( RT->SystemUser ); + usage("Invalid date parameter '$expires_by'") + unless $expires_by_date->Set( Format => 'unknown', Value => $expires_by, Timezone => 'server' ) > 0; + + $expires_by_date->SetToMidnight( Timezone => 'server' ); + + $auth_tokens->Limit( + FIELD => 'Expires', + VALUE => $expires_by_date->ISO( Timezone => 'UTC' ), + OPERATOR => '<', + ENTRYAGGREGATOR => 'AND', + ); + + my $today = RT::Date->new( RT->SystemUser ); + $today->SetToNow; + $today->SetToMidnight( Timezone => 'server' ); + $auth_tokens->Limit( + FIELD => 'Expires', + VALUE => $today->ISO( Timezone => 'UTC' ), + OPERATOR => '>=', + ENTRYAGGREGATOR => 'AND', + ); +} +elsif ($expires_on) { + my $expires_on_start_date = RT::Date->new( RT->SystemUser ); + my $expires_on_end_date = RT::Date->new( RT->SystemUser ); + usage("Invalid date parameter '$expires_on'") + unless $expires_on_start_date->Set( Format => 'unknown', Value => $expires_on, Timezone => 'server' ) > 0; + + $expires_on_start_date->SetToMidnight( Timezone => 'server' ); + $expires_on_end_date->Set( Format => 'unix', Value => $expires_on_start_date->Unix ); + $expires_on_end_date->AddDay; + + $auth_tokens->Limit( + FIELD => 'Expires', + VALUE => $expires_on_start_date->ISO( Timezone => 'UTC' ), + OPERATOR => '>=', + ENTRYAGGREGATOR => 'AND', + ); + $auth_tokens->Limit( + FIELD => 'Expires', + VALUE => $expires_on_end_date->ISO( Timezone => 'UTC' ), + OPERATOR => '<', + ENTRYAGGREGATOR => 'AND', + ); +} + +$auth_tokens->Limit( + FIELD => 'Expires', + VALUE => 'NULL', + OPERATOR => 'IS NOT', + ENTRYAGGREGATOR => 'AND', +); + +my $users_alias = $auth_tokens->Join( + ALIAS1 => 'main', + FIELD1 => 'Owner', + TABLE2 => 'Users', + FIELD2 => 'id', +); + +$auth_tokens->Limit( + ALIAS => $users_alias, + FIELD => 'EmailAddress', + VALUE => 'NULL', + OPERATOR => 'IS NOT', + ENTRYAGGREGATOR => 'AND', +); + +if ( RT->Config->Get('DatabaseType') ne 'Oracle' ) { + $auth_tokens->Limit( + ALIAS => $users_alias, + FIELD => 'EmailAddress', + VALUE => '', + OPERATOR => '!=', + ENTRYAGGREGATOR => 'AND', + CASESENSITIVE => 0, + ); +} + +my $principals_alias = $auth_tokens->Join( + ALIAS1 => $users_alias, + FIELD1 => 'id', + TABLE2 => 'Principals', + FIELD2 => 'id', +); + +$auth_tokens->Limit( + ALIAS => $principals_alias, + FIELD => 'Disabled', + VALUE => 0, +); + +my %expired_tokens_by_user = (); +while ( my $auth_token = $auth_tokens->Next ) { + push @{ $expired_tokens_by_user{ $auth_token->Owner } }, $auth_token; +} + +foreach my $user_id ( keys %expired_tokens_by_user ) { + my $user_obj = RT::User->new( RT->SystemUser ); + $user_obj->Load($user_id); + my $user_email = $user_obj->EmailAddress; + my ( $ret, $msg ) = $template_obj->Parse( AuthTokens => $expired_tokens_by_user{$user_id}, UserObj => $user_obj ); + unless ($ret) { + print "Could not to parse template: $msg\n"; + exit 1; + } + + # Set our sender and recipient. + if ( !$template_obj->MIMEObj->head->get('From') ) { + if ( my $from = RT::Config->Get('RTSupportEmail') || RT::Config->Get('CorrespondAddress') ) { + $template_obj->MIMEObj->head->replace( 'From', Encode::encode( "UTF-8", $from ) ); + } + } + if ( !$template_obj->MIMEObj->head->get('To') ) { + $template_obj->MIMEObj->head->replace( 'To', Encode::encode( "UTF-8", $user_email ) ); + } + + if ($print) { + print $template_obj->MIMEObj->as_string, "\n"; + } + else { + my $ok = RT::Interface::Email::SendEmail( Entity => $template_obj->MIMEObj ); + if ( !$ok ) { + RT->Logger->error("Failed to send expiring auth tokens email to $user_email"); + } + } +} + + +__END__ + +=head1 NAME + +rt-email-expiring-auth-tokens - email users about expiring auth tokens + +=head1 SYNOPSIS + + rt-email-expiring-auth-tokens --expires-by '7 days' --template 'Auth tokens expiring in 7 days' + +=head1 DESCRIPTION + +This script is a tool to email users about their expiring auth tokens. + +=head1 OPTIONS + +=over + +=item expires-by + +All auth tokens that will expire between today and this date will be included in the email. + +Format is YYYY-MM-DD or any date format supported by Time::ParseDate. + +=item expires-on + +All auth tokens that expire on this date will be included in the email. + +Format is YYYY-MM-DD or any date format supported by Time::ParseDate. + +=item template + +Specify name or id of template you want to use. + +=item print + +Print the expiring auth tokens to STDOUT; don't email them. + +=item help + +Print this message + +=back