request.cgi 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. #!/usr/bin/env perl -wT
  2. # -*- Mode: perl; indent-tabs-mode: nil -*-
  3. #
  4. # The contents of this file are subject to the Mozilla Public
  5. # License Version 1.1 (the "License"); you may not use this file
  6. # except in compliance with the License. You may obtain a copy of
  7. # the License at http://www.mozilla.org/MPL/
  8. #
  9. # Software distributed under the License is distributed on an "AS
  10. # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11. # implied. See the License for the specific language governing
  12. # rights and limitations under the License.
  13. #
  14. # The Original Code is the Bugzilla Bug Tracking System.
  15. #
  16. # The Initial Developer of the Original Code is Netscape Communications
  17. # Corporation. Portions created by Netscape are
  18. # Copyright (C) 1998 Netscape Communications Corporation. All
  19. # Rights Reserved.
  20. #
  21. # Contributor(s): Myk Melez <myk@mozilla.org>
  22. # Frédéric Buclin <LpSolit@gmail.com>
  23. ################################################################################
  24. # Script Initialization
  25. ################################################################################
  26. # Make it harder for us to do dangerous things in Perl.
  27. use strict;
  28. use lib qw(. lib);
  29. use Bugzilla;
  30. use Bugzilla::Util;
  31. use Bugzilla::Error;
  32. use Bugzilla::Flag;
  33. use Bugzilla::FlagType;
  34. use Bugzilla::User;
  35. use Bugzilla::Product;
  36. use Bugzilla::Component;
  37. # Make sure the user is logged in.
  38. my $user = Bugzilla->login();
  39. my $cgi = Bugzilla->cgi;
  40. my $dbh = Bugzilla->dbh;
  41. my $template = Bugzilla->template;
  42. my $action = $cgi->param('action') || '';
  43. print $cgi->header();
  44. ################################################################################
  45. # Main Body Execution
  46. ################################################################################
  47. my $fields;
  48. $fields->{'requester'}->{'type'} = 'single';
  49. # If the user doesn't restrict his search to requests from the wind
  50. # (requestee ne '-'), include the requestee for completion.
  51. unless (defined $cgi->param('requestee')
  52. && $cgi->param('requestee') eq '-')
  53. {
  54. $fields->{'requestee'}->{'type'} = 'single';
  55. }
  56. Bugzilla::User::match_field($cgi, $fields);
  57. if ($action eq 'queue') {
  58. queue();
  59. }
  60. else {
  61. my $flagtypes = $dbh->selectcol_arrayref('SELECT DISTINCT(name) FROM flagtypes
  62. ORDER BY name');
  63. my @types = ('all', @$flagtypes);
  64. my $vars = {};
  65. $vars->{'products'} = $user->get_selectable_products;
  66. $vars->{'types'} = \@types;
  67. $vars->{'requests'} = {};
  68. my %components;
  69. foreach my $prod (@{$vars->{'products'}}) {
  70. foreach my $comp (@{$prod->components}) {
  71. $components{$comp->name} = 1;
  72. }
  73. }
  74. $vars->{'components'} = [ sort { $a cmp $b } keys %components ];
  75. $template->process('request/queue.html.tmpl', $vars)
  76. || ThrowTemplateError($template->error());
  77. }
  78. exit;
  79. ################################################################################
  80. # Functions
  81. ################################################################################
  82. sub queue {
  83. my $cgi = Bugzilla->cgi;
  84. # There are some user privilege checks to do. We do them against the main DB.
  85. my $dbh = Bugzilla->dbh;
  86. my $template = Bugzilla->template;
  87. my $user = Bugzilla->user;
  88. my $userid = $user->id;
  89. my $vars = {};
  90. my $status = validateStatus($cgi->param('status'));
  91. my $form_group = validateGroup($cgi->param('group'));
  92. my $query =
  93. # Select columns describing each flag, the bug/attachment on which
  94. # it has been set, who set it, and of whom they are requesting it.
  95. " SELECT flags.id, flagtypes.name,
  96. flags.status,
  97. flags.bug_id, bugs.short_desc,
  98. products.name, components.name,
  99. flags.attach_id, attachments.description,
  100. requesters.realname, requesters.login_name,
  101. requestees.realname, requestees.login_name,
  102. " . $dbh->sql_date_format('flags.modification_date', '%Y.%m.%d %H:%i') .
  103. # Use the flags and flagtypes tables for information about the flags,
  104. # the bugs and attachments tables for target info, the profiles tables
  105. # for setter and requestee info, the products/components tables
  106. # so we can display product and component names, and the bug_group_map
  107. # table to help us weed out secure bugs to which the user should not have
  108. # access.
  109. "
  110. FROM flags
  111. LEFT JOIN attachments
  112. ON flags.attach_id = attachments.attach_id
  113. INNER JOIN flagtypes
  114. ON flags.type_id = flagtypes.id
  115. INNER JOIN profiles AS requesters
  116. ON flags.setter_id = requesters.userid
  117. LEFT JOIN profiles AS requestees
  118. ON flags.requestee_id = requestees.userid
  119. INNER JOIN bugs
  120. ON flags.bug_id = bugs.bug_id
  121. INNER JOIN products
  122. ON bugs.product_id = products.id
  123. INNER JOIN components
  124. ON bugs.component_id = components.id
  125. LEFT JOIN bug_group_map AS bgmap
  126. ON bgmap.bug_id = bugs.bug_id
  127. AND bgmap.group_id NOT IN (" .
  128. join(', ', (-1, values(%{$user->groups}))) . ")
  129. LEFT JOIN cc AS ccmap
  130. ON ccmap.who = $userid
  131. AND ccmap.bug_id = bugs.bug_id
  132. " .
  133. # Weed out bug the user does not have access to
  134. " WHERE ((bgmap.group_id IS NULL) OR
  135. (ccmap.who IS NOT NULL AND cclist_accessible = 1) OR
  136. (bugs.reporter = $userid AND bugs.reporter_accessible = 1) OR
  137. (bugs.assigned_to = $userid) " .
  138. (Bugzilla->params->{'useqacontact'} ? "OR
  139. (bugs.qa_contact = $userid))" : ")");
  140. unless ($user->is_insider) {
  141. $query .= " AND (attachments.attach_id IS NULL
  142. OR attachments.isprivate = 0
  143. OR attachments.submitter_id = $userid)";
  144. }
  145. # Limit query to pending requests.
  146. $query .= " AND flags.status = '?' " unless $status;
  147. # The set of criteria by which we filter records to display in the queue.
  148. # We now move to the shadow DB to query the DB.
  149. my @criteria = ();
  150. $dbh = Bugzilla->switch_to_shadow_db;
  151. # A list of columns to exclude from the report because the report conditions
  152. # limit the data being displayed to exact matches for those columns.
  153. # In other words, if we are only displaying "pending" , we don't
  154. # need to display a "status" column in the report because the value for that
  155. # column will always be the same.
  156. my @excluded_columns = ();
  157. # Filter requests by status: "pending", "granted", "denied", "all"
  158. # (which means any), or "fulfilled" (which means "granted" or "denied").
  159. if ($status) {
  160. if ($status eq "+-") {
  161. push(@criteria, "flags.status IN ('+', '-')");
  162. push(@excluded_columns, 'status') unless $cgi->param('do_union');
  163. }
  164. elsif ($status ne "all") {
  165. push(@criteria, "flags.status = '$status'");
  166. push(@excluded_columns, 'status') unless $cgi->param('do_union');
  167. }
  168. }
  169. # Filter results by exact email address of requester or requestee.
  170. if (defined $cgi->param('requester') && $cgi->param('requester') ne "") {
  171. my $requester = $dbh->quote($cgi->param('requester'));
  172. trick_taint($requester); # Quoted above
  173. push(@criteria, $dbh->sql_istrcmp('requesters.login_name', $requester));
  174. push(@excluded_columns, 'requester') unless $cgi->param('do_union');
  175. }
  176. if (defined $cgi->param('requestee') && $cgi->param('requestee') ne "") {
  177. if ($cgi->param('requestee') ne "-") {
  178. my $requestee = $dbh->quote($cgi->param('requestee'));
  179. trick_taint($requestee); # Quoted above
  180. push(@criteria, $dbh->sql_istrcmp('requestees.login_name',
  181. $requestee));
  182. }
  183. else { push(@criteria, "flags.requestee_id IS NULL") }
  184. push(@excluded_columns, 'requestee') unless $cgi->param('do_union');
  185. }
  186. # Filter results by exact product or component.
  187. if (defined $cgi->param('product') && $cgi->param('product') ne "") {
  188. my $product = Bugzilla::Product::check_product(scalar $cgi->param('product'));
  189. push(@criteria, "bugs.product_id = " . $product->id);
  190. push(@excluded_columns, 'product') unless $cgi->param('do_union');
  191. if (defined $cgi->param('component') && $cgi->param('component') ne "") {
  192. my $component = Bugzilla::Component->check({ product => $product,
  193. name => scalar $cgi->param('component') });
  194. push(@criteria, "bugs.component_id = " . $component->id);
  195. push(@excluded_columns, 'component') unless $cgi->param('do_union');
  196. }
  197. }
  198. # Filter results by flag types.
  199. my $form_type = $cgi->param('type');
  200. if (defined $form_type && !grep($form_type eq $_, ("", "all"))) {
  201. # Check if any matching types are for attachments. If not, don't show
  202. # the attachment column in the report.
  203. my $has_attachment_type =
  204. Bugzilla::FlagType::count({ 'name' => $form_type,
  205. 'target_type' => 'attachment' });
  206. if (!$has_attachment_type) { push(@excluded_columns, 'attachment') }
  207. my $quoted_form_type = $dbh->quote($form_type);
  208. trick_taint($quoted_form_type); # Already SQL quoted
  209. push(@criteria, "flagtypes.name = " . $quoted_form_type);
  210. push(@excluded_columns, 'type') unless $cgi->param('do_union');
  211. }
  212. # Add the criteria to the query. We do an intersection by default
  213. # but do a union if the "do_union" URL parameter (for which there is no UI
  214. # because it's an advanced feature that people won't usually want) is true.
  215. my $and_or = $cgi->param('do_union') ? " OR " : " AND ";
  216. $query .= " AND (" . join($and_or, @criteria) . ") " if scalar(@criteria);
  217. # Group the records by flag ID so we don't get multiple rows of data
  218. # for each flag. This is only necessary because of the code that
  219. # removes flags on bugs the user is unauthorized to access.
  220. $query .= ' ' . $dbh->sql_group_by('flags.id',
  221. 'flagtypes.name, flags.status, flags.bug_id, bugs.short_desc,
  222. products.name, components.name, flags.attach_id,
  223. attachments.description, requesters.realname,
  224. requesters.login_name, requestees.realname,
  225. requestees.login_name, flags.modification_date,
  226. cclist_accessible, bugs.reporter, bugs.reporter_accessible,
  227. bugs.assigned_to');
  228. # Group the records, in other words order them by the group column
  229. # so the loop in the display template can break them up into separate
  230. # tables every time the value in the group column changes.
  231. $form_group ||= "requestee";
  232. if ($form_group eq "requester") {
  233. $query .= " ORDER BY requesters.realname, requesters.login_name";
  234. }
  235. elsif ($form_group eq "requestee") {
  236. $query .= " ORDER BY requestees.realname, requestees.login_name";
  237. }
  238. elsif ($form_group eq "category") {
  239. $query .= " ORDER BY products.name, components.name";
  240. }
  241. elsif ($form_group eq "type") {
  242. $query .= " ORDER BY flagtypes.name";
  243. }
  244. # Order the records (within each group).
  245. $query .= " , flags.modification_date";
  246. # Pass the query to the template for use when debugging this script.
  247. $vars->{'query'} = $query;
  248. $vars->{'debug'} = $cgi->param('debug') ? 1 : 0;
  249. my $results = $dbh->selectall_arrayref($query);
  250. my @requests = ();
  251. foreach my $result (@$results) {
  252. my @data = @$result;
  253. my $request = {
  254. 'id' => $data[0] ,
  255. 'type' => $data[1] ,
  256. 'status' => $data[2] ,
  257. 'bug_id' => $data[3] ,
  258. 'bug_summary' => $data[4] ,
  259. 'category' => "$data[5]: $data[6]" ,
  260. 'attach_id' => $data[7] ,
  261. 'attach_summary' => $data[8] ,
  262. 'requester' => ($data[9] ? "$data[9] <$data[10]>" : $data[10]) ,
  263. 'requestee' => ($data[11] ? "$data[11] <$data[12]>" : $data[12]) ,
  264. 'created' => $data[13]
  265. };
  266. push(@requests, $request);
  267. }
  268. # Get a list of request type names to use in the filter form.
  269. my @types = ("all");
  270. my $flagtypes = $dbh->selectcol_arrayref(
  271. "SELECT DISTINCT(name) FROM flagtypes ORDER BY name");
  272. push(@types, @$flagtypes);
  273. # We move back to the main DB to get the list of products the user can see.
  274. $dbh = Bugzilla->switch_to_main_db;
  275. $vars->{'products'} = $user->get_selectable_products;
  276. $vars->{'excluded_columns'} = \@excluded_columns;
  277. $vars->{'group_field'} = $form_group;
  278. $vars->{'requests'} = \@requests;
  279. $vars->{'types'} = \@types;
  280. my %components;
  281. foreach my $prod (@{$vars->{'products'}}) {
  282. foreach my $comp (@{$prod->components}) {
  283. $components{$comp->name} = 1;
  284. }
  285. }
  286. $vars->{'components'} = [ sort { $a cmp $b } keys %components ];
  287. # Generate and return the UI (HTML page) from the appropriate template.
  288. $template->process("request/queue.html.tmpl", $vars)
  289. || ThrowTemplateError($template->error());
  290. }
  291. ################################################################################
  292. # Data Validation / Security Authorization
  293. ################################################################################
  294. sub validateStatus {
  295. my $status = shift;
  296. return if !defined $status;
  297. grep($status eq $_, qw(? +- + - all))
  298. || ThrowCodeError("flag_status_invalid",
  299. { status => $status });
  300. trick_taint($status);
  301. return $status;
  302. }
  303. sub validateGroup {
  304. my $group = shift;
  305. return if !defined $group;
  306. grep($group eq $_, qw(requester requestee category type))
  307. || ThrowCodeError("request_queue_group_invalid",
  308. { group => $group });
  309. trick_taint($group);
  310. return $group;
  311. }