Commit 71c08104 authored by Per Cederqvist's avatar Per Cederqvist

Imported Bugzilla 4.2.4.

parent 5883d001
No preview for this file type
......@@ -592,7 +592,8 @@ sub fields {
}
}
return $do_by_name ? \%requested : [values %requested];
return $do_by_name ? \%requested
: [sort { $a->sortkey <=> $b->sortkey || $a->name cmp $b->name } values %requested];
}
sub active_custom_fields {
......@@ -847,7 +848,7 @@ in a hashref:
=item C<by_name>
If false (or not specified), this method will return an arrayref of
the requested fields. The order of the returned fields is random.
the requested fields.
If true, this method will return a hashref of fields, where the keys
are field names and the valules are L<Bugzilla::Field> objects.
......
......@@ -723,11 +723,8 @@ sub validate_obsolete {
$attachment->validate_can_edit($bug->product_id)
|| ThrowUserError('illegal_attachment_edit', { attach_id => $attachment->id });
$vars->{'description'} = $attachment->description;
if ($attachment->bug_id != $bug->bug_id) {
$vars->{'my_bug_id'} = $bug->bug_id;
$vars->{'attach_bug_id'} = $attachment->bug_id;
ThrowCodeError('mismatched_bug_ids_on_obsolete', $vars);
}
......
......@@ -37,7 +37,6 @@ sub process_diff {
$last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
# Actually print out the patch.
print $cgi->header(-type => 'text/plain',
-x_content_type_options => "nosniff",
-expires => '+3M');
disable_utf8();
$reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
......@@ -119,7 +118,6 @@ sub process_interdiff {
$last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
# Actually print out the patch.
print $cgi->header(-type => 'text/plain',
-x_content_type_options => "nosniff",
-expires => '+3M');
disable_utf8();
}
......
......@@ -523,17 +523,14 @@ sub possible_duplicates {
if ($dbh->FULLTEXT_OR) {
my $joined_terms = join($dbh->FULLTEXT_OR, @words);
($where_sql, $relevance_sql) =
$dbh->sql_fulltext_search('bugs_fulltext.short_desc',
$joined_terms, 1);
$dbh->sql_fulltext_search('bugs_fulltext.short_desc', $joined_terms);
$relevance_sql ||= $where_sql;
}
else {
my (@where, @relevance);
my $count = 0;
foreach my $word (@words) {
$count++;
my ($term, $rel_term) = $dbh->sql_fulltext_search(
'bugs_fulltext.short_desc', $word, $count);
'bugs_fulltext.short_desc', $word);
push(@where, $term);
push(@relevance, $rel_term || $term);
}
......@@ -2878,7 +2875,8 @@ sub add_see_also {
# ref bug id for sending changes email.
my $ref_bug = delete $field_values->{ref_bug};
if ($class->isa('Bugzilla::BugUrl::Bugzilla::Local')
and !$skip_recursion)
and !$skip_recursion
and $ref_bug->check_can_change_field('see_also', '', $self->id, \$privs))
{
$ref_bug->add_see_also($self->id, 'skip_recursion');
push @{ $self->{_update_ref_bugs} }, $ref_bug;
......@@ -2910,12 +2908,15 @@ sub remove_see_also {
# we need to notify changes for that bug too.
$removed_bug_url = $removed_bug_url->[0];
if (!$skip_recursion and $removed_bug_url
and $removed_bug_url->isa('Bugzilla::BugUrl::Bugzilla::Local'))
and $removed_bug_url->isa('Bugzilla::BugUrl::Bugzilla::Local')
and $removed_bug_url->ref_bug_url)
{
my $ref_bug
= Bugzilla::Bug->check($removed_bug_url->ref_bug_url->bug_id);
if (Bugzilla->user->can_edit_product($ref_bug->product_id)) {
if (Bugzilla->user->can_edit_product($ref_bug->product_id)
and $ref_bug->check_can_change_field('see_also', $self->id, '', \$privs))
{
my $self_url = $removed_bug_url->local_uri($self->id);
$ref_bug->remove_see_also($self_url, 'skip_recursion');
push @{ $self->{_update_ref_bugs} }, $ref_bug;
......
......@@ -316,6 +316,14 @@ sub header {
unshift(@_, '-x_frame_options' => 'SAMEORIGIN');
}
# Add X-XSS-Protection header to prevent simple XSS attacks
# and enforce the blocking (rather than the rewriting) mode.
unshift(@_, '-x_xss_protection' => '1; mode=block');
# Add X-Content-Type-Options header to prevent browsers sniffing
# the MIME type away from the declared Content-Type.
unshift(@_, '-x_content_type_options' => 'nosniff');
return $self->SUPER::header(@_) || "";
}
......
......@@ -202,7 +202,7 @@ use Memoize;
# CONSTANTS
#
# Bugzilla version
use constant BUGZILLA_VERSION => "4.2.3";
use constant BUGZILLA_VERSION => "4.2.4";
# Location of the remote and local XML files to track new releases.
use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
......
......@@ -941,7 +941,9 @@ sub _bz_raw_column_info {
$index = name of an index
Returns: An abstract index definition, always in hashref format.
If the index does not exist, the function returns undef.
=cut
sub bz_index_info_real {
my ($self, $table, $index) = @_;
......
......@@ -56,6 +56,8 @@ use constant BLOB_TYPE => { ora_type => ORA_BLOB };
use constant MIN_LONG_READ_LEN => 32 * 1024;
use constant FULLTEXT_OR => ' OR ';
our $fulltext_label = 0;
sub new {
my ($class, $params) = @_;
my ($user, $pass, $host, $dbname, $port) =
......@@ -124,7 +126,8 @@ sub bz_explain {
sub sql_group_concat {
my ($self, $text, $separator) = @_;
$separator = $self->quote(', ') if !defined $separator;
return "group_concat(T_CLOB_DELIM($text, $separator))";
my ($distinct, $rest) = $text =~/^(\s*DISTINCT\s|)(.+)$/i;
return "group_concat($distinct T_CLOB_DELIM(NVL($rest, ' '), $separator))";
}
sub sql_regexp {
......@@ -170,11 +173,13 @@ sub sql_from_days{
return " TO_DATE($date,'J') ";
}
sub sql_fulltext_search {
my ($self, $column, $text, $label) = @_;
my ($self, $column, $text) = @_;
$text = $self->quote($text);
trick_taint($text);
return "CONTAINS($column,$text,$label) > 0", "SCORE($label)";
$fulltext_label++;
return "CONTAINS($column,$text,$fulltext_label) > 0", "SCORE($fulltext_label)";
}
sub sql_date_format {
......@@ -545,14 +550,17 @@ sub bz_setup_database {
. " RETURN NUMBER IS BEGIN RETURN LENGTH(COLUMN_NAME); END;");
# Create types for group_concat
my $t_clob_delim = $self->selectcol_arrayref("
SELECT TYPE_NAME FROM USER_TYPES WHERE TYPE_NAME=?",
undef, 'T_CLOB_DELIM');
if ( !@$t_clob_delim ) {
$self->do("CREATE OR REPLACE TYPE T_CLOB_DELIM AS OBJECT "
. "( p_CONTENT CLOB, p_DELIMITER VARCHAR2(256));");
}
$self->do("DROP TYPE T_GROUP_CONCAT");
$self->do("CREATE OR REPLACE TYPE T_CLOB_DELIM AS OBJECT "
. "( p_CONTENT CLOB, p_DELIMITER VARCHAR2(256)"
. ", MAP MEMBER FUNCTION T_CLOB_DELIM_ToVarchar return VARCHAR2"
. ");");
$self->do("CREATE OR REPLACE TYPE BODY T_CLOB_DELIM IS
MAP MEMBER FUNCTION T_CLOB_DELIM_ToVarchar return VARCHAR2 is
BEGIN
RETURN p_CONTENT;
END;
END;");
$self->do("CREATE OR REPLACE TYPE T_GROUP_CONCAT AS OBJECT
( CLOB_CONTENT CLOB,
......
......@@ -215,11 +215,12 @@ sub bz_check_server_version {
my $self = shift;
my ($db) = @_;
my $server_version = $self->SUPER::bz_check_server_version(@_);
my ($major_version) = $server_version =~ /^(\d+)/;
# Pg 9 requires DBD::Pg 2.17.2 in order to properly read bytea values.
my ($major_version, $minor_version) = $server_version =~ /^0*(\d+)\.0*(\d+)/;
# Pg 9.0 requires DBD::Pg 2.17.2 in order to properly read bytea values.
# Pg 9.2 requires DBD::Pg 2.19.3 as spclocation no longer exists.
if ($major_version >= 9) {
local $db->{dbd}->{version} = '2.17.2';
local $db->{name} = $db->{name} . ' 9+';
local $db->{dbd}->{version} = ($minor_version >= 2) ? '2.19.3' : '2.17.2';
local $db->{name} = $db->{name} . " ${major_version}.$minor_version";
Bugzilla::DB::_bz_check_dbd(@_);
}
}
......
......@@ -1858,6 +1858,7 @@ C<ALTER TABLE> SQL statement
sub get_fk_ddl {
=item C<_get_fk_ddl>
=over
......@@ -1871,7 +1872,9 @@ Protected method. Translates the C<REFERENCES> item of a column into SQL.
=over
=item C<$table> - The name of the table the reference is from.
=item C<$column> - The name of the column the reference is from
=item C<$references> - The C<REFERENCES> hashref from a column.
=back
......@@ -1972,6 +1975,7 @@ Converts a TYPE from the L</ABSTRACT_SCHEMA> format into the real SQL type.
}
sub get_column {
=item C<get_column($table, $column)>
Description: Public method to get the abstract definition of a column.
......@@ -2837,6 +2841,7 @@ sub serialize_abstract {
in the same fashion as) the current version of Schema.
However, it will represent the serialized data instead of
ABSTRACT_SCHEMA.
=cut
sub deserialize_abstract {
......
......@@ -1016,7 +1016,11 @@ sub create {
# the parameter isn't sent to create().
$params->{sortkey} = undef if !exists $params->{sortkey};
$params->{type} ||= 0;
# We mark the custom field as obsolete till it has been fully created,
# to avoid race conditions when viewing bugs at the same time.
my $is_obsolete = $params->{obsolete};
$params->{obsolete} = 1 if $params->{custom};
$dbh->bz_start_transaction();
$class->check_required_create_fields(@_);
my $field_values = $class->run_create_validators($params);
......@@ -1045,6 +1049,10 @@ sub create {
# Insert a default value of "---" into the legal values table.
$dbh->do("INSERT INTO $name (value) VALUES ('---')");
}
# Restore the original obsolete state of the custom field.
$dbh->do('UPDATE fielddefs SET obsolete = 0 WHERE id = ?', undef, $field->id)
unless $is_obsolete;
}
return $field;
......
......@@ -95,7 +95,7 @@ use constant VALIDATORS => {
description => \&_check_description,
cc_list => \&_check_cc_list,
target_type => \&_check_target_type,
sortkey => \&_check_sortey,
sortkey => \&_check_sortkey,
is_active => \&Bugzilla::Object::check_boolean,
is_requestable => \&Bugzilla::Object::check_boolean,
is_requesteeble => \&Bugzilla::Object::check_boolean,
......@@ -325,7 +325,7 @@ sub _check_target_type {
return $target_type;
}
sub _check_sortey {
sub _check_sortkey {
my ($invocant, $sortkey) = @_;
(detaint_natural($sortkey) && $sortkey <= MAX_SMALLINT)
......
......@@ -189,7 +189,9 @@ sub check_members_are_visible {
my $self = shift;
my $user = Bugzilla->user;
return if !Bugzilla->params->{'usevisibilitygroups'};
my $is_visible = grep { $_->id == $_ } @{ $user->visible_groups_inherited };
my $group_id = $self->id;
my $is_visible = grep { $_ == $group_id } @{ $user->visible_groups_inherited };
if (!$is_visible) {
ThrowUserError('group_not_visible', { group => $self });
}
......
......@@ -801,7 +801,7 @@ your own C<DB_COLUMNS> subroutine in a subclass.)
The name of the column that should be considered to be the unique
"name" of this object. The 'name' is a B<string> that uniquely identifies
this Object in the database. Defaults to 'name'. When you specify
C<{name => $name}> to C<new()>, this is the column that will be
C<< {name => $name} >> to C<new()>, this is the column that will be
matched against in the DB.
=item C<ID_FIELD>
......@@ -964,7 +964,7 @@ for each placeholder in C<condition>, in order.
This is to allow subclasses to have complex parameters, and then to
translate those parameters into C<condition> and C<values> when they
call C<$self->SUPER::new> (which is this function, usually).
call C<< $self->SUPER::new >> (which is this function, usually).
If you try to call C<new> outside of a subclass with the C<condition>
and C<values> parameters, Bugzilla will throw an error. These parameters
......@@ -1089,8 +1089,9 @@ Notes: In order for this function to work in your subclass,
your subclass's L</ID_FIELD> must be of C<SERIAL>
type in the database.
Subclass Implementors: This function basically just
calls L</check_required_create_fields>, then
Subclass Implementors:
This function basically just calls
L</check_required_create_fields>, then
L</run_create_validators>, and then finally
L</insert_create_data>. So if you have a complex system that
you need to implement, you can do it by calling these
......@@ -1283,9 +1284,9 @@ C<0> otherwise.
Returns: A list of objects, or an empty list if there are none.
Notes: Note that you must call this as C<$class->get_all>. For
example, C<Bugzilla::Keyword->get_all>.
C<Bugzilla::Keyword::get_all> will not work.
Notes: Note that you must call this as $class->get_all. For
example, Bugzilla::Keyword->get_all.
Bugzilla::Keyword::get_all will not work.
=back
......
......@@ -2050,8 +2050,8 @@ sub _contact_pronoun {
my ($self, $args) = @_;
my $value = $args->{value};
my $user = $self->_user;
if ($value =~ /^\%group/) {
if ($value =~ /^\%group\.[^%]+%$/) {
$self->_contact_exact_group($args);
}
elsif ($value =~ /^(%\w+%)$/) {
......@@ -2068,11 +2068,17 @@ sub _contact_exact_group {
my $dbh = Bugzilla->dbh;
my $user = $self->_user;
# We already know $value will match this regexp, else we wouldn't be here.
$value =~ /\%group\.([^%]+)%/;
my $group = Bugzilla::Group->check({ name => $1, _error => 'invalid_group_name' });
$group->check_members_are_visible();
my $group_name = $1;
my $group = Bugzilla::Group->check({ name => $group_name, _error => 'invalid_group_name' });
# Pass $group_name instead of $group->name to the error message
# to not leak the existence of the group.
$user->in_group($group)
|| ThrowUserError('invalid_group_name', {name => $group->name});
|| ThrowUserError('invalid_group_name', { name => $group_name });
# Now that we know the user belongs to this group, it's safe
# to disclose more information.
$group->check_members_are_visible();
my $group_ids = Bugzilla::Group->flatten_group_membership($group->id);
my $table = "user_group_map_$chart_id";
......@@ -2342,9 +2348,9 @@ sub _content_matches {
# Create search terms to add to the SELECT and WHERE clauses.
my ($term1, $rterm1) =
$dbh->sql_fulltext_search("$table.$comments_col", $value, 1);
$dbh->sql_fulltext_search("$table.$comments_col", $value);
my ($term2, $rterm2) =
$dbh->sql_fulltext_search("$table.short_desc", $value, 2);
$dbh->sql_fulltext_search("$table.short_desc", $value);
$rterm1 = $term1 if !$rterm1;
$rterm2 = $term2 if !$rterm2;
......
......@@ -166,6 +166,7 @@ use constant WS_ERROR_CODE => {
group_exists => 801,
empty_group_description => 802,
invalid_regexp => 803,
invalid_group_name => 804,
# Errors thrown by the WebService itself. The ones that are negative
# conform to http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
......
......@@ -25,7 +25,9 @@ use Scalar::Util qw(blessed);
sub handle_login {
my ($self, $class, $method, $full_method) = @_;
ThrowCodeError('unknown_method', {method => $full_method}) if !$class;
# Throw error if the supplied class does not exist or the method is private
ThrowCodeError('unknown_method', {method => $full_method}) if (!$class or $method =~ /^_/);
eval "require $class";
ThrowCodeError('unknown_method', {method => $full_method}) if $@;
return if ($class->login_exempt($method)
......
......@@ -233,12 +233,18 @@ sub _filter_users_by_group {
# If no groups are specified, we return all users.
return $users if (!$group_ids and !$group_names);
my $user = Bugzilla->user;
my @groups = map { Bugzilla::Group->check({ id => $_ }) }
@{ $group_ids || [] };
my @name_groups = map { Bugzilla::Group->check($_) }
@{ $group_names || [] };
push(@groups, @name_groups);
if ($group_names) {
foreach my $name (@$group_names) {
my $group = Bugzilla::Group->check({ name => $name, _error => 'invalid_group_name' });
$user->in_group($group) || ThrowUserError('invalid_group_name', { name => $name });
push(@groups, $group);
}
}
my @in_group = grep { $self->_user_in_any_group($_, \@groups) }
@$users;
......@@ -586,10 +592,10 @@ C<real_name>, C<email>, and C<can_login> items.
=over
=item 51 (Bad Login Name or Group Name)
=item 51 (Bad Login Name or Group ID)
You passed an invalid login name in the "names" array or a bad
group name/id in the C<groups>/C<group_ids> arguments.
group ID in the C<group_ids> argument.
=item 304 (Authorization Required)
......@@ -601,6 +607,11 @@ wanted to get information about by user id.
Logged-out users cannot use the "ids" or "match" arguments to this
function.
=item 804 (Invalid Group Name)
You passed a group name in the C<groups> argument which either does not
exist or you do not belong to it.
=back
=item B<History>
......@@ -614,6 +625,9 @@ function.
=item C<include_disabled> added in Bugzilla B<4.0>. Default behavior
for C<match> has changed to only returning enabled accounts.
=item Error 804 has been added in Bugzilla 4.0.9 and 4.2.4. It's now
illegal to pass a group name you don't belong to.
=back
=back
......@@ -143,7 +143,7 @@ a hash to L</filter>, C<0> otherwise.
=head2 validate
This helps in the validation of parameters passed into the WebSerice
This helps in the validation of parameters passed into the WebService
methods. Currently it converts listed parameters into an array reference
if the client only passed a single scalar value. It modifies the parameters
hash in place so other parameters should be unaltered.
......@@ -408,8 +408,7 @@ sub view {
}
print $cgi->header(-type=>"$contenttype; name=\"$filename\"",
-content_disposition=> "$disposition; filename=\"$filename\"",
-content_length => $attachment->datasize,
-x_content_type_options => "nosniff");
-content_length => $attachment->datasize);
disable_utf8();
print $attachment->data;
}
......
......@@ -64,7 +64,6 @@ my $buffer = $cgi->query_string();
my $user = Bugzilla->login();
if (length($buffer) == 0) {
print $cgi->header(-refresh=> '10; URL=query.cgi');
ThrowUserError("buglist_parameters_required");
}
......
......@@ -20,7 +20,6 @@
# Max Kanat-Alexander <mkanat@bugzilla.org>
use strict;
use warnings;
use lib qw(. lib);
use Bugzilla;
......@@ -82,6 +81,8 @@ $dbh->bz_start_transaction();
foreach my $pair (@translation) {
my ($from, $to) = @$pair;
print "Converting $from to $to...\n";
# There is no FK on bugs.bug_status pointing to bug_status.value,
# so it's fine to update the bugs table first.
$dbh->do('UPDATE bugs SET bug_status = ? WHERE bug_status = ?',
undef, $to, $from);
......@@ -103,11 +104,53 @@ foreach my $pair (@translation) {
# If the new status already exists, just delete the old one, but retain
# the workflow items from it.
if (my $existing = new Bugzilla::Status({ name => $to })) {
my $new_status = new Bugzilla::Status({ name => $to });
my $old_status = new Bugzilla::Status({ name => $from });
if ($new_status && $old_status) {
my $to_id = $new_status->id;
my $from_id = $old_status->id;
# The subselect collects existing transitions from the target bug status.
# The main select collects existing transitions from the renamed bug status.
# The diff tells us which transitions are missing from the target bug status.
my $missing_transitions =
$dbh->selectcol_arrayref('SELECT sw1.new_status
FROM status_workflow sw1
WHERE sw1.old_status = ?
AND sw1.new_status NOT IN (SELECT sw2.new_status
FROM status_workflow sw2
WHERE sw2.old_status = ?)',
undef, ($from_id, $to_id));
$dbh->do('UPDATE status_workflow SET old_status = ? WHERE old_status = ? AND '
. $dbh->sql_in('new_status', $missing_transitions),
undef, ($to_id, $from_id)) if @$missing_transitions;
# The subselect collects existing transitions to the target bug status.
# The main select collects existing transitions to the renamed bug status.
# The diff tells us which transitions are missing to the target bug status.
# We have to explicitly exclude NULL from the subselect, because NOT IN
# doesn't know what to do with it (neither true nor false) and no data is returned.
$missing_transitions =
$dbh->selectcol_arrayref('SELECT sw1.old_status
FROM status_workflow sw1
WHERE sw1.new_status = ?
AND sw1.old_status NOT IN (SELECT sw2.old_status
FROM status_workflow sw2
WHERE sw2.new_status = ?
AND sw2.old_status IS NOT NULL)',
undef, ($from_id, $to_id));
$dbh->do('UPDATE status_workflow SET new_status = ? WHERE new_status = ? AND '
. $dbh->sql_in('old_status', $missing_transitions),
undef, ($to_id, $from_id)) if @$missing_transitions;
# Delete rows where old_status = new_status, and then the old status itself.
$dbh->do('DELETE FROM status_workflow WHERE old_status = new_status');
$dbh->do('DELETE FROM bug_status WHERE value = ?', undef, $from);
}
# Otherwise, rename the old status to the new one.
else {
elsif ($old_status) {
$dbh->do('UPDATE bug_status SET value = ? WHERE value = ?',
undef, $to, $from);
}
......
......@@ -2,7 +2,7 @@
<HTML
><HEAD
><TITLE
>The Bugzilla Guide - 4.2.3
>The Bugzilla Guide - 4.2.4
Release</TITLE
><META
NAME="GENERATOR"
......@@ -43,7 +43,7 @@ CLASS="TITLEPAGE"
CLASS="title"
><A
NAME="AEN2"
>The Bugzilla Guide - 4.2.3
>The Bugzilla Guide - 4.2.4
Release</A
></H1
><H3
......@@ -51,7 +51,7 @@ CLASS="corpauthor"
>The Bugzilla Team</H3
><P
CLASS="pubdate"
>2012-08-30<BR></P
>2012-11-13<BR></P
><DIV
><DIV