Commit a383bcdd authored by Per Cederqvist's avatar Per Cederqvist

Merge tag 'bugzilla-4.4.8'

parents e0e8a24d 356d6fd5
This is a Bazaar control directory.
Do not change any files in this directory.
See http://bazaar.canonical.com/ for more information about Bazaar.
Bazaar-NG meta directory, format 1
Bazaar-NG Branch Reference Format 1
bzr://bzr.mozilla.org/bugzilla/4.4/
\ No newline at end of file
BZR conflict list format 1
Bazaar Working Tree Format 6 (bzr 1.14)
21ce812db962edd79e62fa8ba0fd19672fff88a5
\ No newline at end of file
......@@ -342,7 +342,7 @@ sub data {
# If there's no attachment data in the database, the attachment is stored
# in a local file, so retrieve it from there.
if (length($self->{data}) == 0) {
if (open(AH, $self->_get_local_filename())) {
if (open(AH, '<', $self->_get_local_filename())) {
local $/;
binmode AH;
$self->{data} = <AH>;
......@@ -388,7 +388,7 @@ sub datasize {
# is stored in a local file, and so retrieve its size from the file,
# or the attachment has been deleted.
unless ($self->{datasize}) {
if (open(AH, $self->_get_local_filename())) {
if (open(AH, '<', $self->_get_local_filename())) {
binmode AH;
$self->{datasize} = (stat(AH))[7];
close(AH);
......
......@@ -99,7 +99,7 @@ sub process_interdiff {
# Send through interdiff, send output directly to template.
# Must hack path so that interdiff will work.
$ENV{'PATH'} = $lc->{diffpath};
open my $interdiff_fh, "$lc->{interdiffbin} $old_filename $new_filename|";
open my $interdiff_fh, '-|', "$lc->{interdiffbin} $old_filename $new_filename";
binmode $interdiff_fh;
my ($reader, $last_reader) = setup_patch_readers("", $context);
......
......@@ -354,14 +354,16 @@ sub new {
sub check {
my $class = shift;
my ($id, $field) = @_;
ThrowUserError('improper_bug_id_field_value', { field => $field }) unless defined $id;
my ($param, $field) = @_;
# Bugzilla::Bug throws lots of special errors, so we don't call
# SUPER::check, we just call our new and do our own checks.
$id = trim($id);
my $self = $class->new($id);
my $id = ref($param)
? ($param->{id} = trim($param->{id}))
: ($param = trim($param));
ThrowUserError('improper_bug_id_field_value', { field => $field }) unless defined $id;
my $self = $class->new($param);
if ($self->{error}) {
# For error messages, use the id that was returned by new(), because
......
......@@ -344,6 +344,7 @@ sub header {
sub param {
my $self = shift;
local $CGI::LIST_CONTEXT_WARN = 0;
# When we are just requesting the value of a parameter...
if (scalar(@_) == 1) {
......
......@@ -23,7 +23,7 @@ use base qw(Exporter);
qw(check_multi check_numeric check_regexp check_url check_group
check_sslbase check_priority check_severity check_platform
check_opsys check_shadowdb check_urlbase check_webdotbase
check_user_verify_class check_ip
check_user_verify_class check_ip check_smtp_server
check_mail_delivery_method check_notification check_utf8
check_bug_status check_smtp_auth check_theschwartz_available
check_maxattachmentsize check_email check_smtp_ssl
......@@ -231,7 +231,7 @@ sub check_webdotbase {
# Check .htaccess allows access to generated images
my $webdotdir = bz_locations()->{'webdotdir'};
if(-e "$webdotdir/.htaccess") {
open HTACCESS, "$webdotdir/.htaccess";
open HTACCESS, "<", "$webdotdir/.htaccess";
if(! grep(/ \\\.png\$/,<HTACCESS>)) {
return "Dependency graph images are not accessible.\nAssuming that you have not modified the file, delete $webdotdir/.htaccess and re-run checksetup.pl to rectify.\n";
}
......@@ -325,6 +325,19 @@ sub check_notification {
return "";
}
sub check_smtp_server {
my $host = shift;
my $port;
if ($host =~ /:/) {
($host, $port) = split(/:/, $host, 2);
unless ($port && detaint_natural($port)) {
return "Invalid port. It must be an integer (typically 25, 465 or 587)";
}
}
return "";
}
sub check_smtp_auth {
my $username = shift;
if ($username and !Bugzilla->feature('smtp_auth')) {
......
......@@ -49,7 +49,8 @@ sub get_param_list {
{
name => 'smtpserver',
type => 't',
default => 'localhost'
default => 'localhost',
checker => \&check_smtp_server
},
{
name => 'smtp_username',
......
......@@ -182,7 +182,7 @@ use Memoize;
# CONSTANTS
#
# Bugzilla version
use constant BUGZILLA_VERSION => "4.4.6";
use constant BUGZILLA_VERSION => "4.4.8";
# Location of the remote and local XML files to track new releases.
use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
......
......@@ -577,8 +577,11 @@ sub bz_add_column {
my $current_def = $self->bz_column_info($table, $name);
if (!$current_def) {
# REFERENCES need to happen later and not be created right away
my $trimmed_def = dclone($new_def);
delete $trimmed_def->{REFERENCES};
my @statements = $self->_bz_real_schema->get_add_column_ddl(
$table, $name, $new_def,
$table, $name, $trimmed_def,
defined $init_value ? $self->quote($init_value) : undef);
print get_text('install_column_add',
{ column => $name, table => $table }) . "\n"
......@@ -592,14 +595,14 @@ sub bz_add_column {
# column exists there and has a REFERENCES item.
# bz_setup_foreign_keys will then add this FK at the end of
# Install::DB.
my $col_abstract =
my $col_abstract =
$self->_bz_schema->get_column_abstract($table, $name);
if (exists $col_abstract->{REFERENCES}) {
my $new_fk = dclone($col_abstract->{REFERENCES});
$new_fk->{created} = 0;
$new_def->{REFERENCES} = $new_fk;
}
$self->_bz_real_schema->set_column($table, $name, $new_def);
$self->_bz_store_real_schema;
}
......
......@@ -71,7 +71,7 @@ sub _throw_error {
$val = "*****" if $val =~ /password|http_pass/i;
$mesg .= "[$$] " . Data::Dumper->Dump([$val],["env($var)"]);
}
open(ERRORLOGFID, ">>$datadir/errorlog");
open(ERRORLOGFID, ">>", "$datadir/errorlog");
print ERRORLOGFID "$mesg\n";
close ERRORLOGFID;
}
......
......@@ -39,6 +39,7 @@ use Bugzilla::Util;
use Bugzilla::Group;
use Email::Address;
use List::MoreUtils qw(uniq);
use base qw(Bugzilla::Object);
......@@ -369,8 +370,6 @@ sub set_clusions {
if (!$products{$prod_id}) {
$params->{id} = $prod_id;
$products{$prod_id} = Bugzilla::Product->check($params);
$user->in_group('editcomponents', $prod_id)
|| ThrowUserError('product_access_denied', $params);
}
$prod_name = $products{$prod_id}->name;
......@@ -396,6 +395,22 @@ sub set_clusions {
$clusions{"$prod_name:$comp_name"} = "$prod_id:$comp_id";
$clusions_as_hash{$prod_id}->{$comp_id} = 1;
}
# Check the user has the editcomponent permission on products that are changing
if (! $user->in_group('editcomponents')) {
my $current_clusions = $self->$category;
my ($removed, $added)
= diff_arrays([ values %$current_clusions ], [ values %clusions ]);
my @changed_product_ids
= uniq map { substr($_, 0, index($_, ':')) } @$removed, @$added;
foreach my $product_id (@changed_product_ids) {
$user->in_group('editcomponents', $product_id)
|| ThrowUserError('product_access_denied',
{ name => $products{$product_id}->name });
}
}
# Set the changes
$self->{$category} = \%clusions;
$self->{"${category}_as_hash"} = \%clusions_as_hash;
$self->{"_update_$category"} = 1;
......
......@@ -203,8 +203,8 @@ sub set_cpan_config {
# Calling a senseless autoload that does nothing makes us
# automatically load any existing configuration.
# We want to avoid the "invalid command" message.
open(my $saveout, ">&STDOUT");
open(STDOUT, '>/dev/null');
open(my $saveout, ">&", "STDOUT");
open(STDOUT, '>', '/dev/null');
eval { CPAN->ignore_this_error_message_from_bugzilla; };
undef $@;
close(STDOUT);
......
......@@ -574,7 +574,7 @@ sub _update_old_charts {
($in_file =~ /\.orig$/i));
rename("$in_file", "$in_file.orig") or next;
open(IN, "$in_file.orig") or next;
open(IN, "<", "$in_file.orig") or next;
open(OUT, '>', $in_file) or next;
# Fields in the header
......
......@@ -327,20 +327,29 @@ use constant OPERATOR_FIELD_OVERRIDE => {
# These are fields where special action is taken depending on the
# *value* passed in to the chart, sometimes.
use constant SPECIAL_PARSING => {
# Pronoun Fields (Ones that can accept %user%, etc.)
assigned_to => \&_contact_pronoun,
cc => \&_contact_pronoun,
commenter => \&_contact_pronoun,
qa_contact => \&_contact_pronoun,
reporter => \&_contact_pronoun,
'setters.login_name' => \&_contact_pronoun,
'requestees.login_name' => \&_contact_pronoun,
# Date Fields that accept the 1d, 1w, 1m, 1y, etc. format.
creation_ts => \&_timestamp_translate,
deadline => \&_timestamp_translate,
delta_ts => \&_timestamp_translate,
# This is a sub because custom fields are dynamic
sub SPECIAL_PARSING {
my $map = {
# Pronoun Fields (Ones that can accept %user%, etc.)
assigned_to => \&_contact_pronoun,
cc => \&_contact_pronoun,
commenter => \&_contact_pronoun,
qa_contact => \&_contact_pronoun,
reporter => \&_contact_pronoun,
'setters.login_name' => \&_contact_pronoun,
'requestees.login_name' => \&_contact_pronoun,
# Date Fields that accept the 1d, 1w, 1m, 1y, etc. format.
creation_ts => \&_timestamp_translate,
deadline => \&_timestamp_translate,
delta_ts => \&_timestamp_translate,
};
foreach my $field (Bugzilla->active_custom_fields) {
if ($field->type == FIELD_TYPE_DATETIME) {
$map->{$field->name} = \&_timestamp_translate;
}
}
return $map;
};
# Information about fields that represent "users", used by _user_nonchanged.
......
......@@ -29,7 +29,7 @@ sub send {
my $pipe = gensym;
open($pipe, "| $mailer -t -oi @args")
open($pipe, "|-", "$mailer -t -oi @args")
|| return failure "Error executing $mailer: $!";
print($pipe $message->as_string)
|| return failure "Error printing via pipe to $mailer: $!";
......
......@@ -103,6 +103,7 @@ use constant OS_MAP => (
qr/\(.*Android.*\)/ => ["Android"],
# Windows
qr/\(.*Windows XP.*\)/ => ["Windows XP"],
qr/\(.*Windows NT 6\.4.*\)/ => ["Windows 10"],
qr/\(.*Windows NT 6\.3.*\)/ => ["Windows 8.1"],
qr/\(.*Windows NT 6\.2.*\)/ => ["Windows 8"],
qr/\(.*Windows NT 6\.1.*\)/ => ["Windows 7"],
......
......@@ -23,6 +23,10 @@ use constant LOGIN_EXEMPT => { };
# Methods that can modify data MUST not be listed here.
use constant READ_ONLY => ();
# Whitelist of methods that a client is allowed to access when making
# an API call.
use constant PUBLIC_METHODS => ();
sub login_exempt {
my ($class, $method) = @_;
return $class->LOGIN_EXEMPT->{$method};
......
......@@ -49,6 +49,24 @@ use constant READ_ONLY => qw(
search
);
use constant PUBLIC_METHODS => qw(
add_attachment
add_comment
attachments
comments
create
fields
get
history
legal_values
possible_duplicates
render_comment
search
update
update_see_also
update_tags
);
######################################################
# Add aliases here for old method name compatibility #
######################################################
......@@ -707,19 +725,10 @@ sub add_comment {
# Append comment
$bug->add_comment($comment, { isprivate => $params->{is_private},
work_time => $params->{work_time} });
# Capture the call to bug->update (which creates the new comment) in
# a transaction so we're sure to get the correct comment_id.
my $dbh = Bugzilla->dbh;
$dbh->bz_start_transaction();
$bug->update();
my $new_comment_id = $dbh->bz_last_key('longdescs', 'comment_id');
$dbh->bz_commit_transaction();
my $new_comment_id = $bug->{added_comments}[0]->id;
# Send mail.
Bugzilla::BugMail::Send($bug->bug_id, { changer => $user });
......
......@@ -31,6 +31,15 @@ use constant READ_ONLY => qw(
version
);
use constant PUBLIC_METHODS => qw(
extensions
last_audit_time
parameters
time
timezone
version
);
# Logged-out users do not need to know more than that.
use constant PARAMETERS_LOGGED_OUT => qw(
maintainer
......
......@@ -19,6 +19,10 @@ use constant READ_ONLY => qw(
get
);
use constant PUBLIC_METHODS => qw(
get
);
sub get {
my ($self, $params) = validate(@_, 'names', 'ids');
......
......@@ -13,6 +13,11 @@ use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::WebService::Util qw(validate translate params_to_objects);
use constant PUBLIC_METHODS => qw(
create
update
);
use constant MAPPED_RETURNS => {
userregexp => 'user_regexp',
isactive => 'is_active'
......
......@@ -23,6 +23,15 @@ use constant READ_ONLY => qw(
get_selectable_products
);
use constant PUBLIC_METHODS => qw(
create
get
get_accessible_products
get_enterable_products
get_selectable_products
update
);
use constant MAPPED_FIELDS => {
has_unconfirmed => 'allows_unconfirmed',
is_open => 'is_active',
......
......@@ -28,6 +28,7 @@ use Bugzilla::Util qw(correct_urlbase trim disable_utf8);
use HTTP::Message;
use MIME::Base64 qw(decode_base64 encode_base64);
use List::MoreUtils qw(none);
#####################################
# Public JSON::RPC Method Overrides #
......@@ -378,6 +379,11 @@ sub _argument_type_check {
}
}
# Only allowed methods to be used from our whitelist
if (none { $_ eq $method} $pkg->PUBLIC_METHODS) {
ThrowCodeError('unknown_method', { method => $self->_bz_method_name });
}
# This is the best time to do login checks.
$self->handle_login();
......
......@@ -17,6 +17,9 @@ if ($ENV{MOD_PERL}) {
}
use Bugzilla::WebService::Constants;
use Bugzilla::Error;
use List::MoreUtils qw(none);
# Allow WebService methods to call XMLRPC::Lite's type method directly
BEGIN {
......@@ -65,6 +68,14 @@ sub handle_login {
my ($self, $classes, $action, $uri, $method) = @_;
my $class = $classes->{$uri};
my $full_method = $uri . "." . $method;
# Only allowed methods to be used from the module's whitelist
my $file = $class;
$file =~ s{::}{/}g;
$file .= ".pm";
require $file;
if (none { $_ eq $method } $class->PUBLIC_METHODS) {
ThrowCodeError('unknown_method', { method => $full_method });
}
$self->SUPER::handle_login($class, $method, $full_method);
return;
}
......
......@@ -32,6 +32,15 @@ use constant READ_ONLY => qw(
get
);
use constant PUBLIC_METHODS => qw(
create
get
login
logout
offer_account_by_email
update
);
use constant MAPPED_FIELDS => {
email => 'login',
full_name => 'name',
......@@ -53,27 +62,20 @@ use constant MAPPED_RETURNS => {
sub login {
my ($self, $params) = @_;
# Check to see if we are already logged in
my $user = Bugzilla->user;
if ($user->id) {
return $self->_login_to_hash($user);
}
# Username and password params are required
foreach my $param ("login", "password") {
defined $params->{$param}
(defined $params->{$param} || defined $params->{'Bugzilla_' . $param})
|| ThrowCodeError('param_required', { param => $param });
}
# Make sure the CGI user info class works if necessary.
my $input_params = Bugzilla->input_params;
$input_params->{'Bugzilla_login'} = $params->{login};
$input_params->{'Bugzilla_password'} = $params->{password};
$input_params->{'Bugzilla_restrictlogin'} = $params->{restrict_login};
my $user = Bugzilla->login();
my $result = { id => $self->type('int', $user->id) };
if ($user->{_login_token}) {
$result->{'token'} = $user->id . "-" . $user->{_login_token};
}
return $result;
$user = Bugzilla->login();
return $self->_login_to_hash($user);
}
sub logout {
......@@ -384,6 +386,15 @@ sub _report_to_hash {
return $item;
}
sub _login_to_hash {
my ($self, $user) = @_;
my $item = { id => $self->type('int', $user->id) };
if ($user->{_login_token}) {
$item->{'token'} = $user->id . "-" . $user->{_login_token};
}
return $item;
}
1;
__END__
......
......@@ -150,13 +150,13 @@ sub fix_credentials {
# even if not calling User.login. We also do not delete them as
# User.login requires "login" and "password".
if (exists $params->{'login'} && exists $params->{'password'}) {
$params->{'Bugzilla_login'} = $params->{'login'};
$params->{'Bugzilla_password'} = $params->{'password'};
$params->{'Bugzilla_login'} = delete $params->{'login'};
$params->{'Bugzilla_password'} = delete $params->{'password'};
}
# Allow user to pass token=12345678 as a convenience which becomes
# "Bugzilla_token" which is what the auth code looks for.
if (exists $params->{'token'}) {
$params->{'Bugzilla_token'} = $params->{'token'};
$params->{'Bugzilla_token'} = delete $params->{'token'};
}
}
......
......@@ -549,7 +549,6 @@ sub insert {
my ($flags, $new_flags) = Bugzilla::Flag->extract_flags_from_cgi(
$bug, $attachment, $vars, SKIP_REQUESTEE_ON_ERROR);
$attachment->set_flags($flags, $new_flags);
$attachment->update($timestamp);
# Insert a comment about the new attachment into the database.
my $comment = $cgi->param('comment');
......@@ -580,6 +579,10 @@ sub insert {
$bug->add_cc($user) if $cgi->param('addselfcc');
$bug->update($timestamp);
# We have to update the attachment after updating the bug, to ensure new
# comments are available.
$attachment->update($timestamp);
$dbh->bz_commit_transaction;
# Define the variables and functions that will be passed to the UI template.
......@@ -702,6 +705,11 @@ sub update {