(CVE-2013-0786) [SECURITY] build_subselect() leaks the existence of products and...
[WebKit-https.git] / Websites / bugs.webkit.org / Bugzilla / Product.pm
1 # -*- Mode: perl; indent-tabs-mode: nil -*-
2 #
3 # The contents of this file are subject to the Mozilla Public
4 # License Version 1.1 (the "License"); you may not use this file
5 # except in compliance with the License. You may obtain a copy of
6 # the License at http://www.mozilla.org/MPL/
7 #
8 # Software distributed under the License is distributed on an "AS
9 # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
10 # implied. See the License for the specific language governing
11 # rights and limitations under the License.
12 #
13 # The Original Code is the Bugzilla Bug Tracking System.
14 #
15 # Contributor(s): Tiago R. Mello <timello@async.com.br>
16
17 use strict;
18
19 package Bugzilla::Product;
20
21 use Bugzilla::Version;
22 use Bugzilla::Milestone;
23
24 use Bugzilla::Constants;
25 use Bugzilla::Util;
26 use Bugzilla::Group;
27 use Bugzilla::Error;
28
29 use Bugzilla::Install::Requirements;
30
31 use base qw(Bugzilla::Object);
32
33 use constant DEFAULT_CLASSIFICATION_ID => 1;
34
35 ###############################
36 ####    Initialization     ####
37 ###############################
38
39 use constant DB_TABLE => 'products';
40
41 use constant DB_COLUMNS => qw(
42    products.id
43    products.name
44    products.classification_id
45    products.description
46    products.milestoneurl
47    products.disallownew
48    products.votesperuser
49    products.maxvotesperbug
50    products.votestoconfirm
51    products.defaultmilestone
52 );
53
54 ###############################
55 ####     Constructors     #####
56 ###############################
57
58 # This is considerably faster than calling new_from_list three times
59 # for each product in the list, particularly with hundreds or thousands
60 # of products.
61 sub preload {
62     my ($products) = @_;
63     my %prods = map { $_->id => $_ } @$products;
64     my @prod_ids = keys %prods;
65     return unless @prod_ids;
66
67     my $dbh = Bugzilla->dbh;
68     foreach my $field (qw(component version milestone)) {
69         my $classname = "Bugzilla::" . ucfirst($field);
70         my $objects = $classname->match({ product_id => \@prod_ids });
71
72         # Now populate the products with this set of objects.
73         foreach my $obj (@$objects) {
74             my $product_id = $obj->product_id;
75             $prods{$product_id}->{"${field}s"} ||= [];
76             push(@{$prods{$product_id}->{"${field}s"}}, $obj);
77         }
78     }
79 }
80
81 ###############################
82 ####       Methods         ####
83 ###############################
84
85 sub components {
86     my $self = shift;
87     my $dbh = Bugzilla->dbh;
88
89     if (!defined $self->{components}) {
90         my $ids = $dbh->selectcol_arrayref(q{
91             SELECT id FROM components
92             WHERE product_id = ?
93             ORDER BY name}, undef, $self->id);
94
95         require Bugzilla::Component;
96         $self->{components} = Bugzilla::Component->new_from_list($ids);
97     }
98     return $self->{components};
99 }
100
101 sub group_controls {
102     my $self = shift;
103     my $dbh = Bugzilla->dbh;
104
105     if (!defined $self->{group_controls}) {
106         my $query = qq{SELECT
107                        groups.id,
108                        group_control_map.entry,
109                        group_control_map.membercontrol,
110                        group_control_map.othercontrol,
111                        group_control_map.canedit,
112                        group_control_map.editcomponents,
113                        group_control_map.editbugs,
114                        group_control_map.canconfirm
115                   FROM groups
116                   LEFT JOIN group_control_map
117                         ON groups.id = group_control_map.group_id
118                   WHERE group_control_map.product_id = ?
119                   AND   groups.isbuggroup != 0
120                   ORDER BY groups.name};
121         $self->{group_controls} = 
122             $dbh->selectall_hashref($query, 'id', undef, $self->id);
123
124         # For each group ID listed above, create and store its group object.
125         my @gids = keys %{$self->{group_controls}};
126         my $groups = Bugzilla::Group->new_from_list(\@gids);
127         $self->{group_controls}->{$_->id}->{group} = $_ foreach @$groups;
128     }
129     return $self->{group_controls};
130 }
131
132 sub groups_mandatory_for {
133     my ($self, $user) = @_;
134     my $groups = $user->groups_as_string;
135     my $mandatory = CONTROLMAPMANDATORY;
136     # For membercontrol we don't check group_id IN, because if membercontrol
137     # is Mandatory, the group is Mandatory for everybody, regardless of their
138     # group membership.
139     my $ids = Bugzilla->dbh->selectcol_arrayref(
140         "SELECT group_id FROM group_control_map
141           WHERE product_id = ?
142                 AND (membercontrol = $mandatory
143                      OR (othercontrol = $mandatory
144                          AND group_id NOT IN ($groups)))",
145         undef, $self->id);
146     return Bugzilla::Group->new_from_list($ids);
147 }
148
149 sub groups_valid {
150     my ($self) = @_;
151     return $self->{groups_valid} if defined $self->{groups_valid};
152     
153     # Note that we don't check OtherControl below, because there is no
154     # valid NA/* combination.
155     my $ids = Bugzilla->dbh->selectcol_arrayref(
156         "SELECT DISTINCT group_id
157           FROM group_control_map AS gcm
158                INNER JOIN groups ON gcm.group_id = groups.id
159          WHERE product_id = ? AND isbuggroup = 1
160                AND membercontrol != " . CONTROLMAPNA,  undef, $self->id);
161     $self->{groups_valid} = Bugzilla::Group->new_from_list($ids);
162     return $self->{groups_valid};
163 }
164
165 sub versions {
166     my $self = shift;
167     my $dbh = Bugzilla->dbh;
168
169     if (!defined $self->{versions}) {
170         my $ids = $dbh->selectcol_arrayref(q{
171             SELECT id FROM versions
172             WHERE product_id = ?}, undef, $self->id);
173
174         $self->{versions} = Bugzilla::Version->new_from_list($ids);
175     }
176     return $self->{versions};
177 }
178
179 sub milestones {
180     my $self = shift;
181     my $dbh = Bugzilla->dbh;
182
183     if (!defined $self->{milestones}) {
184         my $ids = $dbh->selectcol_arrayref(q{
185             SELECT id FROM milestones
186              WHERE product_id = ?}, undef, $self->id);
187  
188         $self->{milestones} = Bugzilla::Milestone->new_from_list($ids);
189     }
190     return $self->{milestones};
191 }
192
193 sub bug_count {
194     my $self = shift;
195     my $dbh = Bugzilla->dbh;
196
197     if (!defined $self->{'bug_count'}) {
198         $self->{'bug_count'} = $dbh->selectrow_array(qq{
199             SELECT COUNT(bug_id) FROM bugs
200             WHERE product_id = ?}, undef, $self->id);
201
202     }
203     return $self->{'bug_count'};
204 }
205
206 sub bug_ids {
207     my $self = shift;
208     my $dbh = Bugzilla->dbh;
209
210     if (!defined $self->{'bug_ids'}) {
211         $self->{'bug_ids'} = 
212             $dbh->selectcol_arrayref(q{SELECT bug_id FROM bugs
213                                        WHERE product_id = ?},
214                                      undef, $self->id);
215     }
216     return $self->{'bug_ids'};
217 }
218
219 sub user_has_access {
220     my ($self, $user) = @_;
221
222     return Bugzilla->dbh->selectrow_array(
223         'SELECT CASE WHEN group_id IS NULL THEN 1 ELSE 0 END
224            FROM products LEFT JOIN group_control_map
225                 ON group_control_map.product_id = products.id
226                    AND group_control_map.entry != 0
227                    AND group_id NOT IN (' . $user->groups_as_string . ')
228           WHERE products.id = ? ' . Bugzilla->dbh->sql_limit(1),
229           undef, $self->id);
230 }
231
232 sub flag_types {
233     my $self = shift;
234
235     if (!defined $self->{'flag_types'}) {
236         $self->{'flag_types'} = {};
237         foreach my $type ('bug', 'attachment') {
238             my %flagtypes;
239             foreach my $component (@{$self->components}) {
240                 foreach my $flagtype (@{$component->flag_types->{$type}}) {
241                     $flagtypes{$flagtype->{'id'}} ||= $flagtype;
242                 }
243             }
244             $self->{'flag_types'}->{$type} = [sort { $a->{'sortkey'} <=> $b->{'sortkey'}
245                                                     || $a->{'name'} cmp $b->{'name'} } values %flagtypes];
246         }
247     }
248     return $self->{'flag_types'};
249 }
250
251 ###############################
252 ####      Accessors      ######
253 ###############################
254
255 sub description       { return $_[0]->{'description'};       }
256 sub milestone_url     { return $_[0]->{'milestoneurl'};      }
257 sub disallow_new      { return $_[0]->{'disallownew'};       }
258 sub votes_per_user    { return $_[0]->{'votesperuser'};      }
259 sub max_votes_per_bug { return $_[0]->{'maxvotesperbug'};    }
260 sub votes_to_confirm  { return $_[0]->{'votestoconfirm'};    }
261 sub default_milestone { return $_[0]->{'defaultmilestone'};  }
262 sub classification_id { return $_[0]->{'classification_id'}; }
263
264 ###############################
265 ####      Subroutines    ######
266 ###############################
267
268 sub check_product {
269     my ($product_name) = @_;
270
271     unless ($product_name) {
272         ThrowUserError('product_not_specified');
273     }
274     my $product = new Bugzilla::Product({name => $product_name});
275     unless ($product) {
276         ThrowUserError('product_doesnt_exist',
277                        {'product' => $product_name});
278     }
279     return $product;
280 }
281
282 1;
283
284 __END__
285
286 =head1 NAME
287
288 Bugzilla::Product - Bugzilla product class.
289
290 =head1 SYNOPSIS
291
292     use Bugzilla::Product;
293
294     my $product = new Bugzilla::Product(1);
295     my $product = new Bugzilla::Product({ name => 'AcmeProduct' });
296
297     my @components      = $product->components();
298     my $groups_controls = $product->group_controls();
299     my @milestones      = $product->milestones();
300     my @versions        = $product->versions();
301     my $bugcount        = $product->bug_count();
302     my $bug_ids         = $product->bug_ids();
303     my $has_access      = $product->user_has_access($user);
304     my $flag_types      = $product->flag_types();
305
306     my $id               = $product->id;
307     my $name             = $product->name;
308     my $description      = $product->description;
309     my $milestoneurl     = $product->milestone_url;
310     my disallownew       = $product->disallow_new;
311     my votesperuser      = $product->votes_per_user;
312     my maxvotesperbug    = $product->max_votes_per_bug;
313     my votestoconfirm    = $product->votes_to_confirm;
314     my $defaultmilestone = $product->default_milestone;
315     my $classificationid = $product->classification_id;
316
317 =head1 DESCRIPTION
318
319 Product.pm represents a product object. It is an implementation
320 of L<Bugzilla::Object>, and thus provides all methods that
321 L<Bugzilla::Object> provides.
322
323 The methods that are specific to C<Bugzilla::Product> are listed 
324 below.
325
326 =head1 METHODS
327
328 =over
329
330 =item C<components>
331
332  Description: Returns an array of component objects belonging to
333               the product.
334
335  Params:      none.
336
337  Returns:     An array of Bugzilla::Component object.
338
339 =item C<group_controls()>
340
341  Description: Returns a hash (group id as key) with all product
342               group controls.
343
344  Params:      none.
345
346  Returns:     A hash with group id as key and hash containing 
347               a Bugzilla::Group object and the properties of group
348               relative to the product.
349
350 =item C<groups_mandatory_for>
351
352 =over
353
354 =item B<Description>
355
356 Tells you what groups are mandatory for bugs in this product.
357
358 =item B<Params>
359
360 C<$user> - The user who you want to check.
361
362 =item B<Returns> An arrayref of C<Bugzilla::Group> objects.
363
364 =back
365
366 =item C<groups_valid>
367
368 =over
369
370 =item B<Description>
371
372 Returns an arrayref of L<Bugzilla::Group> objects, representing groups
373 that bugs could validly be restricted to within this product. Used mostly
374 by L<Bugzilla::Bug> to assure that you're adding valid groups to a bug.
375
376 B<Note>: This doesn't check whether or not the current user can add/remove
377 bugs to/from these groups. It just tells you that bugs I<could be in> these
378 groups, in this product.
379
380 =item B<Params> (none)
381
382 =item B<Returns> An arrayref of L<Bugzilla::Group> objects.
383
384 =back
385
386 =item C<versions>
387
388  Description: Returns all valid versions for that product.
389
390  Params:      none.
391
392  Returns:     An array of Bugzilla::Version objects.
393
394 =item C<milestones>
395
396  Description: Returns all valid milestones for that product.
397
398  Params:      none.
399
400  Returns:     An array of Bugzilla::Milestone objects.
401
402 =item C<bug_count()>
403
404  Description: Returns the total of bugs that belong to the product.
405
406  Params:      none.
407
408  Returns:     Integer with the number of bugs.
409
410 =item C<bug_ids()>
411
412  Description: Returns the IDs of bugs that belong to the product.
413
414  Params:      none.
415
416  Returns:     An array of integer.
417
418 =item C<user_has_access()>
419
420  Description: Tells you whether or not the user is allowed to enter
421               bugs into this product, based on the C<entry> group
422               control. To see whether or not a user can actually
423               enter a bug into a product, use C<$user-&gt;can_enter_product>.
424
425  Params:      C<$user> - A Bugzilla::User object.
426
427  Returns      C<1> If this user's groups allow him C<entry> access to
428               this Product, C<0> otherwise.
429
430 =item C<flag_types()>
431
432  Description: Returns flag types available for at least one of
433               its components.
434
435  Params:      none.
436
437  Returns:     Two references to an array of flagtype objects.
438
439 =back
440
441 =head1 SUBROUTINES
442
443 =over
444
445 =item C<preload>
446
447 When passed an arrayref of C<Bugzilla::Product> objects, preloads their
448 L</milestones>, L</components>, and L</versions>, which is much faster
449 than calling those accessors on every item in the array individually.
450
451 This function is not exported, so must be called like 
452 C<Bugzilla::Product::preload($products)>.
453
454 =item C<check_product($product_name)>
455
456  Description: Checks if the product name was passed in and if is a valid
457               product.
458
459  Params:      $product_name - String with a product name.
460
461  Returns:     Bugzilla::Product object.
462
463 =back
464
465 =head1 SEE ALSO
466
467 L<Bugzilla::Object>
468
469 =cut