1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2017 Best Practical Solutions, LLC
6 # <sales@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 # General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
30 # CONTRIBUTION SUBMISSION POLICY:
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
47 # END BPS TAGGED BLOCK }}}
49 package RT::Test::GnuPG;
53 use base qw(RT::Test);
54 use File::Temp qw(tempdir);
57 qw(create_a_ticket update_ticket cleanup_headers set_queue_crypt_options
58 check_text_emails send_email_and_check_transaction
59 create_and_test_outgoing_emails
65 my $t = $class->builder;
67 RT::Test::plan( skip_all => 'GnuPG required.' )
68 unless GnuPG::Interface->require;
69 RT::Test::plan( skip_all => 'gpg executable is required.' )
70 unless RT::Test->find_executable('gpg');
72 $class->SUPER::import(%args);
73 return $class->export_to_level(1)
76 RT::Test::diag "GnuPG --homedir " . RT->Config->Get('GnuPGOptions')->{'homedir'};
79 Principal => 'Everyone',
80 Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ReplyToTicket', 'ModifyTicket'],
83 $class->export_to_level(1);
86 sub bootstrap_more_config {
91 $self->SUPER::bootstrap_more_config($handle, $args, @_);
94 'no-permission-warning' => undef,
95 $args->{gnupg_options} ? %{ $args->{gnupg_options} } : (),
97 $gnupg_options{homedir} ||= scalar tempdir( CLEANUP => 1 );
100 local $Data::Dumper::Terse = 1; # "{...}" instead of "$VAR1 = {...};"
101 my $dumped_gnupg_options = Dumper(\%gnupg_options);
106 OutgoingMessagesFormat => 'RFC',
108 Set(\%GnuPGOptions => \%{ $dumped_gnupg_options });
109 Set(\@MailPlugins => qw(Auth::MailFrom Auth::Crypt));
114 sub create_a_ticket {
120 RT::Test->clean_caught_mails;
122 $m->goto_create_ticket( $queue );
123 $m->form_name('TicketCreate');
124 $m->field( Subject => 'test' );
125 $m->field( Requestors => 'rt-test@example.com' );
126 $m->field( Content => 'Some content' );
128 foreach ( qw(Sign Encrypt) ) {
132 $m->untick( $_ => 1 );
137 is $m->status, 200, "request successful";
139 $m->content_lacks("unable to sign outgoing email messages");
142 my @mail = RT::Test->fetch_caught_mails;
143 check_text_emails(\%args, @mail );
144 categorize_emails($mail, \%args, @mail );
153 RT::Test->clean_caught_mails;
155 $m->get( $m->rt_base_url . "/Ticket/Update.html?Action=Respond&id=$tid" );
157 $m->field( UpdateContent => 'Some content' );
159 foreach ( qw(Sign Encrypt) ) {
163 $m->untick( $_ => 1 );
167 $m->click('SubmitTicket');
168 is $m->status, 200, "request successful";
169 $m->content_contains("Correspondence added", 'Correspondence added') or diag $m->content;
172 my @mail = RT::Test->fetch_caught_mails;
173 check_text_emails(\%args, @mail );
174 categorize_emails($mail, \%args, @mail );
177 sub categorize_emails {
182 if ( $args->{'Sign'} && $args->{'Encrypt'} ) {
183 push @{ $mail->{'signed_encrypted'} }, @mail;
185 elsif ( $args->{'Sign'} ) {
186 push @{ $mail->{'signed'} }, @mail;
188 elsif ( $args->{'Encrypt'} ) {
189 push @{ $mail->{'encrypted'} }, @mail;
192 push @{ $mail->{'plain'} }, @mail;
196 sub check_text_emails {
197 my %args = %{ shift @_ };
200 ok scalar @mail, "got some mail";
201 for my $mail (@mail) {
202 for my $type ('email', 'attachment') {
203 next if $type eq 'attachment' && !$args{'Attachment'};
205 my $content = $type eq 'email'
209 if ( $args{'Encrypt'} ) {
210 unlike $mail, qr/$content/, "outgoing $type is not in plaintext";
211 my $entity = RT::Test::parse_mail($mail);
212 my @res = RT::Crypt->VerifyDecrypt(Entity => $entity);
213 like $res[0]{'status'}, qr/DECRYPTION_OKAY/, "Decrypts OK";
214 like $entity->as_string, qr/$content/, "outgoing decrypts to contain $type content";
216 like $mail, qr/$content/, "outgoing $type was not encrypted";
219 next unless $type eq 'email';
221 if ( $args{'Sign'} && $args{'Encrypt'} ) {
222 like $mail, qr/BEGIN PGP MESSAGE/, 'outgoing email was signed';
223 } elsif ( $args{'Sign'} ) {
224 like $mail, qr/SIGNATURE/, 'outgoing email was signed';
226 unlike $mail, qr/SIGNATURE/, 'outgoing email was not signed';
232 sub cleanup_headers {
234 # strip id from subject to create new ticket
235 $mail =~ s/^(Subject:)\s*\[.*?\s+#\d+\]\s*/$1 /m;
236 # strip several headers
237 foreach my $field ( qw(Message-ID RT-Originator RT-Ticket X-RT-Loop-Prevention) ) {
238 $mail =~ s/^$field:.*?\n(?! |\t)//gmsi;
243 sub set_queue_crypt_options {
246 $queue->SetEncrypt($args{'Encrypt'});
247 $queue->SetSign($args{'Sign'});
250 sub send_email_and_check_transaction {
254 my ( $status, $id ) = RT::Test->send_via_mailgate($mail);
255 is( $status >> 8, 0, "The mail gateway exited normally" );
256 ok( $id, "got id of a newly created ticket - $id" );
258 my $tick = RT::Ticket->new( RT->SystemUser );
260 ok( $tick->id, "loaded ticket #$id" );
262 my $txn = $tick->Transactions->First;
263 my ( $msg, @attachments ) = @{ $txn->Attachments->ItemsArrayRef };
265 if ( $attachments[0] ) {
266 like $attachments[0]->Content, qr/Some content/,
267 "RT's mail includes copy of ticket text";
270 like $msg->Content, qr/Some content/,
271 "RT's mail includes copy of ticket text";
274 if ( $type eq 'plain' ) {
275 ok !$msg->GetHeader('X-RT-Privacy'), "RT's outgoing mail has no crypto";
276 is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted',
277 "RT's outgoing mail looks not encrypted";
278 ok !$msg->GetHeader('X-RT-Incoming-Signature'),
279 "RT's outgoing mail looks not signed";
281 elsif ( $type eq 'signed' ) {
282 is $msg->GetHeader('X-RT-Privacy'), 'GnuPG',
283 "RT's outgoing mail has crypto";
284 is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted',
285 "RT's outgoing mail looks not encrypted";
286 like $msg->GetHeader('X-RT-Incoming-Signature'),
287 qr/<rt-recipient\@example.com>/,
288 "RT's outgoing mail looks signed";
290 elsif ( $type eq 'encrypted' ) {
291 is $msg->GetHeader('X-RT-Privacy'), 'GnuPG',
292 "RT's outgoing mail has crypto";
293 is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success',
294 "RT's outgoing mail looks encrypted";
295 ok !$msg->GetHeader('X-RT-Incoming-Signature'),
296 "RT's outgoing mail looks not signed";
299 elsif ( $type eq 'signed_encrypted' ) {
300 is $msg->GetHeader('X-RT-Privacy'), 'GnuPG',
301 "RT's outgoing mail has crypto";
302 is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success',
303 "RT's outgoing mail looks encrypted";
304 like $msg->GetHeader('X-RT-Incoming-Signature'),
305 qr/<rt-recipient\@example.com>/,
306 "RT's outgoing mail looks signed";
309 die "unknown type: $type";
313 sub create_and_test_outgoing_emails {
317 ( {}, { Sign => 1 }, { Encrypt => 1 }, { Sign => 1, Encrypt => 1 }, );
322 # create a ticket for each combination
323 foreach my $ticket_set (@variants) {
324 create_a_ticket( $queue, \%mail, $m, %$ticket_set );
329 my $ticket = RT::Ticket->new( RT->SystemUser );
330 ($tid) = $ticket->Create(
333 Requestor => 'rt-test@example.com',
335 ok $tid, 'ticket created';
338 # again for each combination add a reply message
339 foreach my $ticket_set (@variants) {
340 update_ticket( $tid, \%mail, $m, %$ticket_set );
343 # ------------------------------------------------------------------------------
344 # now delete all keys from the keyring and put back secret/pub pair for rt-test@
345 # and only public key for rt-recipient@ so we can verify signatures and decrypt
346 # like we are on another side recieve emails
347 # ------------------------------------------------------------------------------
350 foreach glob( RT->Config->Get('GnuPGOptions')->{'homedir'} . "/*" );
351 RT::Test->import_gnupg_key( 'rt-recipient@example.com', 'public' );
352 RT::Test->import_gnupg_key('rt-test@example.com');
354 $queue = RT::Test->load_or_create_queue(
355 Name => 'Regression',
356 CorrespondAddress => 'rt-test@example.com',
357 CommentAddress => 'rt-test@example.com',
359 ok $queue && $queue->id, 'changed props of the queue';
361 for my $type ( keys %mail ) {
362 for my $mail ( map cleanup_headers($_), @{ $mail{$type} } ) {
363 send_email_and_check_transaction( $mail, $type );