[Rt-commit] rt branch 5.0/add-auth-token-expires-field created. rt-5.0.4-220-g4acc282198

BPS Git Server git at git.bestpractical.com
Fri Sep 8 17:24:14 UTC 2023


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "rt".

The branch, 5.0/add-auth-token-expires-field has been created
        at  4acc282198e6ee9ba58d803ec4a8d6191ec62fe3 (commit)

- Log -----------------------------------------------------------------
commit 4acc282198e6ee9ba58d803ec4a8d6191ec62fe3
Author: Brad Embree <brad at bestpractical.com>
Date:   Fri Jun 16 08:58:33 2023 -0700

    Fix incorrect Limit calls on Pg
    
    The Limit calls were tested on MySQL which is more forgiving than Pg.
    Had to fix the Limit call for the NOT NULL check and remove the Limit
    call that checked for an empty string for it to work on Pg.

diff --git a/sbin/rt-email-expiring-auth-tokens.in b/sbin/rt-email-expiring-auth-tokens.in
index 6122174566..1d86cba213 100644
--- a/sbin/rt-email-expiring-auth-tokens.in
+++ b/sbin/rt-email-expiring-auth-tokens.in
@@ -167,13 +167,7 @@ elsif ( $expiresOn ) {
 $authTokens->Limit(
     FIELD           => 'Expires',
     VALUE           => 'NULL',
-    OPERATOR        => '!=',
-    ENTRYAGGREGATOR => 'AND',
-);
-$authTokens->Limit(
-    FIELD           => 'Expires',
-    VALUE           => '',
-    OPERATOR        => '!=',
+    OPERATOR        => 'IS NOT',
     ENTRYAGGREGATOR => 'AND',
 );
 

commit 7494701243c2ff0ecfc2bcc7fd9ad2f15c509eb2
Author: Brad Embree <brad at bestpractical.com>
Date:   Tue May 2 07:54:34 2023 -0700

    Add new script to gitignore

diff --git a/.gitignore b/.gitignore
index 2d66e9bda4..f07ed8350a 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

commit 725d6f930846b26601b65348e270e7edf2ba095d
Author: Brad Embree <brad at bestpractical.com>
Date:   Tue May 2 07:49:17 2023 -0700

    Add new script to build files

diff --git a/Makefile.in b/Makefile.in
index 515621aad4..74506a9495 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 53e5cfada4..dd34674364 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

commit 1bdf40538b59d10d36b564f4f1b31543f29020ba
Author: Brad Embree <brad at bestpractical.com>
Date:   Mon May 1 20:03:40 2023 -0700

    Add script to email users expiring auth tokens

diff --git a/sbin/rt-email-expiring-auth-tokens.in b/sbin/rt-email-expiring-auth-tokens.in
new file mode 100644
index 0000000000..6122174566
--- /dev/null
+++ b/sbin/rt-email-expiring-auth-tokens.in
@@ -0,0 +1,292 @@
+#!@PERL@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2023 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 }}}
+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 Getopt::Long;
+use RT;
+use RT::Interface::CLI qw( loc );
+use RT::Interface::Email;
+
+RT::LoadConfig();
+RT::Init();
+
+sub usage {
+    my ($error) = @_;
+    print loc("Usage:") . " $0 --expired-by date [--print] [--help]\n";
+    print "\n" . loc(
+        "[_1] is a utility, meant to be run from cron, that emails all users with a list of their auth tokens that will expire by the expired-by date.",
+        $0
+    ) . "\n";
+    print "\t--expired-by date\t"
+        . loc("Include all auth tokens that will expire by this date.")
+        . "\n"
+        . "\t                 \t"
+        . loc("Date can be in any format understood by Time::ParseDate.")
+        . "\n";
+    print "\t--expires-on date\t"
+        . loc("Include all auth tokens that will expire on this date.")
+        . "\n"
+        . "\t                 \t"
+        . loc("Date can be in any format understood by Time::ParseDate.")
+        . "\n";
+    print "\t-p, --print      \t"
+        . loc("Print the expiring auth tokens to STDOUT; don't email them.");
+    print "\t-h, --help       \t" . loc("Print this message") . "\n";
+
+    if ( $error eq 'help' ) {
+        exit 0;
+    } else {
+        print loc("Error") . ": " . loc($error) . "\n";
+        exit 1;
+    }
+}
+
+my ( $expiredBy, $expiresOn, $print, $help ) = ( '', '', '', '' );
+GetOptions(
+    'expired-by=s' => \$expiredBy,
+    'expires-on=s' => \$expiresOn,
+    'print'        => \$print,
+    'help'         => \$help,
+);
+
+usage('help') if $help;
+usage("one of expired-by or expires-on parameter is required")
+    unless $expiredBy || $expiresOn;
+usage("cannot use both expired-by and expires-on parameters")
+    if $expiredBy && $expiresOn;
+
+my $authTokens = RT::AuthTokens->new( RT->SystemUser );
+
+my $expires_text = 'expire ';
+
+if ( $expiredBy ) {
+    my $expiredByDateObj = RT::Date->new( RT->SystemUser );
+    usage("Invalid date parameter '$expiredBy'")
+        unless $expiredByDateObj->Set( Format => 'unknown', Value => $expiredBy, Timezone => 'server' ) > 0;
+
+    $expiredByDateObj->SetToMidnight( Timezone => 'server' );
+    $expiredByDateObj->AddDay;
+
+    $authTokens->Limit(
+        FIELD           => 'Expires',
+        VALUE           => $expiredByDateObj->ISO( Timezone => 'UTC' ),
+        OPERATOR        => '<=',
+        ENTRYAGGREGATOR => 'AND',
+    );
+
+    $expires_text .= 'by ' . $expiredByDateObj->Date( Timezone => 'server' );
+}
+elsif ( $expiresOn ) {
+    my $expiresOnStartDateObj = RT::Date->new( RT->SystemUser );
+    my $expiresOnEndDateObj = RT::Date->new( RT->SystemUser );
+    usage("Invalid date parameter '$expiresOn'")
+        unless $expiresOnStartDateObj->Set( Format => 'unknown', Value => $expiresOn, Timezone => 'server' ) > 0;
+
+    $expiresOnEndDateObj->Set( Format => 'unix', Value => $expiresOnStartDateObj->SetToMidnight( Timezone => 'server' ), Timezone => 'server' );
+    $expiresOnEndDateObj->AddDay;
+
+    $authTokens->Limit(
+        FIELD           => 'Expires',
+        VALUE           => $expiresOnStartDateObj->ISO( Timezone => 'UTC' ),
+        OPERATOR        => '>=',
+        ENTRYAGGREGATOR => 'AND',
+    );
+    $authTokens->Limit(
+        FIELD           => 'Expires',
+        VALUE           => $expiresOnEndDateObj->ISO( Timezone => 'UTC' ),
+        OPERATOR        => '<',
+        ENTRYAGGREGATOR => 'AND',
+    );
+
+    $expires_text .= 'on ' . $expiresOnStartDateObj->Date( Timezone => 'server' );
+}
+
+$authTokens->Limit(
+    FIELD           => 'Expires',
+    VALUE           => 'NULL',
+    OPERATOR        => '!=',
+    ENTRYAGGREGATOR => 'AND',
+);
+$authTokens->Limit(
+    FIELD           => 'Expires',
+    VALUE           => '',
+    OPERATOR        => '!=',
+    ENTRYAGGREGATOR => 'AND',
+);
+
+my %expiredTokensByUser = ();
+while ( my $row = $authTokens->Next ) {
+    $expiredTokensByUser{ $row->Owner } = [
+        @{ $expiredTokensByUser{ $row->Owner } || [] },
+        {
+            desc    => $row->Description,
+            expires => $row->Expires
+        }
+    ];
+}
+
+# Load our template.  If we cannot load the template, abort
+# immediately rather than failing through many loops.
+my $token_template = RT::Template->new( RT->SystemUser );
+my ( $ret, $msg ) = $token_template->Load('Email Auth Token Expiry');
+unless ($ret) {
+    print loc("Failed to load template")
+        . " 'Email Auth Token Expiry': "
+        . $msg
+        . ".  Cannot continue.\n";
+    exit 1;
+}
+
+foreach my $userId ( keys %expiredTokensByUser ) {
+    my $userObj = RT::User->new( RT->SystemUser );
+    my ( $ret, $msg ) = $userObj->Load($userId);
+    if ( $ret ) {
+        my $userName     = $userObj->RealName || $userObj->Name;
+        my $userEmail    = $userObj->EmailAddress;
+        my $emailContent = $print ? "Expiring Auth Tokens for '$userName':\n"
+                                  : "Hello, $userName.\n\nThe following auth tokens will $expires_text:\n\n";
+        foreach my $authToken ( @{ $expiredTokensByUser{$userId} } ) {
+            my $timezone = $print ? 'server' : 'user';
+            my $auth_expires = RT::Date->new( $userObj );
+            $auth_expires->Set( Format => 'unknown', Value => $authToken->{expires}, Timezone => 'UTC' );
+            $emailContent .= $print ? "\t" : "";
+            $emailContent .= $authToken->{desc} . ' expires on ' . $auth_expires->ISO( Timezone => $timezone ) . "\n";
+        }
+
+        if ( $print ) {
+            print "$emailContent\n";
+        } else {
+            unless ( $userEmail ) {
+                RT->Logger->warning( loc("No email for User") . ": $userName" );
+                next;
+            }
+            ( $ret, $msg ) = $token_template->Parse( Argument => $emailContent );
+            unless ($ret) {
+                print loc("Failed to parse template")
+                    . " 'Email Auth Token Expiry'.  Cannot continue.\n";
+                exit 1;
+            }
+
+            # Set our sender and recipient.
+            $token_template->MIMEObj->head->replace(
+                'From', Encode::encode( "UTF-8", RT::Config->Get('RTSupportEmail') || RT::Config->Get('CorrespondAddress') ) );
+            $token_template->MIMEObj->head->replace(
+                'To',   Encode::encode( "UTF-8", $userEmail ) );
+
+            my $ok = RT::Interface::Email::SendEmail(
+                Entity => $token_template->MIMEObj );
+
+            if ( ! $ok ) {
+                RT->Logger->error("Failed to send expiring auth tokens email to $userEmail");
+            }
+        }
+
+    } else {
+        RT->Logger->error("Could not load User with id $userId: $msg");
+    }
+}
+
+
+__END__
+
+=head1 NAME
+
+rt-email-expiring-auth-tokens - email users about expiring auth tokens
+
+=head1 SYNOPSIS
+
+    rt-email-expiring-auth-tokens --expired-by Expired-By-Date [--print] [--help]
+    rt-email-expiring-auth-tokens --expires-on Expires-On-Date [--print] [--help]
+
+=head1 DESCRIPTION
+
+This script is a tool to email users about their expiring auth tokens.
+
+=head1 OPTIONS
+
+=over
+
+=item expired-by
+
+Any auth tokens that expire by this date will be included in the email.
+
+Format is YYYY-MM-DD or any date format supported by Time::ParseDate.
+
+=item expires-on
+
+Any 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 print
+
+Print the expiring auth tokens to STDOUT; don't email them.
+
+=item help
+
+Print this message
+
+=back

commit d16dae67c57fc3982e4d6e91b7593eaa98e5c6f6
Author: Brad Embree <brad at bestpractical.com>
Date:   Mon May 1 19:57:01 2023 -0700

    Add database schema change

diff --git a/etc/schema.Oracle b/etc/schema.Oracle
index 3daeae98b9..53210b145b 100644
--- a/etc/schema.Oracle
+++ b/etc/schema.Oracle
@@ -572,7 +572,8 @@ CREATE TABLE AuthTokens (
     Creator         NUMBER(11,0)    DEFAULT 0 NOT NULL,
     Created         DATE,
     LastUpdatedBy   NUMBER(11,0)    DEFAULT 0 NOT NULL,
-    LastUpdated     DATE
+    LastUpdated     DATE,
+    Expires         DATE
 );
 
 CREATE INDEX AuthTokensOwner ON AuthTokens (Owner);
diff --git a/etc/schema.Pg b/etc/schema.Pg
index 77b7eb2315..b614793b07 100644
--- a/etc/schema.Pg
+++ b/etc/schema.Pg
@@ -811,6 +811,7 @@ CREATE TABLE AuthTokens (
     Created           timestamp                DEFAULT NULL,
     LastUpdatedBy     integer         NOT NULL DEFAULT 0,
     LastUpdated       timestamp                DEFAULT NULL,
+    Expires           timestamp                DEFAULT NULL,
     PRIMARY KEY (id)
 );
 
diff --git a/etc/schema.SQLite b/etc/schema.SQLite
index 5391694e0b..4b64f3349f 100644
--- a/etc/schema.SQLite
+++ b/etc/schema.SQLite
@@ -603,7 +603,8 @@ CREATE TABLE AuthTokens (
     Creator           int(11)         NOT NULL DEFAULT 0,
     Created           timestamp                DEFAULT NULL,
     LastUpdatedBy     int(11)         NOT NULL DEFAULT 0,
-    LastUpdated       timestamp                DEFAULT NULL
+    LastUpdated       timestamp                DEFAULT NULL,
+    Expires           timestamp                DEFAULT NULL
 );
 
 CREATE INDEX AuthTokensOwner on AuthTokens (Owner);
diff --git a/etc/schema.mysql b/etc/schema.mysql
index b79bb0644d..382a10fc43 100644
--- a/etc/schema.mysql
+++ b/etc/schema.mysql
@@ -593,6 +593,7 @@ CREATE TABLE AuthTokens (
     Created           datetime                 DEFAULT NULL,
     LastUpdatedBy     int(11)         NOT NULL DEFAULT 0,
     LastUpdated       datetime                 DEFAULT NULL,
+    Expires           datetime                 DEFAULT NULL,
     PRIMARY KEY (id)
 ) ENGINE=InnoDB CHARACTER SET utf8mb4;
 
diff --git a/etc/upgrade/5.0.5/schema.Oracle b/etc/upgrade/5.0.5/schema.Oracle
new file mode 100644
index 0000000000..c86d412ff2
--- /dev/null
+++ b/etc/upgrade/5.0.5/schema.Oracle
@@ -0,0 +1 @@
+ALTER TABLE AuthTokens ADD Expires DATE;
diff --git a/etc/upgrade/5.0.5/schema.Pg b/etc/upgrade/5.0.5/schema.Pg
new file mode 100644
index 0000000000..a750698056
--- /dev/null
+++ b/etc/upgrade/5.0.5/schema.Pg
@@ -0,0 +1 @@
+ALTER TABLE AuthTokens ADD COLUMN Expires timestamp DEFAULT NULL;
diff --git a/etc/upgrade/5.0.5/schema.SQLite b/etc/upgrade/5.0.5/schema.SQLite
new file mode 100644
index 0000000000..a750698056
--- /dev/null
+++ b/etc/upgrade/5.0.5/schema.SQLite
@@ -0,0 +1 @@
+ALTER TABLE AuthTokens ADD COLUMN Expires timestamp DEFAULT NULL;
diff --git a/etc/upgrade/5.0.5/schema.mysql b/etc/upgrade/5.0.5/schema.mysql
new file mode 100644
index 0000000000..93887a48b7
--- /dev/null
+++ b/etc/upgrade/5.0.5/schema.mysql
@@ -0,0 +1 @@
+ALTER TABLE AuthTokens ADD COLUMN Expires datetime DEFAULT NULL;

commit 70fecc599efffd4eff03f64e0caddda034f5bdd9
Author: Brad Embree <brad at bestpractical.com>
Date:   Mon May 1 19:56:08 2023 -0700

    Add template for auth token expiry email

diff --git a/etc/initialdata b/etc/initialdata
index 32da7e63e7..4f79505616 100644
--- a/etc/initialdata
+++ b/etc/initialdata
@@ -778,6 +778,14 @@ Hour:         { $SubscriptionObj->SubValue('Hour') }
 }
 }
 },
+               {   Queue       => '0',
+                   Name        => 'Email Auth Token Expiry', # loc
+                   Description => 'Email template for emailing users about expiring auth tokens', # loc
+                   Content => q[Subject: Expiring Auth Token Notification
+
+{ $Argument }
+],
+               },
 );
 
 @Scrips = (
diff --git a/etc/upgrade/5.0.5/content b/etc/upgrade/5.0.5/content
new file mode 100644
index 0000000000..0ce329a09a
--- /dev/null
+++ b/etc/upgrade/5.0.5/content
@@ -0,0 +1,14 @@
+use strict;
+use warnings;
+
+our @Templates = (
+
+               {   Queue       => '0',
+                   Name        => 'Email Auth Token Expiry',    # loc
+                   Description => 'Email template for emailing users about expiring auth tokens',  # loc
+                   Content => q[Subject: Expiring Auth Token Notification
+
+{ $Argument }
+],
+               },
+);

commit 65b677b0f08b1b6043ea4b3acdfa4755451e8fba
Author: Brad Embree <brad at bestpractical.com>
Date:   Sun Apr 30 17:10:51 2023 -0700

    Check for Expires when validating AuthToken

diff --git a/lib/RT/AuthToken.pm b/lib/RT/AuthToken.pm
index 44b6009376..baf6755e9c 100644
--- a/lib/RT/AuthToken.pm
+++ b/lib/RT/AuthToken.pm
@@ -244,6 +244,20 @@ sub IsToken {
     my $self = shift;
     my $value = shift;
 
+    # check if token has expired
+    if ( my $expires = $self->__Value('Expires') ) {
+        my $expiresObj = RT::Date->new( $self->CurrentUser );
+        my $nowObj     = RT::Date->new( $self->CurrentUser );
+
+        $expiresObj->Set( Format => 'sql', Value => $expires );
+        $nowObj->SetToNow();
+
+        if ( $expiresObj->Unix < $nowObj->Unix ) {
+            $RT::Logger->debug("Auth Token has expired.");
+            return 0;
+        }
+    }
+
     my $stored = $self->__Value('Token');
 
     # If it's a new-style (>= RT 4.0) password, it starts with a '!'

commit 84e6a4a1e0278aae38f1dee1d14ff5091888cf6d
Author: Brad Embree <brad at bestpractical.com>
Date:   Sun Apr 30 17:10:20 2023 -0700

    Display Expires details

diff --git a/share/html/Elements/AuthToken/List b/share/html/Elements/AuthToken/List
index b341a59c6c..b71cc73b2e 100644
--- a/share/html/Elements/AuthToken/List
+++ b/share/html/Elements/AuthToken/List
@@ -67,6 +67,16 @@
           <&|/l&>never used</&>
 %       }
         </span>
+%       if ( $token->Expires ) {
+%           if ( $token->ExpiresObj->Unix < $now->Unix ) {
+        <span class="last-used font-italic ml-2" style="color:red">
+          <% loc("Expired") %>
+%           } else {
+        <span class="last-used font-italic ml-2">
+          <% '(' . loc("expires on") . ' ' . $token->ExpiresObj->Date( Timezone => 'user' ) . ' at ' . $token->ExpiresObj->Time( Timezone => 'user' ) . ')' %>
+%           }
+        </span>
+%       }
       </div>
       <a class="button btn btn-sm btn-primary float-right" href="#edit-auth-token-<% $token->id %>" data-toggle="modal" rel="modal:open"><% loc('Edit') %></a>
     </li>
@@ -78,6 +88,9 @@
 <%INIT>
 my $tokens = RT::AuthTokens->new($session{CurrentUser});
 $tokens->LimitOwner(VALUE => $Owner);
+
+my $now = RT::Date->new($session{CurrentUser});
+$now->SetToNow;
 </%INIT>
 
 <%ARGS>

commit aa9a73a3fb41e2bd4fd15c79dda42f7a120bb060
Author: Brad Embree <brad at bestpractical.com>
Date:   Sun Apr 30 17:09:52 2023 -0700

    Add ExpiresObj method

diff --git a/lib/RT/AuthToken.pm b/lib/RT/AuthToken.pm
index fe91414540..44b6009376 100644
--- a/lib/RT/AuthToken.pm
+++ b/lib/RT/AuthToken.pm
@@ -284,6 +284,20 @@ sub LastUsedObj {
     return $date;
 }
 
+=head2 ExpiresObj
+
+L</Expires> as an L<RT::Date> object.
+
+=cut
+
+sub ExpiresObj {
+    my $self = shift;
+    my $date = RT::Date->new($self->CurrentUser);
+    $date->Set(Format => 'sql', Value => $self->Expires)
+        if $self->Expires;
+    return $date;
+}
+
 =head1 PRIVATE METHODS
 
 Documented for internal use only, do not call these from outside

commit 6073cbc13d174f39ac592047e4f0dbdf9b3663ea
Author: Brad Embree <brad at bestpractical.com>
Date:   Sun Apr 30 17:08:45 2023 -0700

    Add Expires input controls

diff --git a/share/html/Elements/AuthToken/Create b/share/html/Elements/AuthToken/Create
index cfe8350598..a6c5042718 100644
--- a/share/html/Elements/AuthToken/Create
+++ b/share/html/Elements/AuthToken/Create
@@ -55,7 +55,7 @@
         </a>
       </div>
       <div class="modal-body">
-        <form method="POST">
+        <form method="POST" id="createAuthToken">
           <input type="hidden" name="Owner" value="<% $Owner %>">
 %         if ( $require_password ){
           <div class="form-row">
@@ -76,6 +76,38 @@
               <input class="form-control" type="text" name="Description" value="<% $Description %>" size="16" />
             </div>
           </div>
+          <div class="form-row">
+            <div class="label col-4">
+              <span class="prev-icon-helper"><&|/l&>Expires</&>:</span>\
+<span class="far fa-question-circle icon-helper" data-toggle="tooltip" data-placement="top" data-original-title="<% loc("Set an optional Expires date?") %>"></span>
+            </div>
+            <div class="col-8">
+              <div class="custom-control custom-checkbox">
+                <input type="checkbox" id="ExpiresCheckbox" name="ExpiresCheckbox" class="custom-control-input" value="0" />
+                <label class="custom-control-label" for="ExpiresCheckbox">Set Expires Date</label>
+              </div>
+            </div>
+          </div>
+          <div class="form-row">
+            <div class="label col-4">
+            </div>
+            <div class="col-8">
+              <select name="ExpiresSelect" id="ExpiresSelect" class="form-control selectpicker">
+                <option value="1M" ><&|/l&>1 Month</&></option>
+                <option value="3M"><&|/l&>3 Months</&></option>
+                <option value="6M"><&|/l&>6 Months</&></option>
+                <option value="1Y"><&|/l&>1 Year</&></option>
+                <option value="Custom"><&|/l&>Custom Date</&></option>
+              </select>
+            </div>
+          </div>
+          <div class="form-row">
+            <div class="label col-4">
+            </div>
+            <div class="col-8">
+              <& /Elements/SelectDate, Name=>"Expires", id=>"Expires", Default => $Expires, ShowTime => 1 &>
+            </div>
+          </div>
 
           <div class="form-row">
             <div class="col-12">
@@ -88,6 +120,81 @@
   </div>
 </div>
 
+<script>
+  jQuery("#ExpiresSelect").prop( "disabled", true );
+  jQuery("#Expires").prop( "disabled", true );
+
+  // Expires input needs to be enabled when the form is submitted to read its value
+  jQuery("#createAuthToken").submit(
+    function(e){
+      jQuery("#Expires").prop( "disabled", false );
+      return true;
+    }
+  );
+
+  var onExpiresSelectChange = function() {
+    var expiresSelectVal = jQuery("#ExpiresSelect option:selected").val();
+    var expires          = jQuery("#Expires");
+
+    // make sure expires is enabled so we can change value
+    expires.prop( "disabled", false );
+    if ( jQuery("#ExpiresSelect").prop("disabled") ) {
+      // Expires date options are disabled so Expires should be blank
+      expires.val("");
+    } else {
+      // Expires date options are enabled so determine what we should set
+      // Expires value to based on selected Expires option
+      if ( expiresSelectVal != 'Custom' ) {
+        var date   = new Date();
+        var regexp = /(\d)(\w)/;
+        var match  = expiresSelectVal.match(regexp);
+
+        if ( match != null ) {
+          if ( match[2] == "M" ) {
+            date.setMonth( date.getMonth() + parseInt( match[1] ) );
+          } else {
+            date.setFullYear( date.getFullYear() + parseInt( match[1] ) );
+          }
+          expires.val( date.toISOString().substr(0, 10) + ' 00:00:00' );
+        }
+      }
+    }
+
+    // now enable/disable expires
+    expires.prop( "disabled", expiresSelectVal != "Custom" );
+  };
+
+  jQuery("#ExpiresCheckbox").click(
+    function(){
+      var expiresSelect = jQuery("#ExpiresSelect");
+
+      var disable = true;
+      if ( expiresSelect.prop("disabled") ) {
+        // user is enabling the Expires date options
+        disable = false;
+      } else {
+        // user is disabling the Expires date options
+        disable = true;
+
+        // set back to default value
+        expiresSelect.val("1M");
+
+        jQuery(".selectpicker").selectpicker("refresh");
+      }
+
+      expiresSelect.prop( "disabled", disable );
+
+      jQuery(".selectpicker").selectpicker("refresh");
+      onExpiresSelectChange();
+    }
+  );
+  jQuery("#ExpiresSelect").change(
+    function(){
+      onExpiresSelectChange();
+    }
+  );
+</script>
+
 <%INIT>
 # Don't require password for systems with some form of federated auth,
 # or if configured to not require a password
@@ -101,4 +208,5 @@ if ( RT->Config->Get('DisablePasswordForAuthToken') or not $res{'CanSet'}) {
 <%ARGS>
 $Owner
 $Description => ''
+$Expires => ''
 </%ARGS>

commit 1506ce597446f8fffd488cfc3ea42c621d7605de
Author: Brad Embree <brad at bestpractical.com>
Date:   Sun Apr 30 17:06:56 2023 -0700

    Allow optional Expires param for new AuthToken

diff --git a/lib/RT/AuthToken.pm b/lib/RT/AuthToken.pm
index 729391caef..fe91414540 100644
--- a/lib/RT/AuthToken.pm
+++ b/lib/RT/AuthToken.pm
@@ -81,6 +81,10 @@ object's CurrentUser, then the AdminUsers permission is required.
 
 A human-readable description of what this token will be used for.
 
+=item Expires
+
+An optional date for when this token should expire.
+
 =back
 
 Returns a tuple of (status, msg) on failure and (id, msg, authstring) on
@@ -110,10 +114,21 @@ sub Create {
 
     my $token = $self->_GenerateToken;
 
+    # delete Expires arg if it is empty
+    delete $args{Expires}
+        unless $args{Expires};
+
+    if ( exists $args{Expires} ) {
+        # convert to UTC
+        my $date = RT::Date->new( $self->CurrentUser );
+        $date->Set( Format => 'unknown', Value => $args{Expires}, Timezone => 'user' );
+        $args{Expires} = $date->ISO( Timezone => 'UTC' );
+    }
+
     my ( $id, $msg ) = $self->SUPER::Create(
         Token => $self->_CryptToken($token),
         map { $_ => $args{$_} } grep {exists $args{$_}}
-            qw(Owner Description),
+            qw(Owner Description Expires),
     );
     unless ($id) {
         return (0, $self->loc("Authentication token create failed: [_1]", $msg));
@@ -366,6 +381,7 @@ sub _CoreAccessible {
         Created       => { read => 1, type => 'datetime',       default => '',  auto => 1 },
         LastUpdatedBy => { read => 1, type => 'int(11)',        default => '0', auto => 1 },
         LastUpdated   => { read => 1, type => 'datetime',       default => '',  auto => 1 },
+        Expires       => { read => 1, type => 'datetime',       default => '',  auto => 1 },
     }
 }
 
diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 415d900e5c..d799c638ed 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -5264,6 +5264,7 @@ sub ProcessAuthToken {
             my ( $ok, $msg, $auth_string ) = $token->Create(
                 Owner       => $args_ref->{Owner},
                 Description => $args_ref->{Description},
+                Expires     => $args_ref->{Expires},
             );
             if ($ok) {
                 push @results, $msg;

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


hooks/post-receive
-- 
rt


More information about the rt-commit mailing list