[Rt-commit] rt branch, 5.0/core-authen-token, created. rt-5.0.0alpha1-418-g52f4a9e467
Jim Brandt
jbrandt at bestpractical.com
Wed May 13 16:59:32 EDT 2020
The branch, 5.0/core-authen-token has been created
at 52f4a9e4671213bcc4ea9f0dc6c8e25879806fa3 (commit)
- Log -----------------------------------------------------------------
commit 6417195ce92fdeb14c4f953c7436f9b116d2995a
Author: Craig <craig at bestpractical.com>
Date: Mon May 4 17:57:42 2020 -0400
Core RT::Authen::Token
The html/js/css/gif are not imported, as we are going to re-implement
it.
diff --git a/etc/acl.Pg b/etc/acl.Pg
index 41a44b16c1..dc3ca03f37 100644
--- a/etc/acl.Pg
+++ b/etc/acl.Pg
@@ -68,6 +68,8 @@ sub acl {
ObjectCustomRoles
configurations_id_seq
Configurations
+ authtokens_id_seq
+ AuthTokens
);
my $db_user = RT->Config->Get('DatabaseUser');
diff --git a/etc/schema.Oracle b/etc/schema.Oracle
index 2b366299cf..5117ad2ef8 100644
--- a/etc/schema.Oracle
+++ b/etc/schema.Oracle
@@ -554,3 +554,18 @@ CREATE TABLE Configurations (
CREATE INDEX Configurations1 ON Configurations (LOWER(Name), Disabled);
CREATE INDEX Configurations2 ON Configurations (Disabled);
+
+CREATE SEQUENCE AuthTokens_seq;
+CREATE TABLE AuthTokens (
+ id NUMBER(11,0) CONSTRAINT AuthTokens_key PRIMARY KEY,
+ Owner NUMBER(11,0) DEFAULT 0 NOT NULL,
+ Token VARCHAR2(256),
+ Description varchar2(255) DEFAULT '',
+ LastUsed DATE,
+ Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
+ Created DATE,
+ LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
+ LastUpdated DATE
+);
+
+CREATE INDEX AuthTokensOwner ON AuthTokens (Owner);
diff --git a/etc/schema.Pg b/etc/schema.Pg
index c51be5dff3..5f6c3d85fb 100644
--- a/etc/schema.Pg
+++ b/etc/schema.Pg
@@ -796,3 +796,18 @@ CREATE TABLE Configurations (
CREATE INDEX Configurations1 ON Configurations (LOWER(Name), Disabled);
CREATE INDEX Configurations2 ON Configurations (Disabled);
+CREATE SEQUENCE authtokens_id_seq;
+CREATE TABLE AuthTokens (
+ id integer DEFAULT nextval('authtokens_id_seq'),
+ Owner integer NOT NULL DEFAULT 0,
+ Token varchar(256) NULL,
+ Description varchar(255) NOT NULL DEFAULT '',
+ LastUsed timestamp DEFAULT NULL,
+ Creator integer NOT NULL DEFAULT 0,
+ Created timestamp DEFAULT NULL,
+ LastUpdatedBy integer NOT NULL DEFAULT 0,
+ LastUpdated timestamp DEFAULT NULL,
+ PRIMARY KEY (id)
+);
+
+CREATE INDEX AuthTokensOwner ON AuthTokens (Owner);
diff --git a/etc/schema.SQLite b/etc/schema.SQLite
index c51070aa87..bc8b456ecd 100644
--- a/etc/schema.SQLite
+++ b/etc/schema.SQLite
@@ -585,3 +585,16 @@ CREATE TABLE Configurations (
CREATE INDEX Configurations1 ON Configurations (Name, Disabled);
CREATE INDEX Configurations2 ON Configurations (Disabled);
+CREATE TABLE AuthTokens (
+ id INTEGER PRIMARY KEY,
+ Owner int(11) NOT NULL DEFAULT 0,
+ Token varchar(256) collate NOCASE NULL ,
+ Description varchar(255) NOT NULL DEFAULT '',
+ LastUsed timestamp DEFAULT NULL,
+ Creator int(11) NOT NULL DEFAULT 0,
+ Created timestamp DEFAULT NULL,
+ LastUpdatedBy int(11) NOT NULL DEFAULT 0,
+ LastUpdated timestamp DEFAULT NULL
+);
+
+CREATE INDEX AuthTokensOwner on AuthTokens (Owner);
diff --git a/etc/schema.mysql b/etc/schema.mysql
index ca90073340..69cb029093 100644
--- a/etc/schema.mysql
+++ b/etc/schema.mysql
@@ -576,3 +576,17 @@ CREATE TABLE Configurations (
CREATE INDEX Configurations1 ON Configurations (Name, Disabled);
CREATE INDEX Configurations2 ON Configurations (Disabled);
+CREATE TABLE AuthTokens (
+ id int(11) NOT NULL AUTO_INCREMENT,
+ Owner int(11) NOT NULL DEFAULT 0,
+ Token varchar(256) NULL,
+ Description varchar(255) NOT NULL DEFAULT '',
+ LastUsed datetime DEFAULT NULL,
+ Creator int(11) NOT NULL DEFAULT 0,
+ Created datetime DEFAULT NULL,
+ LastUpdatedBy int(11) NOT NULL DEFAULT 0,
+ LastUpdated datetime DEFAULT NULL,
+ PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE INDEX AuthTokensOwner ON AuthTokens (Owner);
diff --git a/etc/upgrade/4.5.7/acl.Pg b/etc/upgrade/4.5.7/acl.Pg
new file mode 100644
index 0000000000..61345ade01
--- /dev/null
+++ b/etc/upgrade/4.5.7/acl.Pg
@@ -0,0 +1,29 @@
+sub acl {
+ my $dbh = shift;
+
+ my @acls;
+ my @tables = qw (
+ authtokens_id_seq
+ AuthTokens
+ );
+
+ my $db_user = RT->Config->Get('DatabaseUser');
+
+ my $sequence_right
+ = ( $dbh->{pg_server_version} >= 80200 )
+ ? "USAGE, SELECT, UPDATE"
+ : "SELECT, UPDATE";
+
+ foreach my $table (@tables) {
+ # Tables are upper-case, sequences are lowercase in @tables
+ if ( $table =~ /^[a-z]/ ) {
+ push @acls, "GRANT $sequence_right ON $table TO \"$db_user\";"
+ }
+ else {
+ push @acls, "GRANT SELECT, INSERT, UPDATE, DELETE ON $table TO \"$db_user\";"
+ }
+ }
+ return (@acls);
+}
+
+1;
diff --git a/etc/upgrade/4.5.7/schema.Oracle b/etc/upgrade/4.5.7/schema.Oracle
new file mode 100644
index 0000000000..dafe9c04b1
--- /dev/null
+++ b/etc/upgrade/4.5.7/schema.Oracle
@@ -0,0 +1,14 @@
+CREATE SEQUENCE AuthTokens_seq;
+CREATE TABLE AuthTokens (
+ id NUMBER(11,0) CONSTRAINT AuthTokens_key PRIMARY KEY,
+ Owner NUMBER(11,0) DEFAULT 0 NOT NULL,
+ Token VARCHAR2(256),
+ Description varchar2(255) DEFAULT '',
+ LastUsed DATE,
+ Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
+ Created DATE,
+ LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
+ LastUpdated DATE
+);
+
+CREATE INDEX AuthTokensOwner ON AuthTokens (Owner);
diff --git a/etc/upgrade/4.5.7/schema.Pg b/etc/upgrade/4.5.7/schema.Pg
new file mode 100644
index 0000000000..29a5372a89
--- /dev/null
+++ b/etc/upgrade/4.5.7/schema.Pg
@@ -0,0 +1,15 @@
+CREATE SEQUENCE authtokens_id_seq;
+CREATE TABLE AuthTokens (
+ id integer DEFAULT nextval('authtokens_id_seq'),
+ Owner integer NOT NULL DEFAULT 0,
+ Token varchar(256) NULL,
+ Description varchar(255) NOT NULL DEFAULT '',
+ LastUsed timestamp DEFAULT NULL,
+ Creator integer NOT NULL DEFAULT 0,
+ Created timestamp DEFAULT NULL,
+ LastUpdatedBy integer NOT NULL DEFAULT 0,
+ LastUpdated timestamp DEFAULT NULL,
+ PRIMARY KEY (id)
+);
+
+CREATE INDEX AuthTokensOwner ON AuthTokens (Owner);
diff --git a/etc/upgrade/4.5.7/schema.SQLite b/etc/upgrade/4.5.7/schema.SQLite
new file mode 100644
index 0000000000..d761c4aafb
--- /dev/null
+++ b/etc/upgrade/4.5.7/schema.SQLite
@@ -0,0 +1,13 @@
+CREATE TABLE AuthTokens (
+ id INTEGER PRIMARY KEY,
+ Owner int(11) NOT NULL DEFAULT 0,
+ Token varchar(256) collate NOCASE NULL ,
+ Description varchar(255) NOT NULL DEFAULT '',
+ LastUsed timestamp DEFAULT NULL,
+ Creator int(11) NOT NULL DEFAULT 0,
+ Created timestamp DEFAULT NULL,
+ LastUpdatedBy int(11) NOT NULL DEFAULT 0,
+ LastUpdated timestamp DEFAULT NULL
+);
+
+CREATE INDEX AuthTokensOwner on AuthTokens (Owner);
diff --git a/etc/upgrade/4.5.7/schema.mysql b/etc/upgrade/4.5.7/schema.mysql
new file mode 100644
index 0000000000..0bf9a499ae
--- /dev/null
+++ b/etc/upgrade/4.5.7/schema.mysql
@@ -0,0 +1,14 @@
+CREATE TABLE AuthTokens (
+ id int(11) NOT NULL AUTO_INCREMENT,
+ Owner int(11) NOT NULL DEFAULT 0,
+ Token varchar(256) NULL,
+ Description varchar(255) NOT NULL DEFAULT '',
+ LastUsed datetime DEFAULT NULL,
+ Creator int(11) NOT NULL DEFAULT 0,
+ Created datetime DEFAULT NULL,
+ LastUpdatedBy int(11) NOT NULL DEFAULT 0,
+ LastUpdated datetime DEFAULT NULL,
+ PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE INDEX AuthTokensOwner ON AuthTokens (Owner);
diff --git a/lib/RT.pm b/lib/RT.pm
index 21f54c46c1..060cc1bb05 100644
--- a/lib/RT.pm
+++ b/lib/RT.pm
@@ -506,6 +506,7 @@ sub InitClasses {
require RT::CustomFieldValues::Canonicalizer;
require RT::Configuration;
require RT::Configurations;
+ require RT::Authen::Token;
_BuildTableAttributes();
@@ -771,6 +772,7 @@ our %CORED_PLUGINS = (
'RT::Extension::PriorityAsString' => '5.0',
'RT::Extension::AssetSQL' => '5.0',
'RT::Extension::LifecycleUI' => '5.0',
+ 'RT::Authen::Token' => '5.0',
);
sub InitPlugins {
diff --git a/lib/RT/AuthToken.pm b/lib/RT/AuthToken.pm
new file mode 100644
index 0000000000..d4472970bc
--- /dev/null
+++ b/lib/RT/AuthToken.pm
@@ -0,0 +1,372 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2019 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 strict;
+use warnings;
+use 5.10.1;
+
+package RT::AuthToken;
+use base 'RT::Record';
+
+require RT::User;
+require RT::Util;
+use Digest::SHA 'sha512_hex';
+
+=head1 NAME
+
+RT::AuthToken - Represents an authentication token for a user
+
+=cut
+
+=head1 METHODS
+
+=head2 Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database. Available
+keys are:
+
+=over 4
+
+=item Owner
+
+The user ID for whom this token will authenticate. If it's not the AuthToken
+object's CurrentUser, then the AdminUsers permission is required.
+
+=item Description
+
+A human-readable description of what this token will be used for.
+
+=back
+
+Returns a tuple of (status, msg) on failure and (id, msg, authstring) on
+success. Note that this is the only time the authstring will be directly
+readable (as it is stored in the database hashed like a password, so use
+this opportunity to capture it.
+
+=cut
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Owner => undef,
+ Description => '',
+ @_,
+ );
+
+ return (0, $self->loc("Permission Denied"))
+ unless $self->CurrentUserHasRight('ManageAuthTokens');
+
+ return (0, $self->loc("Owner required"))
+ unless $args{Owner};
+
+ return (0, $self->loc("Permission Denied"))
+ unless $args{Owner} == $self->CurrentUser->Id
+ || $self->CurrentUserHasRight('AdminUsers');
+
+ my $token = $self->_GenerateToken;
+
+ my ( $id, $msg ) = $self->SUPER::Create(
+ Token => $self->_CryptToken($token),
+ map { $_ => $args{$_} } grep {exists $args{$_}}
+ qw(Owner Description),
+ );
+ unless ($id) {
+ return (0, $self->loc("Authentication token create failed: [_1]", $msg));
+ }
+
+ my $authstring = $self->_BuildAuthString($self->Owner, $token);
+
+ return ($id, $self->loc('Authentication token created'), $authstring);
+}
+
+=head2 CurrentUserCanSee
+
+Returns true if the current user can see the AuthToken
+
+=cut
+
+sub CurrentUserCanSee {
+ my $self = shift;
+
+ return 0 unless $self->CurrentUserHasRight('ManageAuthTokens');
+
+ return 0 unless $self->__Value('Owner') == $self->CurrentUser->Id
+ || $self->CurrentUserHasRight('AdminUsers');
+
+ return 1;
+}
+
+=head2 SetOwner
+
+Not permitted
+
+=cut
+
+sub SetOwner {
+ my $self = shift;
+ return (0, $self->loc("Permission Denied"));
+}
+
+=head2 SetToken
+
+Not permitted
+
+=cut
+
+sub SetToken {
+ my $self = shift;
+ return (0, $self->loc("Permission Denied"));
+}
+
+=head2 Delete
+
+Checks ACL
+
+=cut
+
+sub Delete {
+ my $self = shift;
+ return (0, $self->loc("Permission Denied")) unless $self->CurrentUserCanSee;
+ my ($ok, $msg) = $self->SUPER::Delete(@_);
+ return ($ok, $self->loc("Authentication token revoked.")) if $ok;
+ return ($ok, $msg);
+}
+
+=head2 UpdateLastUsed
+
+Sets the "last used" time, without touching "last updated"
+
+=cut
+
+sub UpdateLastUsed {
+ my $self = shift;
+
+ my $now = RT::Date->new( $self->CurrentUser );
+ $now->SetToNow;
+
+ return $self->__Set(
+ Field => 'LastUsed',
+ Value => $now->ISO,
+ );
+}
+
+=head2 ParseAuthString AUTHSTRING
+
+Class method that takes as input an authstring and provides a tuple
+of (user id, token) on success, or the empty list on failure.
+
+=cut
+
+sub ParseAuthString {
+ my $class = shift;
+ my $input = shift;
+
+ my ($version) = $input =~ s/^([0-9]+)-//
+ or return;
+
+ if ($version == 1) {
+ my ($user_id, $token) = $input =~ /^([0-9]+)-([0-9a-f]{32})$/i
+ or return;
+ return ($user_id, $token);
+ }
+
+ return;
+}
+
+=head2 IsToken
+
+Analogous to L<RT::User/IsPassword>, without all of the legacy password
+forms.
+
+=cut
+
+sub IsToken {
+ my $self = shift;
+ my $value = shift;
+
+ my $stored = $self->__Value('Token');
+
+ # If it's a new-style (>= RT 4.0) password, it starts with a '!'
+ my (undef, $method, @rest) = split /!/, $stored;
+ if ($method eq "bcrypt") {
+ if (RT::Util->can('constant_time_eq')) {
+ return 0 unless RT::Util::constant_time_eq(
+ $self->_CryptToken_bcrypt($value, @rest),
+ $stored,
+ );
+ } else {
+ return 0 unless $self->_CryptToken_bcrypt($value, @rest) eq $stored;
+ }
+ # Upgrade to a larger number of rounds if necessary
+ return 1 unless $rest[0] < RT->Config->Get('BcryptCost');
+ }
+ else {
+ $RT::Logger->warn("Unknown hash method $method");
+ return 0;
+ }
+
+ # We got here by validating successfully, but with a legacy
+ # password form. Update to the most recent form.
+ $self->_Set(Field => 'Token', Value => $self->_CryptToken($value));
+ return 1;
+}
+
+=head2 LastUsedObj
+
+L</LastUsed> as an L<RT::Date> object.
+
+=cut
+
+sub LastUsedObj {
+ my $self = shift;
+ my $date = RT::Date->new($self->CurrentUser);
+ $date->Set(Format => 'sql', Value => $self->LastUsed);
+ return $date;
+}
+
+=head1 PRIVATE METHODS
+
+Documented for internal use only, do not call these from outside
+RT::AuthToken itself.
+
+=head2 _Set
+
+Checks if the current user can I<ManageAuthTokens> before calling
+C<SUPER::_Set>.
+
+=cut
+
+sub _Set {
+ my $self = shift;
+ my %args = (
+ Field => undef,
+ Value => undef,
+ @_
+ );
+
+ return (0, $self->loc("Permission Denied"))
+ unless $self->CurrentUserCanSee;
+
+ return $self->SUPER::_Set(@_);
+}
+
+=head2 _Value
+
+Checks L</CurrentUserCanSee> before calling C<SUPER::_Value>.
+
+=cut
+
+sub _Value {
+ my $self = shift;
+ return unless $self->CurrentUserCanSee;
+ return $self->SUPER::_Value(@_);
+}
+
+=head2 _GenerateToken
+
+Generates an unpredictable auth token
+
+=cut
+
+sub _GenerateToken {
+ my $class = shift;
+ require Time::HiRes;
+
+ my $input = join '',
+ Time::HiRes::time(), # subsecond-precision time
+ {}, # unpredictable memory address
+ rand(); # RNG
+
+ my $digest = sha512_hex($input);
+
+ return substr($digest, 0, 32);
+}
+
+=head2 _BuildAuthString
+
+Takes a user id and token and provides an authstring for use in place of
+a (username, password) combo.
+
+=cut
+
+sub _BuildAuthString {
+ my $self = shift;
+ my $version = 1;
+ my $userid = shift;
+ my $token = shift;
+
+ return $version . '-' . $userid . '-' . $token;
+}
+
+sub _CryptToken_bcrypt {
+ my $self = shift;
+ return $self->CurrentUser->UserObj->_GeneratePassword_bcrypt(@_);
+}
+
+sub _CryptToken {
+ my $self = shift;
+ return $self->_CryptToken_bcrypt(@_);
+}
+
+sub Table { "AuthTokens" }
+
+sub _CoreAccessible {
+ {
+ id => { read => 1, type => 'int(11)', default => '' },
+ Owner => { read => 1, type => 'int(11)', default => '0' },
+ Token => { read => 1, sql_type => 12, length => 256, is_blob => 0, is_numeric => 0, type => 'varchar(256)', default => ''},
+ Description => { read => 1, type => 'varchar(255)', default => '', write => 1 },
+ LastUsed => { read => 1, type => 'datetime', default => '', write => 1 },
+ Creator => { read => 1, type => 'int(11)', default => '0', auto => 1 },
+ 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 },
+ }
+}
+
+1;
diff --git a/lib/RT/AuthTokens.pm b/lib/RT/AuthTokens.pm
new file mode 100644
index 0000000000..6f5351ee10
--- /dev/null
+++ b/lib/RT/AuthTokens.pm
@@ -0,0 +1,99 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2019 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 strict;
+use warnings;
+
+package RT::AuthTokens;
+use base 'RT::SearchBuilder';
+
+=head1 NAME
+
+RT::AuthTokens - a collection of L<RT::AuthToken> objects
+
+=cut
+
+=head2 LimitOwner
+
+Limit Owner
+
+=cut
+
+sub LimitOwner {
+ my $self = shift;
+ my %args = (
+ FIELD => 'Owner',
+ OPERATOR => '=',
+ @_
+ );
+
+ $self->SUPER::Limit(%args);
+}
+
+sub NewItem {
+ my $self = shift;
+ return RT::AuthToken->new( $self->CurrentUser );
+}
+
+=head2 _Init
+
+Sets default ordering by id ascending.
+
+=cut
+
+sub _Init {
+ my $self = shift;
+
+ $self->OrderBy( FIELD => 'id', ORDER => 'ASC' );
+ return $self->SUPER::_Init( @_ );
+}
+
+sub Table { "AuthTokens" }
+
+1;
+
diff --git a/lib/RT/Authen/Token.pm b/lib/RT/Authen/Token.pm
new file mode 100644
index 0000000000..b10ebb9764
--- /dev/null
+++ b/lib/RT/Authen/Token.pm
@@ -0,0 +1,127 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2019 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 }}}
+
+package RT::Authen::Token;
+
+use strict;
+use warnings;
+
+use RT::System;
+
+'RT::System'->AddRight(Staff => ManageAuthTokens => 'Manage authentication tokens'); # loc
+
+use RT::AuthToken;
+use RT::AuthTokens;
+
+sub UserForAuthString {
+ my $self = shift;
+ my $authstring = shift;
+ my $user = shift;
+
+ my ($user_id, $cleartext_token) = RT::AuthToken->ParseAuthString($authstring);
+ return unless $user_id;
+
+ my $user_obj = RT::CurrentUser->new;
+ $user_obj->Load($user_id);
+ return if !$user_obj->Id || $user_obj->Disabled;
+
+ if (length $user) {
+ my $check_user = RT::CurrentUser->new;
+ $check_user->Load($user);
+ return unless $check_user->Id && $user_obj->Id == $check_user->Id;
+ }
+
+ my $tokens = RT::AuthTokens->new(RT->SystemUser);
+ $tokens->LimitOwner(VALUE => $user_id);
+ while (my $token = $tokens->Next) {
+ if ($token->IsToken($cleartext_token)) {
+ $token->UpdateLastUsed;
+ return ($user_obj, $token);
+ }
+ }
+
+ return;
+}
+
+=head1 NAME
+
+RT-Authen-Token - token-based authentication
+
+=head1 DESCRIPTION
+
+Allow for users to generate and login with authentication tokens. Users
+with the C<ManageAuthTokens> permission will see a new "Auth Tokens"
+menu item under "Logged in as ____" -> Settings. On that page they will
+be able to generate new tokens and modify or revoke existing tokens.
+
+Once you have an authentication token, you may use it in place of a
+password to log into RT. (Additionally, L<REST2> allows for using auth
+tokens with the C<Authorization: token> HTTP header.) One common use
+case is to use an authentication token as an application-specific
+password, so that you may revoke that application's access without
+disturbing other applications. You also need not change your password,
+since the application never received it.
+
+If you have the C<AdminUsers> permission, along with
+C<ManageAuthTokens>, you may generate, modify, and revoke tokens for
+other users as well by visiting Admin -> Users -> Select -> (user) ->
+Auth Tokens.
+
+Authentication tokens are stored securely (hashed and salted) in the
+database just like passwords, and so cannot be recovered after they are
+generated.
+
+=head2 Update your Apache configuration
+
+If you are running RT under Apache, add the following directive to your RT
+Apache configuration to allow RT to access the Authorization header.
+
+ SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
+=cut
+
+1;
diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index d8fe7af58d..ae4c4b4178 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -336,6 +336,8 @@ sub HandleRequest {
$HTML::Mason::Commands::m->comp( '/Elements/DoAuth', %$ARGS )
if @{ RT->Config->Get( 'ExternalAuthPriority' ) || [] };
+ AttemptTokenAuthentication($ARGS) unless _UserLoggedIn();
+
# Process per-page authentication callbacks
$HTML::Mason::Commands::m->callback( %$ARGS, CallbackName => 'Auth', CallbackPage => '/autohandler' );
@@ -867,6 +869,39 @@ sub AttemptPasswordAuthentication {
}
}
+sub AttemptTokenAuthentication {
+ my $ARGS = shift;
+ my ($pass, $user) = ('', '');
+ if ((RequestENV('HTTP_AUTHORIZATION')||'') =~ /^token (.*)$/i) {
+ $pass ||= $1;
+ my ($user_obj, $token) = RT::Authen::Token->UserForAuthString($pass, $user);
+ if ( $user_obj ) {
+ # log in
+ my $remote_addr = RequestENV('REMOTE_ADDR');
+ $RT::Logger->info("Successful login for @{[$user_obj->Name]} from $remote_addr using authentication token #@{[$token->Id]} (\"@{[$token->Description]}\")");
+
+ # It's important to nab the next page from the session before we blow
+ # the session away
+ my $next = RT::Interface::Web::RemoveNextPage($ARGS->{'next'});
+ $next = $next->{'url'} if ref $next;
+
+ RT::Interface::Web::InstantiateNewSession();
+ $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj;
+
+ # Really the only time we don't want to redirect here is if we were
+ # passed user and pass as query params in the URL.
+ if ($next) {
+ RT::Interface::Web::Redirect($next);
+ }
+ elsif ($ARGS->{'next'}) {
+ # Invalid hash, but still wants to go somewhere, take them to /
+ RT::Interface::Web::Redirect(RT->Config->Get('WebURL'));
+ }
+ }
+ }
+}
+
+
=head2 LoadSessionFromCookie
Load or setup a session cookie for the current user.
diff --git a/lib/RT/Interface/Web/MenuBuilder.pm b/lib/RT/Interface/Web/MenuBuilder.pm
index 7d0f04911c..67f44d16a6 100644
--- a/lib/RT/Interface/Web/MenuBuilder.pm
+++ b/lib/RT/Interface/Web/MenuBuilder.pm
@@ -296,6 +296,9 @@ sub BuildMainNav {
my $settings = $about_me->child( settings => title => loc('Settings'), path => '/Prefs/Other.html' );
$settings->child( options => title => loc('Preferences'), path => '/Prefs/Other.html' );
$settings->child( about_me => title => loc('About me'), path => '/Prefs/AboutMe.html' );
+ if ( $current_user->HasRight( Right => 'ManageAuthTokens', Object => RT->System ) ) {
+ $settings->child( auth_tokens => title => loc('Auth Tokens'), path => '/Prefs/AuthTokens.html' );
+ }
$settings->child( search_options => title => loc('Search options'), path => '/Prefs/SearchOptions.html' );
$settings->child( myrt => title => loc('RT at a glance'), path => '/Prefs/MyRT.html' );
$settings->child( dashboards_in_menu =>
@@ -1351,6 +1354,10 @@ sub _BuildAdminMenu {
$page->child( keys => title => loc('Private keys'), path => "/Admin/Users/Keys.html?id=" . $id );
}
$page->child( 'summary' => title => loc('User Summary'), path => "/User/Summary.html?id=" . $id );
+
+ if ( $current_user->HasRight( Right => 'ManageAuthTokens', Object => RT->System ) ) {
+ $page->child( auth_tokens => title => loc('Auth Tokens'), path => '/Admin/Users/AuthTokens.html?id=' . $id );
+ }
}
}
commit 9da5b21eb2389aced08b0ddabe39941ba9d29522
Author: sunnavy <sunnavy at bestpractical.com>
Date: Tue May 12 03:52:43 2020 +0800
Upgrade charset of AuthTokens table to utf8mb4 for MySQL/MariaDB
diff --git a/etc/schema.mysql b/etc/schema.mysql
index 69cb029093..6c368b7909 100644
--- a/etc/schema.mysql
+++ b/etc/schema.mysql
@@ -587,6 +587,6 @@ CREATE TABLE AuthTokens (
LastUpdatedBy int(11) NOT NULL DEFAULT 0,
LastUpdated datetime DEFAULT NULL,
PRIMARY KEY (id)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+) ENGINE=InnoDB CHARACTER SET utf8mb4;
CREATE INDEX AuthTokensOwner ON AuthTokens (Owner);
diff --git a/etc/upgrade/4.5.7/schema.mysql b/etc/upgrade/4.5.7/schema.mysql
index 0bf9a499ae..aab6f75c5c 100644
--- a/etc/upgrade/4.5.7/schema.mysql
+++ b/etc/upgrade/4.5.7/schema.mysql
@@ -9,6 +9,6 @@ CREATE TABLE AuthTokens (
LastUpdatedBy int(11) NOT NULL DEFAULT 0,
LastUpdated datetime DEFAULT NULL,
PRIMARY KEY (id)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+) ENGINE=InnoDB CHARACTER SET utf8mb4;
CREATE INDEX AuthTokensOwner ON AuthTokens (Owner);
commit 8b412ee946e2c5097bf17c508caaaaa40d15c47c
Author: sunnavy <sunnavy at bestpractical.com>
Date: Tue May 12 03:24:43 2020 +0800
Reduce our z-index to coordinate with ones in bootstrap
Bootstrap has correctly ordered z-index settings, e.g. .modal is 1050,
.tooltip is 1070. Previously we overrode .modal's z-index with 10000,
which caused tooltips to not show up in modals.
This commit resets .modal's z-index and reduces our nav's
correspondingly so .modal could cover nav.
diff --git a/share/static/css/elevator-light/login.css b/share/static/css/elevator-light/login.css
index 6a43419b5b..f7255ebef8 100644
--- a/share/static/css/elevator-light/login.css
+++ b/share/static/css/elevator-light/login.css
@@ -13,7 +13,7 @@
#quick-personal {
position: absolute;
- z-index: 9999;
+ z-index: 1000;
left: 0;
/* This avoids a very weird bug in Chrome where opening a select causes a
* hover event at (0,0), which will be over top of the menu sometimes */
diff --git a/share/static/css/elevator-light/misc.css b/share/static/css/elevator-light/misc.css
index 60a0837e98..f37e36834f 100644
--- a/share/static/css/elevator-light/misc.css
+++ b/share/static/css/elevator-light/misc.css
@@ -105,7 +105,6 @@ textarea.messagebox, #cke_Content, #cke_UpdateContent {
.modal {
background: rgb(0, 0, 0, .70);
- z-index: 10000;
}
/* manipulate the svg image for selected bookmarks */
diff --git a/share/static/css/elevator-light/nav.css b/share/static/css/elevator-light/nav.css
index d6450cb24c..d945d18e11 100644
--- a/share/static/css/elevator-light/nav.css
+++ b/share/static/css/elevator-light/nav.css
@@ -78,7 +78,7 @@ ul.sf-menu li {
position: absolute;
top: 1px;
left: 0;
- z-index: 9999;
+ z-index: 1000;
text-color: #000;
}
commit e1dbab219c6b4161c324dd2814e146c6850d3a48
Author: Craig <craig at bestpractical.com>
Date: Thu May 7 10:43:41 2020 -0400
Re-implement Authen-Token web UI to work like other RT pages
diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index ae4c4b4178..ef5b059bec 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -4983,6 +4983,78 @@ sub ProcessCustomDateRanges {
return @results;
}
+=head2 ProcessAuthToken ARGSRef => ARGSREF
+
+Returns an array of results messages.
+
+=cut
+
+sub ProcessAuthToken {
+ my %args = (
+ ARGSRef => undef,
+ @_
+ );
+ my $args_ref = $args{ARGSRef};
+
+ my @results;
+ my $token = RT::AuthToken->new( $session{CurrentUser} );
+
+ if ( $args_ref->{Create} ) {
+
+ # Don't require password for systems with some form of federated auth
+ my %res = $session{'CurrentUser'}->CurrentUserRequireToSetPassword();
+
+ if ( !length( $args_ref->{Description} ) ) {
+ push @results, loc("Description cannot be blank.");
+ }
+ elsif ( $res{'CanSet'} && !length( $args_ref->{Password} ) ) {
+ push @results, loc("Please enter your current password.");
+ }
+ elsif ( $res{'CanSet'} && !$session{CurrentUser}->IsPassword( $args_ref->{Password} ) ) {
+ push @results, loc("Please enter your current password correctly.");
+ }
+ else {
+ my ( $ok, $msg, $auth_string ) = $token->Create(
+ Owner => $args_ref->{Owner},
+ Description => $args_ref->{Description},
+ );
+ if ($ok) {
+ }
+ push @results, $msg;
+ push @results,
+ loc(
+ '"[_1]" is your new authentication token. Treat it carefully like a password. Please save it now because you cannot access it again.',
+ $auth_string
+ );
+ }
+ }
+ elsif ( $args_ref->{Update} || $args_ref->{Revoke} ) {
+
+ $token->Load( $args_ref->{Token} );
+ if ( $token->Id ) {
+ if ( $args_ref->{Update} ) {
+ if ( length( $args_ref->{Description} ) ) {
+ if ( $args_ref->{Description} ne $token->Description ) {
+ my ( $ok, $msg ) = $token->SetDescription( $args_ref->{Description} );
+ push @results, $msg;
+ }
+ }
+ else {
+ push @results, loc("Description cannot be blank.");
+ }
+ }
+ elsif ( $args_ref->{Revoke} ) {
+ my ( $ok, $msg ) = $token->Delete;
+ push @results, $msg;
+ }
+ }
+ else {
+ push @results, loc("Could not find token: [_1]", $args_ref->{Token});
+ }
+ }
+ return @results;
+}
+
package RT::Interface::Web;
RT::Base->_ImportOverlays();
diff --git a/lib/RT/Interface/Web/MenuBuilder.pm b/lib/RT/Interface/Web/MenuBuilder.pm
index 67f44d16a6..1e42097388 100644
--- a/lib/RT/Interface/Web/MenuBuilder.pm
+++ b/lib/RT/Interface/Web/MenuBuilder.pm
@@ -329,6 +329,12 @@ sub BuildMainNav {
$page->child(
custom_date_ranges => title => loc('Custom Date Ranges'),
path => "/Prefs/CustomDateRanges.html"
+ )
+ }
+
+ if ( $request_path =~ m{^/Prefs/AuthTokens\.html} ) {
+ $page->child( create_auth_token => title => loc('Create'),
+ raw_html => q[<a class="btn menu-item" href="#create-auth-token" data-toggle="modal" rel="modal:open">].loc("Create")."</a>"
);
}
}
@@ -1356,7 +1362,23 @@ sub _BuildAdminMenu {
$page->child( 'summary' => title => loc('User Summary'), path => "/User/Summary.html?id=" . $id );
if ( $current_user->HasRight( Right => 'ManageAuthTokens', Object => RT->System ) ) {
- $page->child( auth_tokens => title => loc('Auth Tokens'), path => '/Admin/Users/AuthTokens.html?id=' . $id );
+ my $auth_tokens = $page->child(
+ auth_tokens => title => loc('Auth Tokens'),
+ path => '/Admin/Users/AuthTokens.html?id=' . $id
+ );
+
+ if ( $request_path =~ m{^/Admin/Users/AuthTokens\.html} ) {
+ $auth_tokens->child(
+ select_auth_token => title => loc('Select'),
+ path => '/Admin/Users/AuthTokens.html?id=' . $id,
+ );
+ $auth_tokens->child(
+ create_auth_token => title => loc('Create'),
+ raw_html =>
+ q[<a class="btn menu-item" href="#create-auth-token" data-toggle="modal" rel="modal:open">]
+ . loc("Create") . "</a>"
+ );
+ }
}
}
}
diff --git a/share/html/Admin/Users/AuthTokens.html b/share/html/Admin/Users/AuthTokens.html
new file mode 100644
index 0000000000..db9adbd30b
--- /dev/null
+++ b/share/html/Admin/Users/AuthTokens.html
@@ -0,0 +1,71 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 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 }}}
+<& /Admin/Elements/Header, Title => loc("[_1]'s authentication tokens",$UserObj->Name) &>
+<& /Elements/Tabs &>
+<& /Elements/ListActions, actions => \@results &>
+
+<& /Elements/AuthToken/List, %ARGS, Owner => $id &>
+
+<%ARGS>
+$id => undef
+</%ARGS>
+<%INIT>
+my $UserObj = RT::User->new( $session{'CurrentUser'} );
+$UserObj->Load( $id );
+unless ( $UserObj->id ) {
+ Abort( loc("Couldn't load user #[_1]", $id) );
+}
+$id = $ARGS{'id'} = $UserObj->id;
+
+my @results = ProcessAuthToken(ARGSRef => \%ARGS);
+MaybeRedirectForResults(
+ Actions => \@results,
+ Arguments => { id => $id },
+);
+
+</%INIT>
diff --git a/share/html/Elements/AuthToken/Create b/share/html/Elements/AuthToken/Create
new file mode 100644
index 0000000000..d94b60aadf
--- /dev/null
+++ b/share/html/Elements/AuthToken/Create
@@ -0,0 +1,99 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 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="create-auth-token">
+ <div class="modal-dialog modal-dialog-centered" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title"><&|/l&>Create auth token</&></h5>
+ <a id="auth-token-close-modal" href="javascript:void(0)" class="close" data-dismiss="modal" aria-label="Close">
+ <span aria-hidden="true">×</span>
+ </a>
+ </div>
+ <div class="modal-body">
+ <form method="POST">
+ <input type="hidden" name="Owner" value="<% $Owner %>">
+% if ( $res{'CanSet'} ){
+ <div class="form-row">
+ <div class="label col-4">
+ <&|/l, $session{'CurrentUser'}->Name()&>[_1]'s current password</&>:
+ </div>
+ <div class="value col-8">
+ <input class="form-control" type="password" name="Password" size="16" autocomplete="off" /></td>
+ </div>
+ </div>
+% }
+ <div class="form-row">
+ <div class="label col-4">
+ <&|/l&>Description</&>:
+ <span class="far fa-question-circle icon-helper" data-toggle="tooltip" data-placement="top" data-original-title="<% loc("What's this token for?") %>"></span>
+ </div>
+ <div class="value col-8">
+ <input class="form-control" type="text" name="Description" value="<% $Description %>" size="16" />
+ </div>
+ </div>
+
+ <div class="form-row">
+ <div class="col-12">
+ <& /Elements/Submit, Label => loc("Create"), Name => 'Create' &>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+</div>
+
+<%INIT>
+# Don't require password for systems with some form of federated auth
+my %res = $session{'CurrentUser'}->CurrentUserRequireToSetPassword();
+</%INIT>
+
+<%ARGS>
+$Owner
+$Description => ''
+</%ARGS>
diff --git a/share/html/Elements/AuthToken/Edit b/share/html/Elements/AuthToken/Edit
new file mode 100644
index 0000000000..870c562c82
--- /dev/null
+++ b/share/html/Elements/AuthToken/Edit
@@ -0,0 +1,85 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 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="edit-auth-token-<% $Token->id %>">
+ <div class="modal-dialog modal-dialog-centered" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title"><&|/l&>Update auth token</&></h5>
+ <a id="auth-token-close-modal" href="javascript:void(0)" class="close" data-dismiss="modal" aria-label="Close">
+ <span aria-hidden="true">×</span>
+ </a>
+ </div>
+ <div class="modal-body">
+ <form method="POST">
+ <input type="hidden" name="Token" value="<% $Token->Id %>">
+ <div class="form-row">
+ <div class="label col-4">
+ <&|/l&>Description</&>:
+ <span class="far fa-question-circle icon-helper" data-toggle="tooltip" data-placement="top" data-original-title="<% loc("What's this token for?") %>"></span>
+
+ </div>
+ <div class="value col-8">
+ <input class="form-control" type="text" name="Description" value="<% $Token->Description %>" size="16" />
+ </div>
+ </div>
+
+ <div class="form-row justify-content-end">
+ <div class="col-auto">
+ <input type="submit" class="button btn btn-primary" name="Revoke" value="<% loc('Revoke') %>" />
+ <input type="submit" class="button btn btn-primary" name="Update" value="<% loc('Update') %>" />
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+</div>
+
+<%ARGS>
+$Token
+</%ARGS>
diff --git a/share/html/Elements/AuthToken/Help b/share/html/Elements/AuthToken/Help
new file mode 100644
index 0000000000..282489ff54
--- /dev/null
+++ b/share/html/Elements/AuthToken/Help
@@ -0,0 +1,52 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 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="form-row">
+ <div class="col-12">
+ <p><&|/l&>Authentication tokens allow other applications to use your user account without having to share your password, while allowing you to revoke access on an application-specific basis. Changing your password <em>does not</em> invalidate your auth tokens; you must revoke them here.</&></p>
+ </div>
+</div>
diff --git a/share/html/Elements/AuthToken/List b/share/html/Elements/AuthToken/List
new file mode 100644
index 0000000000..59d1196897
--- /dev/null
+++ b/share/html/Elements/AuthToken/List
@@ -0,0 +1,84 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 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 }}}
+<& /Elements/AuthToken/Help &>
+<& /Elements/AuthToken/Create, Owner => $Owner &>
+
+<div class="authtoken-list mx-auto max-width-sm" data-owner="<% $Owner %>">
+% if ($tokens->Count == 0) {
+ <p class="mt-3 mb-1 ml-3"><&|/l&>No authentication tokens.</&></p>
+% } else {
+ <ul class="list-group">
+% while (my $token = $tokens->Next) {
+ <& Edit, Token => $token &>
+ <li class="list-group-item" id="token-<% $token->Id %>">
+ <div class="d-inline-block mt-1">
+ <span class="description font-weight-bold"><% $token->Description %></span>
+ <span class="last-used font-italic ml-2">
+% my $used = $token->LastUsedObj;
+% if ( $used->IsSet ) {
+ <&|/l, $used->AgeAsString &>used [_1]</&>
+% } else {
+ <&|/l&>never used</&>
+% }
+ </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>
+% }
+ </ul>
+% }
+</div>
+
+<%INIT>
+my $tokens = RT::AuthTokens->new($session{CurrentUser});
+$tokens->LimitOwner(VALUE => $Owner);
+</%INIT>
+
+<%ARGS>
+$Owner
+</%ARGS>
diff --git a/share/html/Prefs/AuthTokens.html b/share/html/Prefs/AuthTokens.html
new file mode 100644
index 0000000000..23b3a2be1c
--- /dev/null
+++ b/share/html/Prefs/AuthTokens.html
@@ -0,0 +1,58 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2019 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 }}}
+<& /Elements/Header, Title => loc('My authentication tokens') &>
+<& /Elements/Tabs &>
+<& /Elements/ListActions, actions => \@results &>
+
+<& /Elements/AuthToken/List, %ARGS, Owner => $session{'CurrentUser'}->Id &>
+
+<%INIT>
+my @results = ProcessAuthToken(ARGSRef => \%ARGS);
+
+MaybeRedirectForResults( Actions => \@results );
+</%INIT>
commit b0c9daf3fbb08dca6c5486e6e486b2d4f621260a
Author: Craig <craig at bestpractical.com>
Date: Thu May 7 14:18:35 2020 -0400
Add documentation for using token auth
diff --git a/docs/authentication.pod b/docs/authentication.pod
index 882805ed21..eba5b36be1 100644
--- a/docs/authentication.pod
+++ b/docs/authentication.pod
@@ -15,6 +15,24 @@ may be all you need. The administration pages under Admin → Users
provide new user creation as well as password setting and control of RT's
privileged flag for existing users.
+=head1 Token Authentication
+
+Authentication tokens are typically used for accessing RT's REST APIs,
+often L<RT::REST2>. To set up token access, first select an RT user
+account you will use when accessing APIs and give that user account
+appropriate rights to operate on tickets based on what you plan to do
+(read ticket information, create tickets, update tickets, etc.).
+
+You can then give that user the right ManageAuthTokens which will
+add a new option in the menu Logged in as > Settings > AuthTokens.
+
+When setting up token authentication, add the following directive to
+your RT Apache configuration to allow RT to access the Authorization header.
+
+ SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
+
+You can find more information about tokens in L<RT::Authen::Token>.
+
=head1 External Authentication
There are two primary types of external authentication: in one you type your
@@ -85,7 +103,6 @@ An example of using LDAP authentication and HTTP Basic auth:
Require local
</Location>
-
=head3 RT Configuration Options
All of the following options control the behavior of RT's built-in external
diff --git a/docs/web_deployment.pod b/docs/web_deployment.pod
index 779401f985..3ba4f83e71 100644
--- a/docs/web_deployment.pod
+++ b/docs/web_deployment.pod
@@ -103,6 +103,16 @@ C<SetHandler modperl>, as the example below uses.
</Perl>
</VirtualHost>
+=head3 Token Authentication
+
+If you plan to set up token-based access, possibly to use L<RT::REST2>,
+add the following directive to your RT Apache configuration to allow
+RT to access the Authorization header.
+
+ SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
+
+More information is available in L<RT::Authen::Token>.
+
=head2 nginx
C<nginx> requires that you start RT's fastcgi process externally, for
commit 52f4a9e4671213bcc4ea9f0dc6c8e25879806fa3
Author: Jim Brandt <jbrandt at bestpractical.com>
Date: Wed May 13 16:51:56 2020 -0400
Show an error message on create failure
Previously the $ok check was an empty block, so failures would
show the failure message and then "new token" message.
Show appropriate messages on success, and a generic error message
on failure. Error details are logged.
diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index ef5b059bec..35aa50d5d0 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -5019,13 +5019,17 @@ sub ProcessAuthToken {
Description => $args_ref->{Description},
);
if ($ok) {
+ push @results, $msg;
+ push @results,
+ loc(
+ '"[_1]" is your new authentication token. Treat it carefully like a password. Please save it now because you cannot access it again.',
+ $auth_string
+ );
+ }
+ else {
+ push @results, loc('Unable to create a new authentication token. Contact your RT administrator.');
+ RT->Logger->error('Unable to create authentication token: ' . $msg);
}
- push @results, $msg;
- push @results,
- loc(
- '"[_1]" is your new authentication token. Treat it carefully like a password. Please save it now because you cannot access it again.',
- $auth_string
- );
}
}
elsif ( $args_ref->{Update} || $args_ref->{Revoke} ) {
-----------------------------------------------------------------------
More information about the rt-commit
mailing list