diff options
Diffstat (limited to 'rt/lib/RT/Test/GnuPG.pm')
-rw-r--r-- | rt/lib/RT/Test/GnuPG.pm | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/rt/lib/RT/Test/GnuPG.pm b/rt/lib/RT/Test/GnuPG.pm new file mode 100644 index 0000000..6cebb77 --- /dev/null +++ b/rt/lib/RT/Test/GnuPG.pm @@ -0,0 +1,360 @@ +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC +# <sales@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::Test::GnuPG; +use strict; +use Test::More; +use base qw(RT::Test); +use File::Temp qw(tempdir); + +our @EXPORT = + qw(create_a_ticket update_ticket cleanup_headers set_queue_crypt_options + check_text_emails send_email_and_check_transaction + create_and_test_outgoing_emails + ); + +sub import { + my $class = shift; + my %args = @_; + my $t = $class->builder; + + $t->plan( skip_all => 'GnuPG required.' ) + unless eval { require GnuPG::Interface; 1 }; + $t->plan( skip_all => 'gpg executable is required.' ) + unless RT::Test->find_executable('gpg'); + + require RT::Crypt::GnuPG; + $class->SUPER::import(%args); + + RT::Test::diag "GnuPG --homedir " . RT->Config->Get('GnuPGOptions')->{'homedir'}; + + $class->set_rights( + Principal => 'Everyone', + Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ReplyToTicket', 'ModifyTicket'], + ); + + $class->export_to_level(1); +} + +sub bootstrap_more_config { + my $self = shift; + my $handle = shift; + my $args = shift; + + $self->SUPER::bootstrap_more_config($handle, $args, @_); + + my %gnupg_options = ( + 'no-permission-warning' => undef, + $args->{gnupg_options} ? %{ $args->{gnupg_options} } : (), + ); + $gnupg_options{homedir} ||= scalar tempdir( CLEANUP => 1 ); + + use Data::Dumper; + local $Data::Dumper::Terse = 1; # "{...}" instead of "$VAR1 = {...};" + my $dumped_gnupg_options = Dumper(\%gnupg_options); + + print $handle qq{ +Set(\%GnuPG, ( + Enable => 1, + OutgoingMessagesFormat => 'RFC', +)); +Set(\%GnuPGOptions => \%{ $dumped_gnupg_options }); +Set(\@MailPlugins => qw(Auth::MailFrom Auth::GnuPG)); +}; + +} + +sub create_a_ticket { + my $queue = shift; + my $mail = shift; + my $m = shift; + my %args = (@_); + + RT::Test->clean_caught_mails; + + $m->goto_create_ticket( $queue ); + $m->form_name('TicketCreate'); + $m->field( Subject => 'test' ); + $m->field( Requestors => 'rt-test@example.com' ); + $m->field( Content => 'Some content' ); + + foreach ( qw(Sign Encrypt) ) { + if ( $args{ $_ } ) { + $m->tick( $_ => 1 ); + } else { + $m->untick( $_ => 1 ); + } + } + + $m->submit; + is $m->status, 200, "request successful"; + + $m->content_lacks("unable to sign outgoing email messages"); + + + my @mail = RT::Test->fetch_caught_mails; + check_text_emails(\%args, @mail ); + categorize_emails($mail, \%args, @mail ); +} + +sub update_ticket { + my $tid = shift; + my $mail = shift; + my $m = shift; + my %args = (@_); + + RT::Test->clean_caught_mails; + + $m->get( $m->rt_base_url . "/Ticket/Update.html?Action=Respond&id=$tid" ); + $m->form_number(3); + $m->field( UpdateContent => 'Some content' ); + + foreach ( qw(Sign Encrypt) ) { + if ( $args{ $_ } ) { + $m->tick( $_ => 1 ); + } else { + $m->untick( $_ => 1 ); + } + } + + $m->click('SubmitTicket'); + is $m->status, 200, "request successful"; + $m->content_contains("Message recorded", 'Message recorded') or diag $m->content; + + + my @mail = RT::Test->fetch_caught_mails; + check_text_emails(\%args, @mail ); + categorize_emails($mail, \%args, @mail ); +} + +sub categorize_emails { + my $mail = shift; + my $args = shift; + my @mail = @_; + + if ( $args->{'Sign'} && $args->{'Encrypt'} ) { + push @{ $mail->{'signed_encrypted'} }, @mail; + } + elsif ( $args->{'Sign'} ) { + push @{ $mail->{'signed'} }, @mail; + } + elsif ( $args->{'Encrypt'} ) { + push @{ $mail->{'encrypted'} }, @mail; + } + else { + push @{ $mail->{'plain'} }, @mail; + } +} + +sub check_text_emails { + my %args = %{ shift @_ }; + my @mail = @_; + + ok scalar @mail, "got some mail"; + for my $mail (@mail) { + for my $type ('email', 'attachment') { + next if $type eq 'attachment' && !$args{'Attachment'}; + + my $content = $type eq 'email' + ? "Some content" + : "Attachment content"; + + if ( $args{'Encrypt'} ) { + unlike $mail, qr/$content/, "outgoing $type was encrypted"; + } else { + like $mail, qr/$content/, "outgoing $type was not encrypted"; + } + + next unless $type eq 'email'; + + if ( $args{'Sign'} && $args{'Encrypt'} ) { + like $mail, qr/BEGIN PGP MESSAGE/, 'outgoing email was signed'; + } elsif ( $args{'Sign'} ) { + like $mail, qr/SIGNATURE/, 'outgoing email was signed'; + } else { + unlike $mail, qr/SIGNATURE/, 'outgoing email was not signed'; + } + } + } +} + +sub cleanup_headers { + my $mail = shift; + # strip id from subject to create new ticket + $mail =~ s/^(Subject:)\s*\[.*?\s+#\d+\]\s*/$1 /m; + # strip several headers + foreach my $field ( qw(Message-ID X-RT-Original-Encoding RT-Originator RT-Ticket X-RT-Loop-Prevention) ) { + $mail =~ s/^$field:.*?\n(?! |\t)//gmsi; + } + return $mail; +} + +sub set_queue_crypt_options { + my $queue = shift; + my %args = @_; + $queue->SetEncrypt($args{'Encrypt'}); + $queue->SetSign($args{'Sign'}); +} + +sub send_email_and_check_transaction { + my $mail = shift; + my $type = shift; + + my ( $status, $id ) = RT::Test->send_via_mailgate($mail); + is( $status >> 8, 0, "The mail gateway exited normally" ); + ok( $id, "got id of a newly created ticket - $id" ); + + my $tick = RT::Ticket->new( RT->SystemUser ); + $tick->Load($id); + ok( $tick->id, "loaded ticket #$id" ); + + my $txn = $tick->Transactions->First; + my ( $msg, @attachments ) = @{ $txn->Attachments->ItemsArrayRef }; + + if ( $attachments[0] ) { + like $attachments[0]->Content, qr/Some content/, + "RT's mail includes copy of ticket text"; + } + else { + like $msg->Content, qr/Some content/, + "RT's mail includes copy of ticket text"; + } + + if ( $type eq 'plain' ) { + ok !$msg->GetHeader('X-RT-Privacy'), "RT's outgoing mail has no crypto"; + is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted', + "RT's outgoing mail looks not encrypted"; + ok !$msg->GetHeader('X-RT-Incoming-Signature'), + "RT's outgoing mail looks not signed"; + } + elsif ( $type eq 'signed' ) { + is $msg->GetHeader('X-RT-Privacy'), 'PGP', + "RT's outgoing mail has crypto"; + is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted', + "RT's outgoing mail looks not encrypted"; + like $msg->GetHeader('X-RT-Incoming-Signature'), + qr/<rt-recipient\@example.com>/, + "RT's outgoing mail looks signed"; + } + elsif ( $type eq 'encrypted' ) { + is $msg->GetHeader('X-RT-Privacy'), 'PGP', + "RT's outgoing mail has crypto"; + is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success', + "RT's outgoing mail looks encrypted"; + ok !$msg->GetHeader('X-RT-Incoming-Signature'), + "RT's outgoing mail looks not signed"; + + } + elsif ( $type eq 'signed_encrypted' ) { + is $msg->GetHeader('X-RT-Privacy'), 'PGP', + "RT's outgoing mail has crypto"; + is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success', + "RT's outgoing mail looks encrypted"; + like $msg->GetHeader('X-RT-Incoming-Signature'), + qr/<rt-recipient\@example.com>/, + "RT's outgoing mail looks signed"; + } + else { + die "unknown type: $type"; + } +} + +sub create_and_test_outgoing_emails { + my $queue = shift; + my $m = shift; + my @variants = + ( {}, { Sign => 1 }, { Encrypt => 1 }, { Sign => 1, Encrypt => 1 }, ); + + # collect emails + my %mail; + + # create a ticket for each combination + foreach my $ticket_set (@variants) { + create_a_ticket( $queue, \%mail, $m, %$ticket_set ); + } + + my $tid; + { + my $ticket = RT::Ticket->new( RT->SystemUser ); + ($tid) = $ticket->Create( + Subject => 'test', + Queue => $queue->id, + Requestor => 'rt-test@example.com', + ); + ok $tid, 'ticket created'; + } + + # again for each combination add a reply message + foreach my $ticket_set (@variants) { + update_ticket( $tid, \%mail, $m, %$ticket_set ); + } + +# ------------------------------------------------------------------------------ +# now delete all keys from the keyring and put back secret/pub pair for rt-test@ +# and only public key for rt-recipient@ so we can verify signatures and decrypt +# like we are on another side recieve emails +# ------------------------------------------------------------------------------ + + unlink $_ + foreach glob( RT->Config->Get('GnuPGOptions')->{'homedir'} . "/*" ); + RT::Test->import_gnupg_key( 'rt-recipient@example.com', 'public' ); + RT::Test->import_gnupg_key('rt-test@example.com'); + + $queue = RT::Test->load_or_create_queue( + Name => 'Regression', + CorrespondAddress => 'rt-test@example.com', + CommentAddress => 'rt-test@example.com', + ); + ok $queue && $queue->id, 'changed props of the queue'; + + for my $type ( keys %mail ) { + for my $mail ( map cleanup_headers($_), @{ $mail{$type} } ) { + send_email_and_check_transaction( $mail, $type ); + } + } +} |