(CVE-2013-0786) [SECURITY] build_subselect() leaks the existence of products and...
[WebKit-https.git] / Websites / bugs.webkit.org / Bugzilla / Group.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 # The Initial Developer of the Original Code is Netscape Communications
16 # Corporation. Portions created by Netscape are
17 # Copyright (C) 1998 Netscape Communications Corporation. All
18 # Rights Reserved.
19 #
20 # Contributor(s): Joel Peshkin <bugreport@peshkin.net>
21 #                 Erik Stambaugh <erik@dasbistro.com>
22 #                 Tiago R. Mello <timello@async.com.br>
23 #                 Max Kanat-Alexander <mkanat@bugzilla.org>
24
25 use strict;
26
27 package Bugzilla::Group;
28
29 use base qw(Bugzilla::Object);
30
31 use Bugzilla::Constants;
32 use Bugzilla::Util;
33 use Bugzilla::Error;
34 use Bugzilla::Config qw(:admin);
35
36 ###############################
37 ##### Module Initialization ###
38 ###############################
39
40 use constant DB_COLUMNS => qw(
41     groups.id
42     groups.name
43     groups.description
44     groups.isbuggroup
45     groups.userregexp
46     groups.isactive
47     groups.icon_url
48 );
49
50 use constant DB_TABLE => 'groups';
51
52 use constant LIST_ORDER => 'isbuggroup, name';
53
54 use constant VALIDATORS => {
55     name        => \&_check_name,
56     description => \&_check_description,
57     userregexp  => \&_check_user_regexp,
58     isactive    => \&_check_is_active,
59     isbuggroup  => \&_check_is_bug_group,
60     icon_url    => \&_check_icon_url,
61 };
62
63 use constant REQUIRED_CREATE_FIELDS => qw(name description isbuggroup);
64
65 use constant UPDATE_COLUMNS => qw(
66     name
67     description
68     userregexp
69     isactive
70     icon_url
71 );
72
73 # Parameters that are lists of groups.
74 use constant GROUP_PARAMS => qw(chartgroup insidergroup timetrackinggroup
75                                 querysharegroup);
76
77 ###############################
78 ####      Accessors      ######
79 ###############################
80
81 sub description  { return $_[0]->{'description'};  }
82 sub is_bug_group { return $_[0]->{'isbuggroup'};   }
83 sub user_regexp  { return $_[0]->{'userregexp'};   }
84 sub is_active    { return $_[0]->{'isactive'};     }
85 sub icon_url     { return $_[0]->{'icon_url'};     }
86
87 sub members_direct {
88     my ($self) = @_;
89     return $self->{members_direct} if defined $self->{members_direct};
90     my $dbh = Bugzilla->dbh;
91     my $user_ids = $dbh->selectcol_arrayref(
92         "SELECT user_group_map.user_id
93            FROM user_group_map
94           WHERE user_group_map.group_id = ?
95                 AND grant_type = " . GRANT_DIRECT . "
96                 AND isbless = 0", undef, $self->id);
97     require Bugzilla::User;
98     $self->{members_direct} = Bugzilla::User->new_from_list($user_ids);
99     return $self->{members_direct};
100 }
101
102 sub grant_direct {
103     my ($self, $type) = @_;
104     $self->{grant_direct} ||= {};
105     return $self->{grant_direct}->{$type} 
106         if defined $self->{members_direct}->{$type};
107     my $dbh = Bugzilla->dbh;
108
109     my $ids = $dbh->selectcol_arrayref(
110       "SELECT member_id FROM group_group_map
111         WHERE grantor_id = ? AND grant_type = $type", 
112       undef, $self->id) || [];
113
114     $self->{grant_direct}->{$type} = $self->new_from_list($ids);
115     return $self->{grant_direct}->{$type};
116 }
117
118 sub granted_by_direct {
119     my ($self, $type) = @_;
120     $self->{granted_by_direct} ||= {};
121     return $self->{granted_by_direct}->{$type}
122          if defined $self->{granted_by_direct}->{$type};
123     my $dbh = Bugzilla->dbh;
124
125     my $ids = $dbh->selectcol_arrayref(
126       "SELECT grantor_id FROM group_group_map
127         WHERE member_id = ? AND grant_type = $type",
128       undef, $self->id) || [];
129
130     $self->{granted_by_direct}->{$type} = $self->new_from_list($ids);
131     return $self->{granted_by_direct}->{$type};
132 }
133
134 ###############################
135 ####        Methods        ####
136 ###############################
137
138 sub set_description { $_[0]->set('description', $_[1]); }
139 sub set_is_active   { $_[0]->set('isactive', $_[1]);    }
140 sub set_name        { $_[0]->set('name', $_[1]);        }
141 sub set_user_regexp { $_[0]->set('userregexp', $_[1]);  }
142 sub set_icon_url    { $_[0]->set('icon_url', $_[1]);    }
143
144 sub update {
145     my $self = shift;
146     my $changes = $self->SUPER::update(@_);
147
148     if (exists $changes->{name}) {
149         my ($old_name, $new_name) = @{$changes->{name}};
150         my $update_params;
151         foreach my $group (GROUP_PARAMS) {
152             if ($old_name eq Bugzilla->params->{$group}) {
153                 SetParam($group, $new_name);
154                 $update_params = 1;
155             }
156         }
157         write_params() if $update_params;
158     }
159
160     # If we've changed this group to be active, fix any Mandatory groups.
161     $self->_enforce_mandatory if (exists $changes->{isactive} 
162                                   && $changes->{isactive}->[1]);
163
164     $self->_rederive_regexp() if exists $changes->{userregexp};
165     return $changes;
166 }
167
168 # Add missing entries in bug_group_map for bugs created while
169 # a mandatory group was disabled and which is now enabled again.
170 sub _enforce_mandatory {
171     my ($self) = @_;
172     my $dbh = Bugzilla->dbh;
173     my $gid = $self->id;
174
175     my $bug_ids =
176       $dbh->selectcol_arrayref('SELECT bugs.bug_id
177                                   FROM bugs
178                             INNER JOIN group_control_map
179                                     ON group_control_map.product_id = bugs.product_id
180                              LEFT JOIN bug_group_map
181                                     ON bug_group_map.bug_id = bugs.bug_id
182                                    AND bug_group_map.group_id = group_control_map.group_id
183                                  WHERE group_control_map.group_id = ?
184                                    AND group_control_map.membercontrol = ?
185                                    AND bug_group_map.group_id IS NULL',
186                                  undef, ($gid, CONTROLMAPMANDATORY));
187
188     my $sth = $dbh->prepare('INSERT INTO bug_group_map (bug_id, group_id) VALUES (?, ?)');
189     foreach my $bug_id (@$bug_ids) {
190         $sth->execute($bug_id, $gid);
191     }
192 }
193
194 sub is_active_bug_group {
195     my $self = shift;
196     return $self->is_active && $self->is_bug_group;
197 }
198
199 sub _rederive_regexp {
200     my ($self) = @_;
201     RederiveRegexp($self->user_regexp, $self->id);
202 }
203
204 sub members_non_inherited {
205     my ($self) = @_;
206     return $self->{members_non_inherited} 
207            if exists $self->{members_non_inherited};
208
209     my $member_ids = Bugzilla->dbh->selectcol_arrayref(
210         'SELECT DISTINCT user_id FROM user_group_map 
211           WHERE isbless = 0 AND group_id = ?',
212         undef, $self->id) || [];
213     require Bugzilla::User;
214     $self->{members_non_inherited} = Bugzilla::User->new_from_list($member_ids);
215     return $self->{members_non_inherited};
216 }
217
218 ################################
219 #####  Module Subroutines    ###
220 ################################
221
222 sub create {
223     my $class = shift;
224     my ($params) = @_;
225     my $dbh = Bugzilla->dbh;
226
227     print get_text('install_group_create', { name => $params->{name} }) . "\n" 
228         if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
229
230     my $group = $class->SUPER::create(@_);
231
232     # Since we created a new group, give the "admin" group all privileges
233     # initially.
234     my $admin = new Bugzilla::Group({name => 'admin'});
235     # This function is also used to create the "admin" group itself,
236     # so there's a chance it won't exist yet.
237     if ($admin) {
238         my $sth = $dbh->prepare('INSERT INTO group_group_map
239                                  (member_id, grantor_id, grant_type)
240                                  VALUES (?, ?, ?)');
241         $sth->execute($admin->id, $group->id, GROUP_MEMBERSHIP);
242         $sth->execute($admin->id, $group->id, GROUP_BLESS);
243         $sth->execute($admin->id, $group->id, GROUP_VISIBLE);
244     }
245
246     $group->_rederive_regexp() if $group->user_regexp;
247     return $group;
248 }
249
250 sub ValidateGroupName {
251     my ($name, @users) = (@_);
252     my $dbh = Bugzilla->dbh;
253     my $query = "SELECT id FROM groups " .
254                 "WHERE name = ?";
255     if (Bugzilla->params->{'usevisibilitygroups'}) {
256         my @visible = (-1);
257         foreach my $user (@users) {
258             $user && push @visible, @{$user->visible_groups_direct};
259         }
260         my $visible = join(', ', @visible);
261         $query .= " AND id IN($visible)";
262     }
263     my $sth = $dbh->prepare($query);
264     $sth->execute($name);
265     my ($ret) = $sth->fetchrow_array();
266     return $ret;
267 }
268
269 # This sub is not perldoc'ed because we expect it to go away and
270 # just become the _rederive_regexp private method.
271 sub RederiveRegexp {
272     my ($regexp, $gid) = @_;
273     my $dbh = Bugzilla->dbh;
274     my $sth = $dbh->prepare("SELECT userid, login_name, group_id
275                                FROM profiles
276                           LEFT JOIN user_group_map
277                                  ON user_group_map.user_id = profiles.userid
278                                 AND group_id = ?
279                                 AND grant_type = ?
280                                 AND isbless = 0");
281     my $sthadd = $dbh->prepare("INSERT INTO user_group_map
282                                  (user_id, group_id, grant_type, isbless)
283                                  VALUES (?, ?, ?, 0)");
284     my $sthdel = $dbh->prepare("DELETE FROM user_group_map
285                                  WHERE user_id = ? AND group_id = ?
286                                  AND grant_type = ? and isbless = 0");
287     $sth->execute($gid, GRANT_REGEXP);
288     while (my ($uid, $login, $present) = $sth->fetchrow_array()) {
289         if (($regexp =~ /\S+/) && ($login =~ m/$regexp/i))
290         {
291             $sthadd->execute($uid, $gid, GRANT_REGEXP) unless $present;
292         } else {
293             $sthdel->execute($uid, $gid, GRANT_REGEXP) if $present;
294         }
295     }
296 }
297
298 ###############################
299 ###       Validators        ###
300 ###############################
301
302 sub _check_name {
303     my ($invocant, $name) = @_;
304     $name = trim($name);
305     $name || ThrowUserError("empty_group_name");
306     # If we're creating a Group or changing the name...
307     if (!ref($invocant) || $invocant->name ne $name) {
308         my $exists = new Bugzilla::Group({name => $name });
309         ThrowUserError("group_exists", { name => $name }) if $exists;
310     }
311     return $name;
312 }
313
314 sub _check_description {
315     my ($invocant, $desc) = @_;
316     $desc = trim($desc);
317     $desc || ThrowUserError("empty_group_description");
318     return $desc;
319 }
320
321 sub _check_user_regexp {
322     my ($invocant, $regex) = @_;
323     $regex = trim($regex) || '';
324     ThrowUserError("invalid_regexp") unless (eval {qr/$regex/});
325     return $regex;
326 }
327
328 sub _check_is_active { return $_[1] ? 1 : 0; }
329 sub _check_is_bug_group {
330     return $_[1] ? 1 : 0;
331 }
332
333 sub _check_icon_url { return $_[1] ? clean_text($_[1]) : undef; }
334
335 1;
336
337 __END__
338
339 =head1 NAME
340
341 Bugzilla::Group - Bugzilla group class.
342
343 =head1 SYNOPSIS
344
345     use Bugzilla::Group;
346
347     my $group = new Bugzilla::Group(1);
348     my $group = new Bugzilla::Group({name => 'AcmeGroup'});
349
350     my $id           = $group->id;
351     my $name         = $group->name;
352     my $description  = $group->description;
353     my $user_reg_exp = $group->user_reg_exp;
354     my $is_active    = $group->is_active;
355     my $icon_url     = $group->icon_url;
356     my $is_active_bug_group = $group->is_active_bug_group;
357
358     my $group_id = Bugzilla::Group::ValidateGroupName('admin', @users);
359     my @groups   = Bugzilla::Group->get_all;
360
361 =head1 DESCRIPTION
362
363 Group.pm represents a Bugzilla Group object. It is an implementation
364 of L<Bugzilla::Object>, and thus has all the methods that L<Bugzilla::Object>
365 provides, in addition to any methods documented below.
366
367 =head1 SUBROUTINES
368
369 =over
370
371 =item C<create>
372
373 Note that in addition to what L<Bugzilla::Object/create($params)>
374 normally does, this function also makes the new group be inherited
375 by the C<admin> group. That is, the C<admin> group will automatically
376 be a member of this group.
377
378 =item C<ValidateGroupName($name, @users)>
379
380  Description: ValidateGroupName checks to see if ANY of the users
381               in the provided list of user objects can see the
382               named group.
383
384  Params:      $name - String with the group name.
385               @users - An array with Bugzilla::User objects.
386
387  Returns:     It returns the group id if successful
388               and undef otherwise.
389
390 =back
391
392 =head1 METHODS
393
394 =over
395
396 =item C<members_non_inherited>
397
398 Returns an arrayref of L<Bugzilla::User> objects representing people who are
399 "directly" in this group, meaning that they're in it because they match
400 the group regular expression, or they have been actually added to the
401 group manually.
402
403 =back