[rt-users] Searching ticket subjects
Kenneth Marshall
ktm at rice.edu
Tue Jul 12 14:33:18 EDT 2016
On Tue, Jul 12, 2016 at 11:06:44PM +0530, Nilesh wrote:
> ...
> > > > Does the default RT strip that off? Because I'm not seeing in one recent
> > > > duplicate created in the manner I described.
> > > >
> > > Ah it's hidden inside 'show full headers'. This is interesting. Is there
> > > some documentation about this header?
> > Hi,
> >
> > You can look at the RFC's, but that was the header that provided the most
> > value in reducing duplicate tickets.
> >
> > Regards,
> > Ken
>
> As this discussion is going on a few duplicate tickets were created in the
> system and this definitely looks like a good way to solve it. Did you use
> ExtractCustomFields module to get this done?
>
> I'm thinking of doing it like this:
> Run ExtractCustomFields on every correspond to get the Message ID out of the
> headers. Then I can search through this field probably using a customized rt-
> mailgate. Does that sound good? I don't think there's a way using scrips because
> by the time scrip executes ticket is already created (unless something is
> possible in the "On transaction" condition).
>
Hi Nilesh,
I have attached my current version of ./local/lib/RT/Interface/Email_Local.pm.
It includes our modifications gathered from the list recommendations in the function
ParseInReplyTo()
This patch is against RT 3.8.13+ and has not been updated to RT 4+ but is should
be a straight-forward update. To make your own version of Email_Local.pm, start
with just the function ParseInReplyTo() and the calling function and add functions
from ./lib/RT/Interface/Email.pm until you have a working file. That will be the
minimum that you will need. Take a look at http://requesttracker.wikia.com/wiki/Customizing
for more details. Let me know if you have any questions.
Regards,
Ken
-------------- next part --------------
package RT::Interface::Email;
use strict;
no warnings qw(redefine);
use Email::Address;
use MIME::Entity;
use RT::EmailParser;
use File::Temp;
use UNIVERSAL::require;
use Mail::Mailer ();
use Text::ParseWords qw/shellwords/;
sub ParseTicketId {
my $Subject = shift;
my $rtname = RT->Config->Get('rtname');
my $test_name = RT->Config->Get('EmailSubjectTagRegex') || qr/\Q$rtname\E/i;
my $id;
if ( $Subject =~ s/\[$test_name\s+\#(\d+)\s*\]//i ) {
$id = $1;
} else {
foreach my $tag ( RT->System->SubjectTag ) {
next unless $Subject =~ s/\[\Q$tag\E\s+\#(\d+)\s*\]//i;
$id = $1;
last;
}
}
return undef unless $id;
$RT::Logger->debug("Found a ticket ID. It's $id");
return $id;
}
sub ExtractTicketId {
my $entity = shift;
my $subject = $entity->head->get('Subject') || '';
chomp $subject;
return ParseTicketId( $subject );
}
sub ParseInReplyTo {
my $MessageId = shift;
$MessageId =~ s/<(.+)>/$1/;
my $id;
if ($MessageId ne '') {
$RT::Logger->debug("Looking for matching ticket using In-Reply-To: $MessageId");
my $query = "SELECT ObjectId" .
" FROM Transactions JOIN Attachments" .
" ON Attachments.TransactionId = Transactions.id" .
" WHERE ObjectType = 'RT::Ticket'" .
" AND MessageId = ?;";
my @result = $RT::Handle->FetchResult($query, $MessageId);
if ( $result[0] =~ /^\d+$/ ) {
$id = $result[0];
$RT::Logger->debug("Found a ticket ID using In-Reply-To header. It's $id");
}
}
return undef unless defined($id);
return $id;
}
sub ParseCcAddressesFromHead {
my %args = (
Head => undef,
QueueObj => undef,
CurrentUser => undef,
@_
);
my @recipients =
map lc $_->address,
map Email::Address->parse( $args{'Head'}->get( $_ ) ),
qw(To Cc);
my @res;
foreach my $address ( @recipients ) {
$address = $args{'CurrentUser'}->UserObj->CanonicalizeEmailAddress( $address );
next if lc $args{'CurrentUser'}->EmailAddress eq $address;
next if lc $args{'QueueObj'}->CorrespondAddress eq $address;
next if lc $args{'QueueObj'}->CommentAddress eq $address;
next if RT::EmailParser->IsRTAddress( $address );
push @res, $address;
}
#
# Limit the number of Cc addresses that we add to a
# ticket during the initial create to minimize damage
# to our Email reputation when SPAM slips through DSPAM.
$RT::Logger->debug("$#res Ccs");
if ( $#res > 3 ) {
my @riceCc;
my @nonriceCc;
@riceCc = grep /rice.edu/i, @res;
@nonriceCc = grep !/rice.edu/i, @res;
$RT::Logger->debug("$#riceCc riceCcs, $#nonriceCc nonriceCcs");
if ($#nonriceCc > 1) {
@res = (@riceCc, @nonriceCc[0]);
}
}
return @res;
}
sub Gateway {
my $argsref = shift;
my %args = (
action => 'correspond',
queue => '1',
ticket => undef,
message => undef,
%$argsref
);
my $SystemTicket;
my $Right;
# Validate the action
my ( $status, @actions ) = IsCorrectAction( $args{'action'} );
unless ($status) {
return (
-75,
"Invalid 'action' parameter "
. $actions[0]
. " for queue "
. $args{'queue'},
undef
);
}
my $parser = RT::EmailParser->new();
$parser->SmartParseMIMEEntityFromScalar(
Message => $args{'message'},
Decode => 0,
Exact => 1,
);
my $Message = $parser->Entity();
unless ($Message) {
MailError(
Subject => "RT Bounce: Unparseable message",
Explanation => "RT couldn't process the message below",
Attach => $args{'message'}
);
return ( 0,
"Failed to parse this message. Something is likely badly wrong with the message"
);
}
my @mail_plugins = grep $_, RT->Config->Get('MailPlugins');
push @mail_plugins, "Auth::MailFrom" unless @mail_plugins;
@mail_plugins = _LoadPlugins( @mail_plugins );
my %skip_plugin;
foreach my $class( grep !ref, @mail_plugins ) {
# check if we should apply filter before decoding
my $check_cb = do {
no strict 'refs';
*{ $class . "::ApplyBeforeDecode" }{CODE};
};
next unless defined $check_cb;
next unless $check_cb->(
Message => $Message,
RawMessageRef => \$args{'message'},
);
$skip_plugin{ $class }++;
my $Code = do {
no strict 'refs';
*{ $class . "::GetCurrentUser" }{CODE};
};
my ($status, $msg) = $Code->(
Message => $Message,
RawMessageRef => \$args{'message'},
);
next if $status > 0;
if ( $status == -2 ) {
return (1, $msg, undef);
} elsif ( $status == -1 ) {
return (0, $msg, undef);
}
}
@mail_plugins = grep !$skip_plugin{"$_"}, @mail_plugins;
$parser->_DecodeBodies;
$parser->_PostProcessNewEntity;
my $head = $Message->head;
my $ErrorsTo = ParseErrorsToAddressFromHead( $head );
my $MessageId = $head->get('Message-ID')
|| "<no-message-id-". time . rand(2000) .'@'. RT->Config->Get('Organization') .'>';
#Pull apart the subject line
my $Subject = $head->get('Subject') || '';
chomp $Subject;
# Lets check for mail loops of various sorts.
my ($should_store_machine_generated_message, $IsALoop, $result);
( $should_store_machine_generated_message, $ErrorsTo, $result, $IsALoop ) =
_HandleMachineGeneratedMail(
Message => $Message,
ErrorsTo => $ErrorsTo,
Subject => $Subject,
MessageId => $MessageId
);
# Do not pass loop messages to MailPlugins, to make sure the loop
# is broken, unless $RT::StoreLoops is set.
if ($IsALoop && !$should_store_machine_generated_message) {
return ( 0, $result, undef );
}
# }}}
my $InReplyToMessageId = $head->get('In-Reply-To') || '';
chomp($InReplyToMessageId);
$args{'ticket'} ||= ExtractTicketId( $Message );
$args{'ticket'} ||= ParseInReplyTo( $InReplyToMessageId );
# ExtractTicketId may have been overridden, and edited the Subject
my $NewSubject = $Message->head->get('Subject');
chomp $NewSubject;
$SystemTicket = RT::Ticket->new( RT->SystemUser );
$SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
if ( $SystemTicket->id ) {
$Right = 'ReplyToTicket';
} else {
$Right = 'CreateTicket';
}
#Set up a queue object
my $SystemQueueObj = RT::Queue->new( RT->SystemUser );
$SystemQueueObj->Load( $args{'queue'} );
# We can safely have no queue of we have a known-good ticket
unless ( $SystemTicket->id || $SystemQueueObj->id ) {
return ( -75, "RT couldn't find the queue: " . $args{'queue'}, undef );
}
my ($AuthStat, $CurrentUser, $error) = GetAuthenticationLevel(
MailPlugins => \@mail_plugins,
Actions => \@actions,
Message => $Message,
RawMessageRef => \$args{message},
SystemTicket => $SystemTicket,
SystemQueue => $SystemQueueObj,
);
# If authentication fails and no new user was created, get out.
if ( !$CurrentUser || !$CurrentUser->id || $AuthStat == -1 ) {
# If the plugins refused to create one, they lose.
unless ( $AuthStat == -1 ) {
_NoAuthorizedUserFound(
Right => $Right,
Message => $Message,
Requestor => $ErrorsTo,
Queue => $args{'queue'}
);
}
return ( 0, "Could not load a valid user", undef );
}
# If we got a user, but they don't have the right to say things
if ( $AuthStat == 0 ) {
MailError(
To => $ErrorsTo,
Subject => "Permission Denied",
Explanation =>
"You do not have permission to communicate with RT",
MIMEObj => $Message
);
return (
0,
"$ErrorsTo tried to submit a message to "
. $args{'Queue'}
. " without permission.",
undef
);
}
unless ($should_store_machine_generated_message) {
return ( 0, $result, undef );
}
# if plugin's updated SystemTicket then update arguments
$args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
my $Ticket = RT::Ticket->new($CurrentUser);
if ( !$args{'ticket'} && grep /^(comment|correspond)$/, @actions )
{
my @Cc;
my @Requestors = ( $CurrentUser->id );
if (RT->Config->Get('ParseNewMessageForTicketCcs')) {
@Cc = ParseCcAddressesFromHead(
Head => $head,
CurrentUser => $CurrentUser,
QueueObj => $SystemQueueObj
);
}
my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
Queue => $SystemQueueObj->Id,
Subject => $NewSubject,
Requestor => \@Requestors,
Cc => \@Cc,
MIMEObj => $Message
);
if ( $id == 0 ) {
MailError(
To => $ErrorsTo,
Subject => "Ticket creation failed: $Subject",
Explanation => $ErrStr,
MIMEObj => $Message
);
return ( 0, "Ticket creation failed: $ErrStr", $Ticket );
}
# strip comments&corresponds from the actions we don't need
# to record them if we've created the ticket just now
@actions = grep !/^(comment|correspond)$/, @actions;
$args{'ticket'} = $id;
} elsif ( $args{'ticket'} ) {
$Ticket->Load( $args{'ticket'} );
unless ( $Ticket->Id ) {
my $error = "Could not find a ticket with id " . $args{'ticket'};
MailError(
To => $ErrorsTo,
Subject => "Message not recorded: $Subject",
Explanation => $error,
MIMEObj => $Message
);
return ( 0, $error );
}
$args{'ticket'} = $Ticket->id;
} else {
return ( 1, "Success", $Ticket );
}
# }}}
my $unsafe_actions = RT->Config->Get('UnsafeEmailCommands');
foreach my $action (@actions) {
# If the action is comment, add a comment.
if ( $action =~ /^(?:comment|correspond)$/i ) {
my $method = ucfirst lc $action;
my ( $status, $msg ) = $Ticket->$method( MIMEObj => $Message );
unless ($status) {
#Warn the sender that we couldn't actually submit the comment.
MailError(
To => $ErrorsTo,
Subject => "Message not recorded: $Subject",
Explanation => $msg,
MIMEObj => $Message
);
return ( 0, "Message not recorded: $msg", $Ticket );
}
} elsif ($unsafe_actions) {
my ( $status, $msg ) = _RunUnsafeAction(
Action => $action,
ErrorsTo => $ErrorsTo,
Message => $Message,
Ticket => $Ticket,
CurrentUser => $CurrentUser,
);
return ($status, $msg, $Ticket) unless $status == 1;
}
}
return ( 1, "Success", $Ticket );
}
1;
More information about the rt-users
mailing list