[Rt-commit] rt branch 5.0/add-auth-token-expires-field2 created. rt-5.0.4-218-g3e89b36282

BPS Git Server git at git.bestpractical.com
Tue Sep 12 14:53:32 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-field2 has been created
        at  3e89b362828f0c6b3da04ac59c0b63045c817bae (commit)

- Log -----------------------------------------------------------------
commit 3e89b362828f0c6b3da04ac59c0b63045c817bae
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/.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
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
diff --git a/etc/initialdata b/etc/initialdata
index 32da7e63e7..185616ad5e 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 0000000000..01e2be8b37
--- /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 0000000000..30ce05fcc1
--- /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
+#                                          <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 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

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

    Add Expires column to AuthToken

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
index 00eb899f46..9f0af697df 100644
--- a/etc/upgrade/5.0.5/schema.Pg
+++ b/etc/upgrade/5.0.5/schema.Pg
@@ -1 +1,2 @@
 UPDATE CustomFieldValues SET Category=NULL WHERE Category='';
+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
index 00eb899f46..9f0af697df 100644
--- a/etc/upgrade/5.0.5/schema.SQLite
+++ b/etc/upgrade/5.0.5/schema.SQLite
@@ -1 +1,2 @@
 UPDATE CustomFieldValues SET Category=NULL WHERE Category='';
+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
index 00eb899f46..450587af4f 100644
--- a/etc/upgrade/5.0.5/schema.mysql
+++ b/etc/upgrade/5.0.5/schema.mysql
@@ -1 +1,2 @@
 UPDATE CustomFieldValues SET Category=NULL WHERE Category='';
+ALTER TABLE AuthTokens ADD COLUMN Expires datetime DEFAULT NULL;
diff --git a/lib/RT/AuthToken.pm b/lib/RT/AuthToken.pm
index 729391caef..f936f66245 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,13 @@ sub Create {
 
     my $token = $self->_GenerateToken;
 
+    # delete Expires arg if it is empty
+    delete $args{Expires} unless $args{Expires};
+
     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));
@@ -229,6 +236,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 '!'
@@ -269,6 +290,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
@@ -366,6 +401,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..64ce8dc643 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -5261,9 +5261,15 @@ sub ProcessAuthToken {
             push @results, loc("Please enter your current password correctly.");
         }
         else {
+            my $expires;
+            if ( defined $args_ref->{'Expires'} and $args_ref->{'Expires'} =~ /\S/ ) {
+                $expires = RT::Date->new( $session{CurrentUser} );
+                $expires->Set( Format => 'unknown', Value => $args_ref->{'Expires'} );
+            }
             my ( $ok, $msg, $auth_string ) = $token->Create(
                 Owner       => $args_ref->{Owner},
                 Description => $args_ref->{Description},
+                $expires ? ( Expires => $expires->ISO ) : (),
             );
             if ($ok) {
                 push @results, $msg;
diff --git a/share/html/Elements/AuthToken/Create b/share/html/Elements/AuthToken/Create
index cfe8350598..fc1e557af1 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 &>[quant,_1,Month,Months]</&></option>
+                <option value="3M"><&|/l, 3 &>[quant,_1,Month,Months]</&></option>
+                <option value="6M"><&|/l, 6 &>[quant,_1,Month,Months]</&></option>
+                <option value="1Y"><&|/l, 1 &>[quant,_1,Year,Years]</&></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>
diff --git a/share/html/Elements/AuthToken/List b/share/html/Elements/AuthToken/List
index b341a59c6c..57f4244927 100644
--- a/share/html/Elements/AuthToken/List
+++ b/share/html/Elements/AuthToken/List
@@ -67,6 +67,18 @@
           <&|/l&>never used</&>
 %       }
         </span>
+
+%       my $expires = $token->ExpiresObj;
+%       if ( $expires->IsSet ) {
+%           if ( $expires->Unix < $now->Unix ) {
+        <span class="expires font-italic ml-2 expired">
+          <% loc("expired") %>
+%           } else {
+        <span class="expires font-italic ml-2">
+          <&|/l, $expires->AsString &>expires at [_1]</&>
+%           }
+        </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 +90,9 @@
 <%INIT>
 my $tokens = RT::AuthTokens->new($session{CurrentUser});
 $tokens->LimitOwner(VALUE => $Owner);
+
+my $now = RT::Date->new($session{CurrentUser});
+$now->SetToNow;
 </%INIT>
 
 <%ARGS>
diff --git a/share/static/css/elevator-light/misc.css b/share/static/css/elevator-light/misc.css
index 6ed4dd5eb1..060633b6bf 100644
--- a/share/static/css/elevator-light/misc.css
+++ b/share/static/css/elevator-light/misc.css
@@ -183,3 +183,7 @@ ul.ui-autocomplete {
 .modal.search-results-filter .modal-dialog {
     margin: 0;
 }
+
+.expires.expired {
+    color: red;
+}

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


hooks/post-receive
-- 
rt


More information about the rt-commit mailing list