rt 4.0.6
[freeside.git] / rt / t / web / csrf.t
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4
5 use RT::Test tests => undef;
6
7 my $ticket = RT::Ticket->new(RT::CurrentUser->new('root'));
8 my ($ok, $msg) = $ticket->Create(Queue => 1, Owner => 'nobody', Subject => 'bad music');
9 ok($ok);
10 my $other = RT::Test->load_or_create_queue(Name => "Other queue", Disabled => 0);
11 my $other_queue_id = $other->id;
12
13 my ($baseurl, $m) = RT::Test->started_ok;
14
15 my $test_page = "/Ticket/Create.html?Queue=1";
16 my $test_path = "/Ticket/Create.html";
17
18 ok $m->login, 'logged in';
19
20 # valid referer
21 $m->add_header(Referer => $baseurl);
22 $m->get_ok($test_page);
23 $m->content_lacks("Possible cross-site request forgery");
24 $m->title_is('Create a new ticket');
25
26 # off-site referer BUT provides auth
27 $m->add_header(Referer => 'http://example.net');
28 $m->get_ok("$test_page&user=root&pass=password");
29 $m->content_lacks("Possible cross-site request forgery");
30 $m->title_is('Create a new ticket');
31
32 # explicitly no referer BUT provides auth
33 $m->add_header(Referer => undef);
34 $m->get_ok("$test_page&user=root&pass=password");
35 $m->content_lacks("Possible cross-site request forgery");
36 $m->title_is('Create a new ticket');
37
38 # now send a referer from an attacker
39 $m->add_header(Referer => 'http://example.net');
40 $m->get_ok($test_page);
41 $m->content_contains("Possible cross-site request forgery");
42 $m->content_contains("If you really intended to visit <tt>/Ticket/Create.html</tt>");
43 $m->content_contains("the Referrer header supplied by your browser (example.net:80) is not allowed");
44 $m->title_is('Possible cross-site request forgery');
45
46 # reinstate mech's usual header policy
47 $m->delete_header('Referer');
48
49 # clicking the resume request button gets us to the test page
50 $m->follow_link(text_regex => qr{resume your request});
51 $m->content_lacks("Possible cross-site request forgery");
52 like($m->response->request->uri, qr{^http://[^/]+\Q$test_path\E\?CSRF_Token=\w+$});
53 $m->title_is('Create a new ticket');
54
55 # try a whitelisted argument from an attacker
56 $m->add_header(Referer => 'http://example.net');
57 $m->get_ok("/Ticket/Display.html?id=1");
58 $m->content_lacks("Possible cross-site request forgery");
59 $m->title_is('#1: bad music');
60
61 # now a non-whitelisted argument
62 $m->get_ok("/Ticket/Display.html?id=1&Action=Take");
63 $m->content_contains("Possible cross-site request forgery");
64 $m->content_contains("If you really intended to visit <tt>/Ticket/Display.html</tt>");
65 $m->content_contains("the Referrer header supplied by your browser (example.net:80) is not allowed");
66 $m->title_is('Possible cross-site request forgery');
67
68 $m->delete_header('Referer');
69 $m->follow_link(text_regex => qr{resume your request});
70 $m->content_lacks("Possible cross-site request forgery");
71 like($m->response->request->uri, qr{^http://[^/]+\Q/Ticket/Display.html});
72 $m->title_is('#1: bad music');
73 $m->content_contains('Owner changed from Nobody to root');
74
75 # force mech to never set referer
76 $m->add_header(Referer => undef);
77 $m->get_ok($test_page);
78 $m->content_contains("Possible cross-site request forgery");
79 $m->content_contains("If you really intended to visit <tt>/Ticket/Create.html</tt>");
80 $m->content_contains("your browser did not supply a Referrer header");
81 $m->title_is('Possible cross-site request forgery');
82
83 $m->follow_link(text_regex => qr{resume your request});
84 $m->content_lacks("Possible cross-site request forgery");
85 is($m->response->redirects, 0, "no redirection");
86 like($m->response->request->uri, qr{^http://[^/]+\Q$test_path\E\?CSRF_Token=\w+$});
87 $m->title_is('Create a new ticket');
88
89 # try sending the wrong csrf token, then the right one
90 $m->add_header(Referer => undef);
91 $m->get_ok($test_page);
92 $m->content_contains("Possible cross-site request forgery");
93 $m->content_contains("If you really intended to visit <tt>/Ticket/Create.html</tt>");
94 $m->content_contains("your browser did not supply a Referrer header");
95 $m->title_is('Possible cross-site request forgery');
96
97 # Sending a wrong CSRF is just a normal request.  We'll make a request
98 # with just an invalid token, which means no Queue=, which means
99 # Create.html errors out.
100 my $link = $m->find_link(text_regex => qr{resume your request});
101 (my $broken_url = $link->url) =~ s/(CSRF_Token)=\w+/$1=crud/;
102 $m->get_ok($broken_url);
103 $m->content_contains("Queue could not be loaded");
104 $m->title_is('RT Error');
105 $m->warning_like(qr/Queue could not be loaded/);
106
107 # The token doesn't work for other pages, or other arguments to the same page.
108 $m->add_header(Referer => undef);
109 $m->get_ok($test_page);
110 $m->content_contains("Possible cross-site request forgery");
111 my ($token) = $m->content =~ m{CSRF_Token=(\w+)};
112
113 $m->add_header(Referer => undef);
114 $m->get_ok("/Admin/Queues/Modify.html?id=new&Name=test&CSRF_Token=$token");
115 $m->content_contains("Possible cross-site request forgery");
116 $m->content_contains("If you really intended to visit <tt>/Admin/Queues/Modify.html</tt>");
117 $m->content_contains("your browser did not supply a Referrer header");
118 $m->title_is('Possible cross-site request forgery');
119
120 $m->follow_link(text_regex => qr{resume your request});
121 $m->content_lacks("Possible cross-site request forgery");
122 $m->title_is('Configuration for queue test');
123
124 # Try the same page, but different query parameters, which are blatted by the token
125 $m->get_ok("/Ticket/Create.html?Queue=$other_queue_id&CSRF_Token=$token");
126 $m->content_lacks("Possible cross-site request forgery");
127 $m->title_is('Create a new ticket');
128 $m->text_unlike(qr/Queue:\s*Other queue/);
129 $m->text_like(qr/Queue:\s*General/);
130
131 # Ensure that file uploads work across the interstitial
132 $m->delete_header('Referer');
133 $m->get_ok($test_page);
134 $m->content_contains("Create a new ticket", 'ticket create page');
135 $m->form_name('TicketCreate');
136 $m->field('Subject', 'Attachments test');
137
138 my $logofile = "$RT::MasonComponentRoot/NoAuth/images/bpslogo.png";
139 open LOGO, "<", $logofile or die "Can't open logo file: $!";
140 binmode LOGO;
141 my $logo_contents = do {local $/; <LOGO>};
142 close LOGO;
143 $m->field('Attach',  $logofile);
144
145 # Lose the referer before the POST
146 $m->add_header(Referer => undef);
147 $m->submit;
148 $m->content_contains("Possible cross-site request forgery");
149 $m->content_contains("If you really intended to visit <tt>/Ticket/Create.html</tt>");
150 $m->follow_link(text_regex => qr{resume your request});
151 $m->content_contains('Download bpslogo.png', 'page has file name');
152 $m->follow_link_ok({text => "Download bpslogo.png"});
153 is($m->content, $logo_contents, "Binary content matches");
154
155
156 # now try self-service with CSRF
157 my $user = RT::User->new(RT->SystemUser);
158 $user->Create(Name => "SelfService", Password => "chops", Privileged => 0);
159
160 $m = RT::Test::Web->new;
161 $m->get_ok("$baseurl/index.html?user=SelfService&pass=chops");
162 $m->title_is("Open tickets", "got self-service interface");
163 $m->content_contains("My open tickets", "got self-service interface");
164
165 # post without referer
166 $m->add_header(Referer => undef);
167 $m->get_ok("/SelfService/Create.html?Queue=1");
168 $m->content_contains("Possible cross-site request forgery");
169 $m->content_contains("If you really intended to visit <tt>/SelfService/Create.html</tt>");
170 $m->content_contains("your browser did not supply a Referrer header");
171 $m->title_is('Possible cross-site request forgery');
172
173 $m->follow_link(text_regex => qr{resume your request});
174 $m->content_lacks("Possible cross-site request forgery");
175 is($m->response->redirects, 0, "no redirection");
176 like($m->response->request->uri, qr{^http://[^/]+\Q/SelfService/Create.html\E\?CSRF_Token=\w+$});
177 $m->title_is('Create a ticket');
178 $m->content_contains('Describe the issue below:');
179
180 undef $m;
181 done_testing;