Master to 4.2.8
[usit-rt.git] / lib / RT / Test / GnuPG.pm
CommitLineData
84fb5b46
MKG
1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
320f0092 5# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
84fb5b46
MKG
6# <sales@bestpractical.com>
7#
8# (Except where explicitly superseded by other copyright notices)
9#
10#
11# LICENSE:
12#
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
16# from www.gnu.org.
17#
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.
22#
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.
28#
29#
30# CONTRIBUTION SUBMISSION POLICY:
31#
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.)
37#
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.
46#
47# END BPS TAGGED BLOCK }}}
48
49package RT::Test::GnuPG;
50use strict;
403d7b0b 51use warnings;
84fb5b46
MKG
52use Test::More;
53use base qw(RT::Test);
54use File::Temp qw(tempdir);
55
56our @EXPORT =
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
60 );
61
62sub import {
63 my $class = shift;
64 my %args = @_;
65 my $t = $class->builder;
66
67 $t->plan( skip_all => 'GnuPG required.' )
c33a4027 68 unless GnuPG::Interface->require;
84fb5b46
MKG
69 $t->plan( skip_all => 'gpg executable is required.' )
70 unless RT::Test->find_executable('gpg');
71
84fb5b46 72 $class->SUPER::import(%args);
c36a7e1d
MKG
73 return $class->export_to_level(1)
74 if $^C;
84fb5b46
MKG
75
76 RT::Test::diag "GnuPG --homedir " . RT->Config->Get('GnuPGOptions')->{'homedir'};
77
78 $class->set_rights(
79 Principal => 'Everyone',
80 Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ReplyToTicket', 'ModifyTicket'],
81 );
82
83 $class->export_to_level(1);
84}
85
86sub bootstrap_more_config {
87 my $self = shift;
88 my $handle = shift;
89 my $args = shift;
90
91 $self->SUPER::bootstrap_more_config($handle, $args, @_);
92
93 my %gnupg_options = (
94 'no-permission-warning' => undef,
95 $args->{gnupg_options} ? %{ $args->{gnupg_options} } : (),
96 );
97 $gnupg_options{homedir} ||= scalar tempdir( CLEANUP => 1 );
98
99 use Data::Dumper;
100 local $Data::Dumper::Terse = 1; # "{...}" instead of "$VAR1 = {...};"
101 my $dumped_gnupg_options = Dumper(\%gnupg_options);
102
103 print $handle qq{
104Set(\%GnuPG, (
105 Enable => 1,
106 OutgoingMessagesFormat => 'RFC',
107));
108Set(\%GnuPGOptions => \%{ $dumped_gnupg_options });
af59614d 109Set(\@MailPlugins => qw(Auth::MailFrom Auth::Crypt));
84fb5b46
MKG
110};
111
112}
113
114sub create_a_ticket {
115 my $queue = shift;
116 my $mail = shift;
117 my $m = shift;
118 my %args = (@_);
119
120 RT::Test->clean_caught_mails;
121
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' );
127
128 foreach ( qw(Sign Encrypt) ) {
129 if ( $args{ $_ } ) {
130 $m->tick( $_ => 1 );
131 } else {
132 $m->untick( $_ => 1 );
133 }
134 }
135
136 $m->submit;
137 is $m->status, 200, "request successful";
138
139 $m->content_lacks("unable to sign outgoing email messages");
140
141
142 my @mail = RT::Test->fetch_caught_mails;
143 check_text_emails(\%args, @mail );
144 categorize_emails($mail, \%args, @mail );
145}
146
147sub update_ticket {
148 my $tid = shift;
149 my $mail = shift;
150 my $m = shift;
151 my %args = (@_);
152
153 RT::Test->clean_caught_mails;
154
155 $m->get( $m->rt_base_url . "/Ticket/Update.html?Action=Respond&id=$tid" );
156 $m->form_number(3);
157 $m->field( UpdateContent => 'Some content' );
158
159 foreach ( qw(Sign Encrypt) ) {
160 if ( $args{ $_ } ) {
161 $m->tick( $_ => 1 );
162 } else {
163 $m->untick( $_ => 1 );
164 }
165 }
166
167 $m->click('SubmitTicket');
168 is $m->status, 200, "request successful";
c33a4027 169 $m->content_contains("Correspondence added", 'Correspondence added') or diag $m->content;
84fb5b46
MKG
170
171
172 my @mail = RT::Test->fetch_caught_mails;
173 check_text_emails(\%args, @mail );
174 categorize_emails($mail, \%args, @mail );
175}
176
177sub categorize_emails {
178 my $mail = shift;
179 my $args = shift;
180 my @mail = @_;
181
182 if ( $args->{'Sign'} && $args->{'Encrypt'} ) {
183 push @{ $mail->{'signed_encrypted'} }, @mail;
184 }
185 elsif ( $args->{'Sign'} ) {
186 push @{ $mail->{'signed'} }, @mail;
187 }
188 elsif ( $args->{'Encrypt'} ) {
189 push @{ $mail->{'encrypted'} }, @mail;
190 }
191 else {
192 push @{ $mail->{'plain'} }, @mail;
193 }
194}
195
196sub check_text_emails {
197 my %args = %{ shift @_ };
198 my @mail = @_;
199
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'};
204
205 my $content = $type eq 'email'
206 ? "Some content"
207 : "Attachment content";
208
209 if ( $args{'Encrypt'} ) {
210 unlike $mail, qr/$content/, "outgoing $type was encrypted";
211 } else {
212 like $mail, qr/$content/, "outgoing $type was not encrypted";
213 }
214
215 next unless $type eq 'email';
216
217 if ( $args{'Sign'} && $args{'Encrypt'} ) {
218 like $mail, qr/BEGIN PGP MESSAGE/, 'outgoing email was signed';
219 } elsif ( $args{'Sign'} ) {
220 like $mail, qr/SIGNATURE/, 'outgoing email was signed';
221 } else {
222 unlike $mail, qr/SIGNATURE/, 'outgoing email was not signed';
223 }
224 }
225 }
226}
227
228sub cleanup_headers {
229 my $mail = shift;
230 # strip id from subject to create new ticket
231 $mail =~ s/^(Subject:)\s*\[.*?\s+#\d+\]\s*/$1 /m;
232 # strip several headers
af59614d 233 foreach my $field ( qw(Message-ID RT-Originator RT-Ticket X-RT-Loop-Prevention) ) {
84fb5b46
MKG
234 $mail =~ s/^$field:.*?\n(?! |\t)//gmsi;
235 }
236 return $mail;
237}
238
239sub set_queue_crypt_options {
240 my $queue = shift;
241 my %args = @_;
242 $queue->SetEncrypt($args{'Encrypt'});
243 $queue->SetSign($args{'Sign'});
244}
245
246sub send_email_and_check_transaction {
247 my $mail = shift;
248 my $type = shift;
249
250 my ( $status, $id ) = RT::Test->send_via_mailgate($mail);
251 is( $status >> 8, 0, "The mail gateway exited normally" );
252 ok( $id, "got id of a newly created ticket - $id" );
253
254 my $tick = RT::Ticket->new( RT->SystemUser );
255 $tick->Load($id);
256 ok( $tick->id, "loaded ticket #$id" );
257
258 my $txn = $tick->Transactions->First;
259 my ( $msg, @attachments ) = @{ $txn->Attachments->ItemsArrayRef };
260
261 if ( $attachments[0] ) {
262 like $attachments[0]->Content, qr/Some content/,
263 "RT's mail includes copy of ticket text";
264 }
265 else {
266 like $msg->Content, qr/Some content/,
267 "RT's mail includes copy of ticket text";
268 }
269
270 if ( $type eq 'plain' ) {
271 ok !$msg->GetHeader('X-RT-Privacy'), "RT's outgoing mail has no crypto";
272 is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted',
273 "RT's outgoing mail looks not encrypted";
274 ok !$msg->GetHeader('X-RT-Incoming-Signature'),
275 "RT's outgoing mail looks not signed";
276 }
277 elsif ( $type eq 'signed' ) {
af59614d 278 is $msg->GetHeader('X-RT-Privacy'), 'GnuPG',
84fb5b46
MKG
279 "RT's outgoing mail has crypto";
280 is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted',
281 "RT's outgoing mail looks not encrypted";
282 like $msg->GetHeader('X-RT-Incoming-Signature'),
283 qr/<rt-recipient\@example.com>/,
284 "RT's outgoing mail looks signed";
285 }
286 elsif ( $type eq 'encrypted' ) {
af59614d 287 is $msg->GetHeader('X-RT-Privacy'), 'GnuPG',
84fb5b46
MKG
288 "RT's outgoing mail has crypto";
289 is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success',
290 "RT's outgoing mail looks encrypted";
291 ok !$msg->GetHeader('X-RT-Incoming-Signature'),
292 "RT's outgoing mail looks not signed";
293
294 }
295 elsif ( $type eq 'signed_encrypted' ) {
af59614d 296 is $msg->GetHeader('X-RT-Privacy'), 'GnuPG',
84fb5b46
MKG
297 "RT's outgoing mail has crypto";
298 is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success',
299 "RT's outgoing mail looks encrypted";
300 like $msg->GetHeader('X-RT-Incoming-Signature'),
301 qr/<rt-recipient\@example.com>/,
302 "RT's outgoing mail looks signed";
303 }
304 else {
305 die "unknown type: $type";
306 }
307}
308
309sub create_and_test_outgoing_emails {
310 my $queue = shift;
311 my $m = shift;
312 my @variants =
313 ( {}, { Sign => 1 }, { Encrypt => 1 }, { Sign => 1, Encrypt => 1 }, );
314
315 # collect emails
316 my %mail;
317
318 # create a ticket for each combination
319 foreach my $ticket_set (@variants) {
320 create_a_ticket( $queue, \%mail, $m, %$ticket_set );
321 }
322
323 my $tid;
324 {
325 my $ticket = RT::Ticket->new( RT->SystemUser );
326 ($tid) = $ticket->Create(
327 Subject => 'test',
328 Queue => $queue->id,
329 Requestor => 'rt-test@example.com',
330 );
331 ok $tid, 'ticket created';
332 }
333
334 # again for each combination add a reply message
335 foreach my $ticket_set (@variants) {
336 update_ticket( $tid, \%mail, $m, %$ticket_set );
337 }
338
339# ------------------------------------------------------------------------------
340# now delete all keys from the keyring and put back secret/pub pair for rt-test@
341# and only public key for rt-recipient@ so we can verify signatures and decrypt
342# like we are on another side recieve emails
343# ------------------------------------------------------------------------------
344
345 unlink $_
346 foreach glob( RT->Config->Get('GnuPGOptions')->{'homedir'} . "/*" );
347 RT::Test->import_gnupg_key( 'rt-recipient@example.com', 'public' );
348 RT::Test->import_gnupg_key('rt-test@example.com');
349
350 $queue = RT::Test->load_or_create_queue(
351 Name => 'Regression',
352 CorrespondAddress => 'rt-test@example.com',
353 CommentAddress => 'rt-test@example.com',
354 );
355 ok $queue && $queue->id, 'changed props of the queue';
356
357 for my $type ( keys %mail ) {
358 for my $mail ( map cleanup_headers($_), @{ $mail{$type} } ) {
359 send_email_and_check_transaction( $mail, $type );
360 }
361 }
362}