Commit 425158e8 authored by Perl Tidy's avatar Perl Tidy Committed by Dylan William Hardison

no bug - reformat all the code using the new perltidy rules

parent 450ce307
use 5.006; use 5.006;
use strict; use strict;
use warnings; use warnings;
package CPAN::Meta; package CPAN::Meta;
our $VERSION = '2.150005'; our $VERSION = '2.150005';
...@@ -92,7 +93,7 @@ BEGIN { ...@@ -92,7 +93,7 @@ BEGIN {
no strict 'refs'; no strict 'refs';
for my $attr (@STRING_READERS) { for my $attr (@STRING_READERS) {
*$attr = sub { $_[0]{ $attr } }; *$attr = sub { $_[0]{$attr} };
} }
} }
...@@ -121,10 +122,9 @@ BEGIN { ...@@ -121,10 +122,9 @@ BEGIN {
no strict 'refs'; no strict 'refs';
for my $attr (@LIST_READERS) { for my $attr (@LIST_READERS) {
*$attr = sub { *$attr = sub {
my $value = $_[0]{ $attr }; my $value = $_[0]{$attr};
croak "$attr must be called in list context" croak "$attr must be called in list context" unless wantarray;
unless wantarray; return @{_dclone($value)} if ref $value;
return @{ _dclone($value) } if ref $value;
return $value; return $value;
}; };
} }
...@@ -163,7 +163,7 @@ BEGIN { ...@@ -163,7 +163,7 @@ BEGIN {
for my $attr (@MAP_READERS) { for my $attr (@MAP_READERS) {
(my $subname = $attr) =~ s/-/_/; (my $subname = $attr) =~ s/-/_/;
*$subname = sub { *$subname = sub {
my $value = $_[0]{ $attr }; my $value = $_[0]{$attr};
return _dclone($value) if $value; return _dclone($value) if $value;
return {}; return {};
}; };
...@@ -182,7 +182,7 @@ BEGIN { ...@@ -182,7 +182,7 @@ BEGIN {
#pod =cut #pod =cut
sub custom_keys { sub custom_keys {
return grep { /^x_/i } keys %{$_[0]}; return grep {/^x_/i} keys %{$_[0]};
} }
sub custom { sub custom {
...@@ -220,29 +220,29 @@ sub _new { ...@@ -220,29 +220,29 @@ sub _new {
my ($class, $struct, $options) = @_; my ($class, $struct, $options) = @_;
my $self; my $self;
if ( $options->{lazy_validation} ) { if ($options->{lazy_validation}) {
# try to convert to a valid structure; if succeeds, then return it # try to convert to a valid structure; if succeeds, then return it
my $cmc = CPAN::Meta::Converter->new( $struct ); my $cmc = CPAN::Meta::Converter->new($struct);
$self = $cmc->convert( version => 2 ); # valid or dies $self = $cmc->convert(version => 2); # valid or dies
return bless $self, $class; return bless $self, $class;
} }
else { else {
# validate original struct # validate original struct
my $cmv = CPAN::Meta::Validator->new( $struct ); my $cmv = CPAN::Meta::Validator->new($struct);
unless ( $cmv->is_valid) { unless ($cmv->is_valid) {
die "Invalid metadata structure. Errors: " die "Invalid metadata structure. Errors: " . join(", ", $cmv->errors) . "\n";
. join(", ", $cmv->errors) . "\n";
} }
} }
# up-convert older spec versions # up-convert older spec versions
my $version = $struct->{'meta-spec'}{version} || '1.0'; my $version = $struct->{'meta-spec'}{version} || '1.0';
if ( $version == 2 ) { if ($version == 2) {
$self = $struct; $self = $struct;
} }
else { else {
my $cmc = CPAN::Meta::Converter->new( $struct ); my $cmc = CPAN::Meta::Converter->new($struct);
$self = $cmc->convert( version => 2 ); $self = $cmc->convert(version => 2);
} }
return bless $self, $class; return bless $self, $class;
...@@ -268,10 +268,10 @@ sub new { ...@@ -268,10 +268,10 @@ sub new {
sub create { sub create {
my ($class, $struct, $options) = @_; my ($class, $struct, $options) = @_;
my $version = __PACKAGE__->VERSION || 2; my $version = __PACKAGE__->VERSION || 2;
$struct->{generated_by} ||= __PACKAGE__ . " version $version" ; $struct->{generated_by} ||= __PACKAGE__ . " version $version";
$struct->{'meta-spec'}{version} ||= int($version); $struct->{'meta-spec'}{version} ||= int($version);
my $self = eval { $class->_new($struct, $options) }; my $self = eval { $class->_new($struct, $options) };
croak ($@) if $@; croak($@) if $@;
return $self; return $self;
} }
...@@ -293,12 +293,11 @@ sub load_file { ...@@ -293,12 +293,11 @@ sub load_file {
my ($class, $file, $options) = @_; my ($class, $file, $options) = @_;
$options->{lazy_validation} = 1 unless exists $options->{lazy_validation}; $options->{lazy_validation} = 1 unless exists $options->{lazy_validation};
croak "load_file() requires a valid, readable filename" croak "load_file() requires a valid, readable filename" unless -r $file;
unless -r $file;
my $self; my $self;
eval { eval {
my $struct = Parse::CPAN::Meta->load_file( $file ); my $struct = Parse::CPAN::Meta->load_file($file);
$self = $class->_new($struct, $options); $self = $class->_new($struct, $options);
}; };
croak($@) if $@; croak($@) if $@;
...@@ -320,7 +319,7 @@ sub load_yaml_string { ...@@ -320,7 +319,7 @@ sub load_yaml_string {
my $self; my $self;
eval { eval {
my ($struct) = Parse::CPAN::Meta->load_yaml_string( $yaml ); my ($struct) = Parse::CPAN::Meta->load_yaml_string($yaml);
$self = $class->_new($struct, $options); $self = $class->_new($struct, $options);
}; };
croak($@) if $@; croak($@) if $@;
...@@ -342,7 +341,7 @@ sub load_json_string { ...@@ -342,7 +341,7 @@ sub load_json_string {
my $self; my $self;
eval { eval {
my $struct = Parse::CPAN::Meta->load_json_string( $json ); my $struct = Parse::CPAN::Meta->load_json_string($json);
$self = $class->_new($struct, $options); $self = $class->_new($struct, $options);
}; };
croak($@) if $@; croak($@) if $@;
...@@ -365,7 +364,7 @@ sub load_string { ...@@ -365,7 +364,7 @@ sub load_string {
my $self; my $self;
eval { eval {
my $struct = Parse::CPAN::Meta->load_string( $string ); my $struct = Parse::CPAN::Meta->load_string($string);
$self = $class->_new($struct, $options); $self = $class->_new($struct, $options);
}; };
croak($@) if $@; croak($@) if $@;
...@@ -400,22 +399,18 @@ sub save { ...@@ -400,22 +399,18 @@ sub save {
my $version = $options->{version} || '2'; my $version = $options->{version} || '2';
my $layer = $] ge '5.008001' ? ':utf8' : ''; my $layer = $] ge '5.008001' ? ':utf8' : '';
if ( $version ge '2' ) { if ($version ge '2') {
carp "'$file' should end in '.json'" carp "'$file' should end in '.json'" unless $file =~ m{\.json$};
unless $file =~ m{\.json$};
} }
else { else {
carp "'$file' should end in '.yml'" carp "'$file' should end in '.yml'" unless $file =~ m{\.yml$};
unless $file =~ m{\.yml$};
} }
my $data = $self->as_string( $options ); my $data = $self->as_string($options);
open my $fh, ">$layer", $file open my $fh, ">$layer", $file or die "Error opening '$file' for writing: $!\n";
or die "Error opening '$file' for writing: $!\n";
print {$fh} $data; print {$fh} $data;
close $fh close $fh or die "Error closing '$file': $!\n";
or die "Error closing '$file': $!\n";
return 1; return 1;
} }
...@@ -455,7 +450,7 @@ sub effective_prereqs { ...@@ -455,7 +450,7 @@ sub effective_prereqs {
return $prereq unless @$features; return $prereq unless @$features;
my @other = map {; $self->feature($_)->prereqs } @$features; my @other = map { ; $self->feature($_)->prereqs } @$features;
return $prereq->with_merged_prereqs(\@other); return $prereq->with_merged_prereqs(\@other);
} }
...@@ -476,11 +471,11 @@ sub effective_prereqs { ...@@ -476,11 +471,11 @@ sub effective_prereqs {
sub should_index_file { sub should_index_file {
my ($self, $filename) = @_; my ($self, $filename) = @_;
for my $no_index_file (@{ $self->no_index->{file} || [] }) { for my $no_index_file (@{$self->no_index->{file} || []}) {
return if $filename eq $no_index_file; return if $filename eq $no_index_file;
} }
for my $no_index_dir (@{ $self->no_index->{directory} }) { for my $no_index_dir (@{$self->no_index->{directory}}) {
$no_index_dir =~ s{$}{/} unless $no_index_dir =~ m{/\z}; $no_index_dir =~ s{$}{/} unless $no_index_dir =~ m{/\z};
return if index($filename, $no_index_dir) == 0; return if index($filename, $no_index_dir) == 0;
} }
...@@ -502,11 +497,11 @@ sub should_index_file { ...@@ -502,11 +497,11 @@ sub should_index_file {
sub should_index_package { sub should_index_package {
my ($self, $package) = @_; my ($self, $package) = @_;
for my $no_index_pkg (@{ $self->no_index->{package} || [] }) { for my $no_index_pkg (@{$self->no_index->{package} || []}) {
return if $package eq $no_index_pkg; return if $package eq $no_index_pkg;
} }
for my $no_index_ns (@{ $self->no_index->{namespace} }) { for my $no_index_ns (@{$self->no_index->{namespace}}) {
return if index($package, "${no_index_ns}::") == 0; return if index($package, "${no_index_ns}::") == 0;
} }
...@@ -526,8 +521,8 @@ sub features { ...@@ -526,8 +521,8 @@ sub features {
my ($self) = @_; my ($self) = @_;
my $opt_f = $self->optional_features; my $opt_f = $self->optional_features;
my @features = map {; CPAN::Meta::Feature->new($_ => $opt_f->{ $_ }) } my @features
keys %$opt_f; = map { ; CPAN::Meta::Feature->new($_ => $opt_f->{$_}) } keys %$opt_f;
return @features; return @features;
} }
...@@ -546,7 +541,7 @@ sub feature { ...@@ -546,7 +541,7 @@ sub feature {
my ($self, $ident) = @_; my ($self, $ident) = @_;
croak "no feature named $ident" croak "no feature named $ident"
unless my $f = $self->optional_features->{ $ident }; unless my $f = $self->optional_features->{$ident};
return CPAN::Meta::Feature->new($ident, $f); return CPAN::Meta::Feature->new($ident, $f);
} }
...@@ -567,9 +562,9 @@ sub feature { ...@@ -567,9 +562,9 @@ sub feature {
sub as_struct { sub as_struct {
my ($self, $options) = @_; my ($self, $options) = @_;
my $struct = _dclone($self); my $struct = _dclone($self);
if ( $options->{version} ) { if ($options->{version}) {
my $cmc = CPAN::Meta::Converter->new( $struct ); my $cmc = CPAN::Meta::Converter->new($struct);
$struct = $cmc->convert( version => $options->{version} ); $struct = $cmc->convert(version => $options->{version});
} }
return $struct; return $struct;
} }
...@@ -603,28 +598,28 @@ sub as_string { ...@@ -603,28 +598,28 @@ sub as_string {
my $version = $options->{version} || '2'; my $version = $options->{version} || '2';
my $struct; my $struct;
if ( $self->meta_spec_version ne $version ) { if ($self->meta_spec_version ne $version) {
my $cmc = CPAN::Meta::Converter->new( $self->as_struct ); my $cmc = CPAN::Meta::Converter->new($self->as_struct);
$struct = $cmc->convert( version => $version ); $struct = $cmc->convert(version => $version);
} }
else { else {
$struct = $self->as_struct; $struct = $self->as_struct;
} }
my ($data, $backend); my ($data, $backend);
if ( $version ge '2' ) { if ($version ge '2') {
$backend = Parse::CPAN::Meta->json_backend(); $backend = Parse::CPAN::Meta->json_backend();
local $struct->{x_serialization_backend} = sprintf '%s version %s', local $struct->{x_serialization_backend} = sprintf '%s version %s', $backend,
$backend, $backend->VERSION; $backend->VERSION;
$data = $backend->new->pretty->canonical->encode($struct); $data = $backend->new->pretty->canonical->encode($struct);
} }
else { else {
$backend = Parse::CPAN::Meta->yaml_backend(); $backend = Parse::CPAN::Meta->yaml_backend();
local $struct->{x_serialization_backend} = sprintf '%s version %s', local $struct->{x_serialization_backend} = sprintf '%s version %s', $backend,
$backend, $backend->VERSION; $backend->VERSION;
$data = eval { no strict 'refs'; &{"$backend\::Dump"}($struct) }; $data = eval { no strict 'refs'; &{"$backend\::Dump"}($struct) };
if ( $@ ) { if ($@) {
croak $backend->can('errstr') ? $backend->errstr : $@ croak $backend->can('errstr') ? $backend->errstr : $@;
} }
} }
...@@ -633,7 +628,7 @@ sub as_string { ...@@ -633,7 +628,7 @@ sub as_string {
# Used by JSON::PP, etc. for "convert_blessed" # Used by JSON::PP, etc. for "convert_blessed"
sub TO_JSON { sub TO_JSON {
return { %{ $_[0] } }; return {%{$_[0]}};
} }
1; 1;
......
use 5.006; use 5.006;
use strict; use strict;
use warnings; use warnings;
package CPAN::Meta::Feature; package CPAN::Meta::Feature;
our $VERSION = '2.150005'; our $VERSION = '2.150005';
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
use 5.006; use 5.006;
use strict; use strict;
use warnings; use warnings;
package CPAN::Meta::History; package CPAN::Meta::History;
our $VERSION = '2.150005'; our $VERSION = '2.150005';
......
...@@ -11,15 +11,16 @@ use CPAN::Meta::Converter 2.141170; ...@@ -11,15 +11,16 @@ use CPAN::Meta::Converter 2.141170;
sub _is_identical { sub _is_identical {
my ($left, $right) = @_; my ($left, $right) = @_;
return return (not defined $left and not defined $right)
(not defined $left and not defined $right)
# if either of these are references, we compare the serialized value # if either of these are references, we compare the serialized value
|| (defined $left and defined $right and $left eq $right); || (defined $left and defined $right and $left eq $right);
} }
sub _identical { sub _identical {
my ($left, $right, $path) = @_; my ($left, $right, $path) = @_;
croak sprintf "Can't merge attribute %s: '%s' does not equal '%s'", join('.', @{$path}), $left, $right croak sprintf "Can't merge attribute %s: '%s' does not equal '%s'",
join('.', @{$path}), $left, $right
unless _is_identical($left, $right); unless _is_identical($left, $right);
return $left; return $left;
} }
...@@ -31,10 +32,10 @@ sub _merge { ...@@ -31,10 +32,10 @@ sub _merge {
$current->{$key} = $next->{$key}; $current->{$key} = $next->{$key};
} }
elsif (my $merger = $mergers->{$key}) { elsif (my $merger = $mergers->{$key}) {
$current->{$key} = $merger->($current->{$key}, $next->{$key}, [ @{$path}, $key ]); $current->{$key} = $merger->($current->{$key}, $next->{$key}, [@{$path}, $key]);
} }
elsif ($merger = $mergers->{':default'}) { elsif ($merger = $mergers->{':default'}) {
$current->{$key} = $merger->($current->{$key}, $next->{$key}, [ @{$path}, $key ]); $current->{$key} = $merger->($current->{$key}, $next->{$key}, [@{$path}, $key]);
} }
else { else {
croak sprintf "Can't merge unknown attribute '%s'", join '.', @{$path}, $key; croak sprintf "Can't merge unknown attribute '%s'", join '.', @{$path}, $key;
...@@ -50,7 +51,7 @@ sub _uniq { ...@@ -50,7 +51,7 @@ sub _uniq {
sub _set_addition { sub _set_addition {
my ($left, $right) = @_; my ($left, $right) = @_;
return [ +_uniq(@{$left}, @{$right}) ]; return [+_uniq(@{$left}, @{$right})];
} }
sub _uniq_map { sub _uniq_map {
...@@ -59,12 +60,13 @@ sub _uniq_map { ...@@ -59,12 +60,13 @@ sub _uniq_map {
if (not exists $left->{$key}) { if (not exists $left->{$key}) {
$left->{$key} = $right->{$key}; $left->{$key} = $right->{$key};
} }
# identical strings or references are merged identically # identical strings or references are merged identically
elsif (_is_identical($left->{$key}, $right->{$key})) { elsif (_is_identical($left->{$key}, $right->{$key})) {
1; # do nothing - keep left 1; # do nothing - keep left
} }
elsif (ref $left->{$key} eq 'HASH' and ref $right->{$key} eq 'HASH') { elsif (ref $left->{$key} eq 'HASH' and ref $right->{$key} eq 'HASH') {
$left->{$key} = _uniq_map($left->{$key}, $right->{$key}, [ @{$path}, $key ]); $left->{$key} = _uniq_map($left->{$key}, $right->{$key}, [@{$path}, $key]);
} }
else { else {
croak 'Duplication of element ' . join '.', @{$path}, $key; croak 'Duplication of element ' . join '.', @{$path}, $key;
...@@ -98,20 +100,24 @@ sub _optional_features { ...@@ -98,20 +100,24 @@ sub _optional_features {
$left->{$key} = $right->{$key}; $left->{$key} = $right->{$key};
} }
else { else {
for my $subkey (keys %{ $right->{$key} }) { for my $subkey (keys %{$right->{$key}}) {
next if $subkey eq 'prereqs'; next if $subkey eq 'prereqs';
if (not exists $left->{$key}{$subkey}) { if (not exists $left->{$key}{$subkey}) {
$left->{$key}{$subkey} = $right->{$key}{$subkey}; $left->{$key}{$subkey} = $right->{$key}{$subkey};
} }
else { else {
Carp::croak "Cannot merge two optional_features named '$key' with different '$subkey' values" Carp::croak
if do { no warnings 'uninitialized'; $left->{$key}{$subkey} ne $right->{$key}{$subkey} }; "Cannot merge two optional_features named '$key' with different '$subkey' values"
if do {
no warnings 'uninitialized';
$left->{$key}{$subkey} ne $right->{$key}{$subkey};
};
} }
} }
require CPAN::Meta::Prereqs; require CPAN::Meta::Prereqs;
$left->{$key}{prereqs} = $left->{$key}{prereqs}
CPAN::Meta::Prereqs->new($left->{$key}{prereqs}) = CPAN::Meta::Prereqs->new($left->{$key}{prereqs})
->with_merged_prereqs(CPAN::Meta::Prereqs->new($right->{$key}{prereqs})) ->with_merged_prereqs(CPAN::Meta::Prereqs->new($right->{$key}{prereqs}))
->as_string_hash; ->as_string_hash;
} }
...@@ -132,20 +138,18 @@ my %default = ( ...@@ -132,20 +138,18 @@ my %default = (
return join ', ', _uniq(split(/, /, $left), split(/, /, $right)); return join ', ', _uniq(split(/, /, $left), split(/, /, $right));
}, },
license => \&_set_addition, license => \&_set_addition,
'meta-spec' => { 'meta-spec' => {version => \&_identical, url => \&_identical},
version => \&_identical,
url => \&_identical
},
name => \&_identical, name => \&_identical,
release_status => \&_identical, release_status => \&_identical,
version => \&_identical, version => \&_identical,
description => \&_identical, description => \&_identical,
keywords => \&_set_addition, keywords => \&_set_addition,
no_index => { map { ($_ => \&_set_addition) } qw/file directory package namespace/ }, no_index =>
{map { ($_ => \&_set_addition) } qw/file directory package namespace/},
optional_features => \&_optional_features, optional_features => \&_optional_features,
prereqs => sub { prereqs => sub {
require CPAN::Meta::Prereqs; require CPAN::Meta::Prereqs;
my ($left, $right) = map { CPAN::Meta::Prereqs->new($_) } @_[0,1]; my ($left, $right) = map { CPAN::Meta::Prereqs->new($_) } @_[0, 1];
return $left->with_merged_prereqs($right)->as_string_hash; return $left->with_merged_prereqs($right)->as_string_hash;
}, },
provides => \&_uniq_map, provides => \&_uniq_map,
...@@ -163,10 +167,10 @@ sub new { ...@@ -163,10 +167,10 @@ sub new {
my ($class, %arguments) = @_; my ($class, %arguments) = @_;
croak 'default version required' if not exists $arguments{default_version}; croak 'default version required' if not exists $arguments{default_version};
my %mapping = %default; my %mapping = %default;
my %extra = %{ $arguments{extra_mappings} || {} }; my %extra = %{$arguments{extra_mappings} || {}};
for my $key (keys %extra) { for my $key (keys %extra) {
if (ref($mapping{$key}) eq 'HASH') { if (ref($mapping{$key}) eq 'HASH') {
$mapping{$key} = { %{ $mapping{$key} }, %{ $extra{$key} } }; $mapping{$key} = {%{$mapping{$key}}, %{$extra{$key}}};
} }
else { else {
$mapping{$key} = $extra{$key}; $mapping{$key} = $extra{$key};
...@@ -194,10 +198,10 @@ sub _coerce_mapping { ...@@ -194,10 +198,10 @@ sub _coerce_mapping {
$ret{$key} = $value; $ret{$key} = $value;
} }
elsif (ref($value) eq 'HASH') { elsif (ref($value) eq 'HASH') {
my $mapping = _coerce_mapping($value, [ @{$map_path}, $key ]); my $mapping = _coerce_mapping($value, [@{$map_path}, $key]);
$ret{$key} = sub { $ret{$key} = sub {
my ($left, $right, $path) = @_; my ($left, $right, $path) = @_;
return _merge($left, $right, $mapping, [ @{$path} ]); return _merge($left, $right, $mapping, [@{$path}]);
}; };
} }
elsif ($coderef_for{$value}) { elsif ($coderef_for{$value}) {
...@@ -214,13 +218,12 @@ sub merge { ...@@ -214,13 +218,12 @@ sub merge {
my ($self, @items) = @_; my ($self, @items) = @_;
my $current = {}; my $current = {};
for my $next (@items) { for my $next (@items) {
if ( blessed($next) && $next->isa('CPAN::Meta') ) { if (blessed($next) && $next->isa('CPAN::Meta')) {
$next = $next->as_struct; $next = $next->as_struct;
} }
elsif ( ref($next) eq 'HASH' ) { elsif (ref($next) eq 'HASH') {
my $cmc = CPAN::Meta::Converter->new( my $cmc = CPAN::Meta::Converter->new($next,
$next, default_version => $self->{default_version} default_version => $self->{default_version});
);
$next = $cmc->upgrade_fragment; $next = $cmc->upgrade_fragment;
} }
else { else {
......
use 5.006; use 5.006;
use strict; use strict;
use warnings; use warnings;
package CPAN::Meta::Prereqs; package CPAN::Meta::Prereqs;
our $VERSION = '2.150005'; our $VERSION = '2.150005';
...@@ -45,34 +46,33 @@ use CPAN::Meta::Requirements 2.121; ...@@ -45,34 +46,33 @@ use CPAN::Meta::Requirements 2.121;
#pod #pod
#pod =cut #pod =cut
sub __legal_phases { qw(configure build test runtime develop) } sub __legal_phases {qw(configure build test runtime develop)}
sub __legal_types { qw(requires recommends suggests conflicts) } sub __legal_types {qw(requires recommends suggests conflicts)}
# expect a prereq spec from META.json -- rjbs, 2010-04-11 # expect a prereq spec from META.json -- rjbs, 2010-04-11
sub new { sub new {
my ($class, $prereq_spec) = @_; my ($class, $prereq_spec) = @_;
$prereq_spec ||= {}; $prereq_spec ||= {};
my %is_legal_phase = map {; $_ => 1 } $class->__legal_phases; my %is_legal_phase = map { ; $_ => 1 } $class->__legal_phases;
my %is_legal_type = map {; $_ => 1 } $class->__legal_types; my %is_legal_type = map { ; $_ => 1 } $class->__legal_types;
my %guts; my %guts;
PHASE: for my $phase (keys %$prereq_spec) { PHASE: for my $phase (keys %$prereq_spec) {
next PHASE unless $phase =~ /\Ax_/i or $is_legal_phase{$phase}; next PHASE unless $phase =~ /\Ax_/i or $is_legal_phase{$phase};
my $phase_spec = $prereq_spec->{ $phase }; my $phase_spec = $prereq_spec->{$phase};
next PHASE unless keys %$phase_spec; next PHASE unless keys %$phase_spec;
TYPE: for my $type (keys %$phase_spec) { TYPE: for my $type (keys %$phase_spec) {
next TYPE unless $type =~ /\Ax_/i or $is_legal_type{$type}; next TYPE unless $type =~ /\Ax_/i or $is_legal_type{$type};
my $spec = $phase_spec->{ $type }; my $spec = $phase_spec->{$type};
next TYPE unless keys %$spec; next TYPE unless keys %$spec;
$guts{prereqs}{$phase}{$type} = CPAN::Meta::Requirements->from_string_hash( $guts{prereqs}{$phase}{$type}
$spec = CPAN::Meta::Requirements->from_string_hash($spec);
);
} }
} }
...@@ -152,7 +152,7 @@ sub with_merged_prereqs { ...@@ -152,7 +152,7 @@ sub with_merged_prereqs {
next unless $req->required_modules; next unless $req->required_modules;
$new_arg{ $phase }{ $type } = $req->as_string_hash; $new_arg{$phase}{$type} = $req->as_string_hash;
} }
} }
...@@ -184,15 +184,15 @@ sub merged_requirements { ...@@ -184,15 +184,15 @@ sub merged_requirements {
my $req = CPAN::Meta::Requirements->new; my $req = CPAN::Meta::Requirements->new;
for my $phase ( @$phases ) { for my $phase (@$phases) {
unless ($phase =~ /\Ax_/i or grep { $phase eq $_ } $self->__legal_phases) { unless ($phase =~ /\Ax_/i or grep { $phase eq $_ } $self->__legal_phases) {
confess "requested requirements for unknown phase: $phase"; confess "requested requirements for unknown phase: $phase";
} }
for my $type ( @$types ) { for my $type (@$types) {
unless ($type =~ /\Ax_/i or grep { $type eq $_ } $self->__legal_types) { unless ($type =~ /\Ax_/i or grep { $type eq $_ } $self->__legal_types) {
confess "requested requirements for unknown type: $type"; confess "requested requirements for unknown type: $type";
} }
$req->add_requirements( $self->requirements_for($phase, $type) ); $req->add_requirements($self->requirements_for($phase, $type));
} }
} }
...@@ -220,7 +220,7 @@ sub as_string_hash { ...@@ -220,7 +220,7 @@ sub as_string_hash {
my $req = $self->requirements_for($phase, $type); my $req = $self->requirements_for($phase, $type);
next unless $req->required_modules; next unless $req->required_modules;
$hash{ $phase }{ $type } = $req->as_string_hash; $hash{$phase}{$type} = $req->as_string_hash;
} }
} }
...@@ -249,8 +249,8 @@ sub finalize { ...@@ -249,8 +249,8 @@ sub finalize {
$self->{finalized} = 1; $self->{finalized} = 1;
for my $phase (keys %{ $self->{prereqs} }) { for my $phase (keys %{$self->{prereqs}}) {
$_->finalize for values %{ $self->{prereqs}{$phase} }; $_->finalize for values %{$self->{prereqs}{$phase}};
} }
} }
...@@ -268,7 +268,7 @@ sub finalize { ...@@ -268,7 +268,7 @@ sub finalize {
sub clone { sub clone {
my ($self) = @_; my ($self) = @_;
my $clone = (ref $self)->new( $self->as_string_hash ); my $clone = (ref $self)->new($self->as_string_hash);
} }
1; 1;
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
use 5.006; use 5.006;
use strict; use strict;
use warnings; use warnings;
package CPAN::Meta::Spec; package CPAN::Meta::Spec;
our $VERSION = '2.150005'; our $VERSION = '2.150005';
......
=head1 NAME =head1 NAME
JSON::PP::Boolean - dummy module providing JSON::PP::Boolean JSON::PP::Boolean - dummy module providing JSON::PP::Boolean
......
use 5.008001; use 5.008001;
use strict; use strict;
package Parse::CPAN::Meta; package Parse::CPAN::Meta;
# ABSTRACT: Parse META.yml and META.json CPAN metadata files # ABSTRACT: Parse META.yml and META.json CPAN metadata files
our $VERSION = '1.4417'; our $VERSION = '1.4417';
...@@ -29,10 +31,10 @@ sub load_file { ...@@ -29,10 +31,10 @@ sub load_file {
sub load_string { sub load_string {
my ($class, $string) = @_; my ($class, $string) = @_;
if ( $string =~ /^---/ ) { # looks like YAML if ($string =~ /^---/) { # looks like YAML
return $class->load_yaml_string($string); return $class->load_yaml_string($string);
} }
elsif ( $string =~ /^\s*\{/ ) { # looks like JSON elsif ($string =~ /^\s*\{/) { # looks like JSON
return $class->load_json_string($string); return $class->load_json_string($string);
} }
else { # maybe doc-marker-free YAML else { # maybe doc-marker-free YAML
...@@ -56,15 +58,14 @@ sub load_json_string { ...@@ -56,15 +58,14 @@ sub load_json_string {
} }
sub yaml_backend { sub yaml_backend {
if (! defined $ENV{PERL_YAML_BACKEND} ) { if (!defined $ENV{PERL_YAML_BACKEND}) {
_can_load( 'CPAN::Meta::YAML', 0.011 ) _can_load('CPAN::Meta::YAML', 0.011)
or croak "CPAN::Meta::YAML 0.011 is not available\n"; or croak "CPAN::Meta::YAML 0.011 is not available\n";
return "CPAN::Meta::YAML"; return "CPAN::Meta::YAML";
} }
else { else {
my $backend = $ENV{PERL_YAML_BACKEND}; my $backend = $ENV{PERL_YAML_BACKEND};
_can_load( $backend ) _can_load($backend) or croak "Could not load PERL_YAML_BACKEND '$backend'\n";
or croak "Could not load PERL_YAML_BACKEND '$backend'\n";
$backend->can("Load") $backend->can("Load")
or croak "PERL_YAML_BACKEND '$backend' does not implement Load()\n"; or croak "PERL_YAML_BACKEND '$backend' does not implement Load()\n";
return $backend; return $backend;
...@@ -72,15 +73,14 @@ sub yaml_backend { ...@@ -72,15 +73,14 @@ sub yaml_backend {
} }
sub json_backend { sub json_backend {
if (! $ENV{PERL_JSON_BACKEND} or $ENV{PERL_JSON_BACKEND} eq 'JSON::PP') { if (!$ENV{PERL_JSON_BACKEND} or $ENV{PERL_JSON_BACKEND} eq 'JSON::PP') {
_can_load( 'JSON::PP' => 2.27103 ) _can_load('JSON::PP' => 2.27103) or croak "JSON::PP 2.27103 is not available\n";
or croak "JSON::PP 2.27103 is not available\n";
return 'JSON::PP'; return 'JSON::PP';
} }
else { else {
_can_load( 'JSON' => 2.5 ) _can_load('JSON' => 2.5)
or croak "JSON 2.5 is required for " . or croak "JSON 2.5 is required for "
"\$ENV{PERL_JSON_BACKEND} = '$ENV{PERL_JSON_BACKEND}'\n"; . "\$ENV{PERL_JSON_BACKEND} = '$ENV{PERL_JSON_BACKEND}'\n";
return "JSON"; return "JSON";
} }
} }
...@@ -100,11 +100,9 @@ sub _can_load { ...@@ -100,11 +100,9 @@ sub _can_load {
$file .= ".pm"; $file .= ".pm";
return 1 if $INC{$file}; return 1 if $INC{$file};
return 0 if exists $INC{$file}; # prior load failed return 0 if exists $INC{$file}; # prior load failed
eval { require $file; 1 } eval { require $file; 1 } or return 0;
or return 0; if (defined $version) {
if ( defined $version ) { eval { $module->VERSION($version); 1 } or return 0;
eval { $module->VERSION($version); 1 }
or return 0;
} }
return 1; return 1;
} }
......
...@@ -46,6 +46,7 @@ use File::Basename; ...@@ -46,6 +46,7 @@ use File::Basename;
use File::Spec::Functions; use File::Spec::Functions;
use DateTime::TimeZone; use DateTime::TimeZone;
use Date::Parse; use Date::Parse;
# Bug 1270550 - Tie::Hash::NamedCapture must be loaded before Safe. # Bug 1270550 - Tie::Hash::NamedCapture must be loaded before Safe.
use Tie::Hash::NamedCapture; use Tie::Hash::NamedCapture;
use Safe; use Safe;
...@@ -88,12 +89,14 @@ sub init_page { ...@@ -88,12 +89,14 @@ sub init_page {
if (${^TAINT}) { if (${^TAINT}) {
my $path = ''; my $path = '';
if (ON_WINDOWS) { if (ON_WINDOWS) {
# On Windows, these paths are tainted, preventing # On Windows, these paths are tainted, preventing
# File::Spec::Win32->tmpdir from using them. But we need # File::Spec::Win32->tmpdir from using them. But we need
# a place to temporary store attachments which are uploaded. # a place to temporary store attachments which are uploaded.
foreach my $temp (qw(TMPDIR TMP TEMP WINDIR)) { foreach my $temp (qw(TMPDIR TMP TEMP WINDIR)) {
trick_taint($ENV{$temp}) if $ENV{$temp}; trick_taint($ENV{$temp}) if $ENV{$temp};
} }
# Some DLLs used by Strawberry Perl are also in c\bin, # Some DLLs used by Strawberry Perl are also in c\bin,
# see https://rt.cpan.org/Public/Bug/Display.html?id=99104 # see https://rt.cpan.org/Public/Bug/Display.html?id=99104
if (!ON_ACTIVESTATE) { if (!ON_ACTIVESTATE) {
...@@ -103,8 +106,10 @@ sub init_page { ...@@ -103,8 +106,10 @@ sub init_page {
trick_taint($path); trick_taint($path);
} }
} }
# Some environment variables are not taint safe # Some environment variables are not taint safe
delete @::ENV{'PATH', 'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; delete @::ENV{'PATH', 'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
# Some modules throw undefined errors (notably File::Spec::Win32) if # Some modules throw undefined errors (notably File::Spec::Win32) if
# PATH is undefined. # PATH is undefined.
$ENV{'PATH'} = $path; $ENV{'PATH'} = $path;
...@@ -144,11 +149,10 @@ sub init_page { ...@@ -144,11 +149,10 @@ sub init_page {
# Allow non-cgi scripts to exit silently (without displaying any # Allow non-cgi scripts to exit silently (without displaying any
# message), if desired. At this point, no DBI call has been made # message), if desired. At this point, no DBI call has been made
# yet, and no error will be returned if the DB is inaccessible. # yet, and no error will be returned if the DB is inaccessible.
if (!i_am_cgi() if (!i_am_cgi() && grep { $_ eq $script } SHUTDOWNHTML_EXIT_SILENTLY) {
&& grep { $_ eq $script } SHUTDOWNHTML_EXIT_SILENTLY)
{
exit; exit;
} }
# Plack requires to exit differently. # Plack requires to exit differently.
return -1 if $ENV{BZ_PLACK}; return -1 if $ENV{BZ_PLACK};
_shutdown(); _shutdown();
...@@ -156,10 +160,12 @@ sub init_page { ...@@ -156,10 +160,12 @@ sub init_page {
} }
sub _shutdown { sub _shutdown {
# For security reasons, log out users when Bugzilla is down. # For security reasons, log out users when Bugzilla is down.
# Bugzilla->login() is required to catch the logincookie, if any. # Bugzilla->login() is required to catch the logincookie, if any.
my $user = eval { Bugzilla->login(LOGIN_OPTIONAL); }; my $user = eval { Bugzilla->login(LOGIN_OPTIONAL); };
if ($@) { if ($@) {
# The DB is not accessible. Use the default user object. # The DB is not accessible. Use the default user object.
$user = Bugzilla->user; $user = Bugzilla->user;
$user->{settings} = {}; $user->{settings} = {};
...@@ -173,10 +179,10 @@ sub _shutdown { ...@@ -173,10 +179,10 @@ sub _shutdown {
my $extension = 'txt'; my $extension = 'txt';
if (i_am_cgi()) { if (i_am_cgi()) {
# Set the HTTP status to 503 when Bugzilla is down to avoid pages # Set the HTTP status to 503 when Bugzilla is down to avoid pages
# being indexed by search engines. # being indexed by search engines.
print $cgi->header(-status => 503, print $cgi->header(-status => 503, -retry_after => SHUTDOWNHTML_RETRY_AFTER);
-retry_after => SHUTDOWNHTML_RETRY_AFTER);
if (!$cgi->param('ctype') || $cgi->param('ctype') eq 'html') { if (!$cgi->param('ctype') || $cgi->param('ctype') eq 'html') {
$extension = 'html'; $extension = 'html';
...@@ -184,7 +190,7 @@ sub _shutdown { ...@@ -184,7 +190,7 @@ sub _shutdown {
} }
my $template = Bugzilla->template; my $template = Bugzilla->template;
my $vars = { message => 'shutdown', userid => $userid }; my $vars = {message => 'shutdown', userid => $userid};
$template->process("global/message.$extension.tmpl", $vars) $template->process("global/message.$extension.tmpl", $vars)
or ThrowTemplateError($template->error); or ThrowTemplateError($template->error);
...@@ -204,14 +210,17 @@ sub template_inner { ...@@ -204,14 +210,17 @@ sub template_inner {
my $cache = $class->request_cache; my $cache = $class->request_cache;
my $current_lang = $cache->{template_current_lang}->[0]; my $current_lang = $cache->{template_current_lang}->[0];
$lang ||= $current_lang || ''; $lang ||= $current_lang || '';
return $cache->{"template_inner_$lang"} ||= Bugzilla::Template->create(language => $lang); return $cache->{"template_inner_$lang"}
||= Bugzilla::Template->create(language => $lang);
} }
our $extension_packages; our $extension_packages;
sub extensions { sub extensions {
my ($class) = @_; my ($class) = @_;
my $cache = $class->request_cache; my $cache = $class->request_cache;
if (!$cache->{extensions}) { if (!$cache->{extensions}) {
# Under mod_perl, mod_perl.pl populates $extension_packages for us. # Under mod_perl, mod_perl.pl populates $extension_packages for us.
if (!$extension_packages) { if (!$extension_packages) {
$extension_packages = Bugzilla::Extension->load_all(); $extension_packages = Bugzilla::Extension->load_all();
...@@ -249,8 +258,9 @@ sub feature { ...@@ -249,8 +258,9 @@ sub feature {
my $cache = $class->process_cache; my $cache = $class->process_cache;
my $feature = $cache->{cpan_meta}->feature($feature_name); my $feature = $cache->{cpan_meta}->feature($feature_name);
# Bugzilla expects this will also load all the modules.. so we have to do that.
# Later we should put a deprecation warning here, and favor calling has_feature(). # Bugzilla expects this will also load all the modules.. so we have to do that.
# Later we should put a deprecation warning here, and favor calling has_feature().
return 1 if $cache->{feature_loaded}{$feature_name}; return 1 if $cache->{feature_loaded}{$feature_name};
my @modules = $feature->prereqs->merged_requirements->required_modules; my @modules = $feature->prereqs->merged_requirements->required_modules;
...@@ -270,7 +280,7 @@ sub has_feature { ...@@ -270,7 +280,7 @@ sub has_feature {
my $meta = $cache->{cpan_meta} //= load_cpan_meta(); my $meta = $cache->{cpan_meta} //= load_cpan_meta();
my $feature = eval { $meta->feature($feature_name) } my $feature = eval { $meta->feature($feature_name) }
or ThrowCodeError('invalid_feature', { feature => $feature_name }); or ThrowCodeError('invalid_feature', {feature => $feature_name});
return $cache->{feature}{$feature_name} = check_cpan_feature($feature)->{ok}; return $cache->{feature}{$feature_name} = check_cpan_feature($feature)->{ok};
} }
...@@ -282,6 +292,7 @@ sub cgi { ...@@ -282,6 +292,7 @@ sub cgi {
sub input_params { sub input_params {
my ($class, $params) = @_; my ($class, $params) = @_;
my $cache = $class->request_cache; my $cache = $class->request_cache;
# This is how the WebService and other places set input_params. # This is how the WebService and other places set input_params.
if (defined $params) { if (defined $params) {
$cache->{input_params} = $params; $cache->{input_params} = $params;
...@@ -320,6 +331,7 @@ sub sudo_request { ...@@ -320,6 +331,7 @@ sub sudo_request {
my ($class, $new_user, $new_sudoer) = @_; my ($class, $new_user, $new_sudoer) = @_;
$class->request_cache->{user} = $new_user; $class->request_cache->{user} = $new_user;
$class->request_cache->{sudoer} = $new_sudoer; $class->request_cache->{sudoer} = $new_sudoer;
# NOTE: If you want to log the start of an sudo session, do it here. # NOTE: If you want to log the start of an sudo session, do it here.
} }
...@@ -368,12 +380,13 @@ sub login { ...@@ -368,12 +380,13 @@ sub login {
} }
my $sudo_target = new Bugzilla::User($sudo_target_id); my $sudo_target = new Bugzilla::User($sudo_target_id);
if ($authenticated_user->in_group('bz_sudoers') if ( $authenticated_user->in_group('bz_sudoers')
&& defined $sudo_target && defined $sudo_target
&& !$sudo_target->in_group('bz_sudo_protect')) && !$sudo_target->in_group('bz_sudo_protect'))
{ {
$class->set_user($sudo_target); $class->set_user($sudo_target);
$class->request_cache->{sudoer} = $authenticated_user; $class->request_cache->{sudoer} = $authenticated_user;
# And make sure that both users have the same Auth object, # And make sure that both users have the same Auth object,
# since we never call Auth::login for the sudo target. # since we never call Auth::login for the sudo target.
$sudo_target->set_authorizer($authenticated_user->authorizer); $sudo_target->set_authorizer($authenticated_user->authorizer);
...@@ -383,8 +396,8 @@ sub login { ...@@ -383,8 +396,8 @@ sub login {
else { else {
delete_token($token); delete_token($token);
$class->cgi->remove_cookie('sudo'); $class->cgi->remove_cookie('sudo');
ThrowUserError('sudo_illegal_action', { sudoer => $authenticated_user, ThrowUserError('sudo_illegal_action',
target_user => $sudo_target }); {sudoer => $authenticated_user, target_user => $sudo_target});
} }
} }
else { else {
...@@ -393,7 +406,8 @@ sub login { ...@@ -393,7 +406,8 @@ sub login {
if ($class->sudoer) { if ($class->sudoer) {
$class->sudoer->update_last_seen_date(); $class->sudoer->update_last_seen_date();
} else { }
else {
$class->user->update_last_seen_date(); $class->user->update_last_seen_date();
} }
...@@ -413,6 +427,7 @@ sub logout { ...@@ -413,6 +427,7 @@ sub logout {
sub logout_user { sub logout_user {
my ($class, $user) = @_; my ($class, $user) = @_;
# When we're logging out another user we leave cookies alone, and # When we're logging out another user we leave cookies alone, and
# therefore avoid calling Bugzilla->logout() directly. # therefore avoid calling Bugzilla->logout() directly.
Bugzilla::Auth::Persist::Cookie->logout({user => $user}); Bugzilla::Auth::Persist::Cookie->logout({user => $user});
...@@ -430,6 +445,7 @@ sub logout_request { ...@@ -430,6 +445,7 @@ sub logout_request {
my $class = shift; my $class = shift;
delete $class->request_cache->{user}; delete $class->request_cache->{user};
delete $class->request_cache->{sudoer}; delete $class->request_cache->{sudoer};
# We can't delete from $cgi->cookie, so logincookie data will remain # We can't delete from $cgi->cookie, so logincookie data will remain
# there. Don't rely on it: use Bugzilla->user->login instead! # there. Don't rely on it: use Bugzilla->user->login instead!
} }
...@@ -447,6 +463,7 @@ sub job_queue { ...@@ -447,6 +463,7 @@ sub job_queue {
} }
sub dbh { sub dbh {
# If we're not connected, then we must want the main db # If we're not connected, then we must want the main db
return $_[0]->request_cache->{dbh} ||= $_[0]->dbh_main; return $_[0]->request_cache->{dbh} ||= $_[0]->dbh_main;
} }
...@@ -511,8 +528,7 @@ sub usage_mode { ...@@ -511,8 +528,7 @@ sub usage_mode {
$class->error_mode(ERROR_MODE_REST); $class->error_mode(ERROR_MODE_REST);
} }
else { else {
ThrowCodeError('usage_mode_invalid', ThrowCodeError('usage_mode_invalid', {'invalid_usage_mode', $newval});
{'invalid_usage_mode', $newval});
} }
$class->request_cache->{usage_mode} = $newval; $class->request_cache->{usage_mode} = $newval;
} }
...@@ -521,7 +537,7 @@ sub usage_mode { ...@@ -521,7 +537,7 @@ sub usage_mode {
return $class->request_cache->{usage_mode}; return $class->request_cache->{usage_mode};
} }
else { else {
return (i_am_cgi()? USAGE_MODE_BROWSER : USAGE_MODE_CMDLINE); return (i_am_cgi() ? USAGE_MODE_BROWSER : USAGE_MODE_CMDLINE);
} }
} }
...@@ -553,12 +569,14 @@ sub switch_to_shadow_db { ...@@ -553,12 +569,14 @@ sub switch_to_shadow_db {
if (!$class->request_cache->{dbh_shadow}) { if (!$class->request_cache->{dbh_shadow}) {
if ($class->params->{'shadowdb'}) { if ($class->params->{'shadowdb'}) {
$class->request_cache->{dbh_shadow} = Bugzilla::DB::connect_shadow(); $class->request_cache->{dbh_shadow} = Bugzilla::DB::connect_shadow();
} else { }
else {
$class->request_cache->{dbh_shadow} = $class->dbh_main; $class->request_cache->{dbh_shadow} = $class->dbh_main;
} }
} }
$class->request_cache->{dbh} = $class->request_cache->{dbh_shadow}; $class->request_cache->{dbh} = $class->request_cache->{dbh_shadow};
# we have to return $class->dbh instead of {dbh} as # we have to return $class->dbh instead of {dbh} as
# {dbh_shadow} may be undefined if no shadow DB is used # {dbh_shadow} may be undefined if no shadow DB is used
# and no connection to the main DB has been established yet. # and no connection to the main DB has been established yet.
...@@ -597,17 +615,17 @@ sub fields { ...@@ -597,17 +615,17 @@ sub fields {
$by_type{$field->type}->{$name} = $field; $by_type{$field->type}->{$name} = $field;
$by_name{$name} = $field; $by_name{$name} = $field;
} }
$cache->{fields} = { by_type => \%by_type, by_name => \%by_name }; $cache->{fields} = {by_type => \%by_type, by_name => \%by_name};
} }
my $fields = $cache->{fields}; my $fields = $cache->{fields};
my %requested; my %requested;
if (my $types = delete $criteria->{type}) { if (my $types = delete $criteria->{type}) {
$types = ref($types) ? $types : [$types]; $types = ref($types) ? $types : [$types];
%requested = map { %{ $fields->{by_type}->{$_} || {} } } @$types; %requested = map { %{$fields->{by_type}->{$_} || {}} } @$types;
} }
else { else {
%requested = %{ $fields->{by_name} }; %requested = %{$fields->{by_name}};
} }
my $do_by_name = delete $criteria->{by_name}; my $do_by_name = delete $criteria->{by_name};
...@@ -622,15 +640,17 @@ sub fields { ...@@ -622,15 +640,17 @@ sub fields {
} }
} }
return $do_by_name ? \%requested return $do_by_name
: [sort { $a->sortkey <=> $b->sortkey || $a->name cmp $b->name } values %requested]; ? \%requested
: [sort { $a->sortkey <=> $b->sortkey || $a->name cmp $b->name }
values %requested];
} }
sub active_custom_fields { sub active_custom_fields {
my $class = shift; my $class = shift;
if (!exists $class->request_cache->{active_custom_fields}) { if (!exists $class->request_cache->{active_custom_fields}) {
$class->request_cache->{active_custom_fields} = $class->request_cache->{active_custom_fields}
Bugzilla::Field->match({ custom => 1, obsolete => 0 }); = Bugzilla::Field->match({custom => 1, obsolete => 0});
} }
return @{$class->request_cache->{active_custom_fields}}; return @{$class->request_cache->{active_custom_fields}};
} }
...@@ -652,7 +672,7 @@ sub local_timezone { ...@@ -652,7 +672,7 @@ sub local_timezone {
use constant request_cache => Bugzilla::Install::Util::_cache(); use constant request_cache => Bugzilla::Install::Util::_cache();
sub clear_request_cache { sub clear_request_cache {
%{ request_cache() } = (); %{request_cache()} = ();
} }
# This is a per-process cache. Under mod_cgi it's identical to the # This is a per-process cache. Under mod_cgi it's identical to the
...@@ -696,6 +716,7 @@ sub _cleanup { ...@@ -696,6 +716,7 @@ sub _cleanup {
} }
sub END { sub END {
# This is managed in mod_perl.pl and app.psgi when running # This is managed in mod_perl.pl and app.psgi when running
# in a persistent environment. # in a persistent environment.
_cleanup() unless i_am_persistent(); _cleanup() unless i_am_persistent();
......
...@@ -55,6 +55,7 @@ our @EXPORT_OK = qw( ...@@ -55,6 +55,7 @@ our @EXPORT_OK = qw(
# comment that it was retired. Also, if an error changes its name, you'll # comment that it was retired. Also, if an error changes its name, you'll
# have to fix it here. # have to fix it here.
use constant WS_ERROR_CODE => { use constant WS_ERROR_CODE => {
# Generic errors (Bugzilla::Object and others) are 50-9 # Generic errors (Bugzilla::Object and others) are 50-9
object_not_specified => 50, object_not_specified => 50,
reassign_to_empty => 50, reassign_to_empty => 50,
...@@ -70,68 +71,86 @@ use constant WS_ERROR_CODE => { ...@@ -70,68 +71,86 @@ use constant WS_ERROR_CODE => {
illegal_date => 56, illegal_date => 56,
param_integer_required => 57, param_integer_required => 57,
param_scalar_array_required => 58, param_scalar_array_required => 58,
# Bug errors usually occupy the 100-200 range. # Bug errors usually occupy the 100-200 range.
improper_bug_id_field_value => 100, improper_bug_id_field_value => 100,
bug_id_does_not_exist => 101, bug_id_does_not_exist => 101,
bug_access_denied => 102, bug_access_denied => 102,
bug_access_query => 102, bug_access_query => 102,
# These all mean "invalid alias" # These all mean "invalid alias"
alias_too_long => 103, alias_too_long => 103,
alias_in_use => 103, alias_in_use => 103,
alias_is_numeric => 103, alias_is_numeric => 103,
alias_has_comma_or_space => 103, alias_has_comma_or_space => 103,
multiple_alias_not_allowed => 103, multiple_alias_not_allowed => 103,
# Misc. bug field errors # Misc. bug field errors
illegal_field => 104, illegal_field => 104,
freetext_too_long => 104, freetext_too_long => 104,
# Component errors # Component errors
require_component => 105, require_component => 105,
component_name_too_long => 105, component_name_too_long => 105,
product_unknown_component => 105, product_unknown_component => 105,
# Invalid Product # Invalid Product
no_products => 106, no_products => 106,
entry_access_denied => 106, entry_access_denied => 106,
product_access_denied => 106, product_access_denied => 106,
product_disabled => 106, product_disabled => 106,
# Invalid Summary # Invalid Summary
require_summary => 107, require_summary => 107,
# Invalid field name # Invalid field name
invalid_field_name => 108, invalid_field_name => 108,
# Not authorized to edit the bug # Not authorized to edit the bug
product_edit_denied => 109, product_edit_denied => 109,
# Comment-related errors # Comment-related errors
comment_is_private => 110, comment_is_private => 110,
comment_id_invalid => 111, comment_id_invalid => 111,
comment_too_long => 114, comment_too_long => 114,
comment_invalid_isprivate => 117, comment_invalid_isprivate => 117,
markdown_disabled => 140, markdown_disabled => 140,
# Comment tagging # Comment tagging
comment_tag_disabled => 125, comment_tag_disabled => 125,
comment_tag_invalid => 126, comment_tag_invalid => 126,
comment_tag_too_long => 127, comment_tag_too_long => 127,
comment_tag_too_short => 128, comment_tag_too_short => 128,
# See Also errors # See Also errors
bug_url_invalid => 112, bug_url_invalid => 112,
bug_url_too_long => 112, bug_url_too_long => 112,
# Insidergroup Errors # Insidergroup Errors
user_not_insider => 113, user_not_insider => 113,
# Note: 114 is above in the Comment-related section. # Note: 114 is above in the Comment-related section.
# Bug update errors # Bug update errors
illegal_change => 115, illegal_change => 115,
# Dependency errors # Dependency errors
dependency_loop_single => 116, dependency_loop_single => 116,
dependency_loop_multi => 116, dependency_loop_multi => 116,
# Note: 117 is above in the Comment-related section. # Note: 117 is above in the Comment-related section.
# Dup errors # Dup errors
dupe_loop_detected => 118, dupe_loop_detected => 118,
dupe_id_required => 119, dupe_id_required => 119,
# Bug-related group errors # Bug-related group errors
group_invalid_removal => 120, group_invalid_removal => 120,
group_restriction_not_allowed => 120, group_restriction_not_allowed => 120,
# Status/Resolution errors # Status/Resolution errors
missing_resolution => 121, missing_resolution => 121,
resolution_not_allowed => 122, resolution_not_allowed => 122,
illegal_bug_status_transition => 123, illegal_bug_status_transition => 123,
# Flag errors # Flag errors
flag_status_invalid => 129, flag_status_invalid => 129,
flag_update_denied => 130, flag_update_denied => 130,
...@@ -162,8 +181,10 @@ use constant WS_ERROR_CODE => { ...@@ -162,8 +181,10 @@ use constant WS_ERROR_CODE => {
account_creation_disabled => 501, account_creation_disabled => 501,
account_creation_restricted => 501, account_creation_restricted => 501,
password_too_short => 502, password_too_short => 502,
# Error 503 password_too_long no longer exists. # Error 503 password_too_long no longer exists.
invalid_username => 504, invalid_username => 504,
# This is from strict_isolation, but it also basically means # This is from strict_isolation, but it also basically means
# "invalid user." # "invalid user."
invalid_user_group => 504, invalid_user_group => 504,
...@@ -173,9 +194,11 @@ use constant WS_ERROR_CODE => { ...@@ -173,9 +194,11 @@ use constant WS_ERROR_CODE => {
# Attachment errors are 600-700. # Attachment errors are 600-700.
file_too_large => 600, file_too_large => 600,
invalid_content_type => 601, invalid_content_type => 601,
# Error 602 attachment_illegal_url no longer exists. # Error 602 attachment_illegal_url no longer exists.
file_not_specified => 603, file_not_specified => 603,
missing_attachment_description => 604, missing_attachment_description => 604,
# Error 605 attachment_url_disabled no longer exists. # Error 605 attachment_url_disabled no longer exists.
zero_length_file => 606, zero_length_file => 606,
...@@ -271,10 +294,10 @@ sub REST_STATUS_CODE_MAP { ...@@ -271,10 +294,10 @@ sub REST_STATUS_CODE_MAP {
}; };
Bugzilla::Hook::process('webservice_status_code_map', Bugzilla::Hook::process('webservice_status_code_map',
{ status_code_map => $status_code_map }); {status_code_map => $status_code_map});
return $status_code_map; return $status_code_map;
}; }
# These are the fallback defaults for errors not in ERROR_CODE. # These are the fallback defaults for errors not in ERROR_CODE.
use constant ERROR_UNKNOWN_FATAL => -32000; use constant ERROR_UNKNOWN_FATAL => -32000;
......
...@@ -27,7 +27,7 @@ use constant DATE_FIELDS => {}; ...@@ -27,7 +27,7 @@ use constant DATE_FIELDS => {};
use constant BASE64_FIELDS => {}; use constant BASE64_FIELDS => {};
# For some methods, we shouldn't call Bugzilla->login before we call them # For some methods, we shouldn't call Bugzilla->login before we call them
use constant LOGIN_EXEMPT => { }; use constant LOGIN_EXEMPT => {};
# Used to allow methods to be called in the JSON-RPC WebService via GET. # Used to allow methods to be called in the JSON-RPC WebService via GET.
# Methods that can modify data MUST not be listed here. # Methods that can modify data MUST not be listed here.
......
...@@ -38,17 +38,18 @@ sub REST_RESOURCES { ...@@ -38,17 +38,18 @@ sub REST_RESOURCES {
use re '/a'; use re '/a';
return [ return [
# bug-id # bug-id
qr{^/bug_user_last_visit/(\d+)$}, { qr{^/bug_user_last_visit/(\d+)$},
{
GET => { GET => {
method => 'get', method => 'get',
params => sub { params => sub {
return { ids => [$_[0]] }; return {ids => [$_[0]]};
}, },
}, },
POST => { POST => {
method => 'update', method => 'update',
params => sub { params => sub {
return { ids => [$_[0]] }; return {ids => [$_[0]]};
}, },
}, },
}, },
...@@ -67,7 +68,7 @@ sub update { ...@@ -67,7 +68,7 @@ sub update {
$user->login(LOGIN_REQUIRED); $user->login(LOGIN_REQUIRED);
my $ids = $params->{ids} // []; my $ids = $params->{ids} // [];
ThrowCodeError('param_required', { param => 'ids' }) unless @$ids; ThrowCodeError('param_required', {param => 'ids'}) unless @$ids;
# Cache permissions for bugs. This highly reduces the number of calls to the # Cache permissions for bugs. This highly reduces the number of calls to the
# DB. visible_bugs() is only able to handle bug IDs, so we have to skip # DB. visible_bugs() is only able to handle bug IDs, so we have to skip
...@@ -78,18 +79,15 @@ sub update { ...@@ -78,18 +79,15 @@ sub update {
my @results; my @results;
my $last_visit_ts = $dbh->selectrow_array('SELECT NOW()'); my $last_visit_ts = $dbh->selectrow_array('SELECT NOW()');
foreach my $bug_id (@$ids) { foreach my $bug_id (@$ids) {
my $bug = Bugzilla::Bug->check({ id => $bug_id, cache => 1 }); my $bug = Bugzilla::Bug->check({id => $bug_id, cache => 1});
ThrowUserError('user_not_involved', { bug_id => $bug->id }) ThrowUserError('user_not_involved', {bug_id => $bug->id})
unless $user->is_involved_in_bug($bug); unless $user->is_involved_in_bug($bug);
$bug->update_user_last_visit($user, $last_visit_ts); $bug->update_user_last_visit($user, $last_visit_ts);
push( push(@results,
@results, _bug_user_last_visit_to_hash($bug->bug_id, $last_visit_ts, $params));
_bug_user_last_visit_to_hash(
$bug->bug_id, $last_visit_ts, $params
));
} }
$dbh->bz_commit_transaction(); $dbh->bz_commit_transaction();
...@@ -105,18 +103,21 @@ sub get { ...@@ -105,18 +103,21 @@ sub get {
my @last_visits; my @last_visits;
if ($ids) { if ($ids) {
# Cache permissions for bugs. This highly reduces the number of calls to # Cache permissions for bugs. This highly reduces the number of calls to
# the DB. visible_bugs() is only able to handle bug IDs, so we have to # the DB. visible_bugs() is only able to handle bug IDs, so we have to
# skip aliases. # skip aliases.
$user->visible_bugs([grep /^[0-9]+$/, @$ids]); $user->visible_bugs([grep /^[0-9]+$/, @$ids]);
my %last_visit = map { $_->bug_id => $_->last_visit_ts } @{ $user->last_visited($ids) }; my %last_visit
@last_visits = map { _bug_user_last_visit_to_hash($_, $last_visit{$_}, $params) } @$ids; = map { $_->bug_id => $_->last_visit_ts } @{$user->last_visited($ids)};
@last_visits
= map { _bug_user_last_visit_to_hash($_, $last_visit{$_}, $params) } @$ids;
} }
else { else {
@last_visits = map { @last_visits
_bug_user_last_visit_to_hash($_->bug_id, $_->last_visit_ts, $params) = map { _bug_user_last_visit_to_hash($_->bug_id, $_->last_visit_ts, $params) }
} @{ $user->last_visited }; @{$user->last_visited};
} }
return \@last_visits; return \@last_visits;
...@@ -125,8 +126,8 @@ sub get { ...@@ -125,8 +126,8 @@ sub get {
sub _bug_user_last_visit_to_hash { sub _bug_user_last_visit_to_hash {
my ($bug_id, $last_visit_ts, $params) = @_; my ($bug_id, $last_visit_ts, $params) = @_;
my %result = (id => as_int($bug_id), my %result
last_visit_ts => as_datetime($last_visit_ts)); = (id => as_int($bug_id), last_visit_ts => as_datetime($last_visit_ts));
return filter($params, \%result); return filter($params, \%result);
} }
......
...@@ -26,11 +26,7 @@ extends 'Bugzilla::API::1_0::Resource'; ...@@ -26,11 +26,7 @@ extends 'Bugzilla::API::1_0::Resource';
############## ##############
# Basic info that is needed before logins # Basic info that is needed before logins
use constant LOGIN_EXEMPT => { use constant LOGIN_EXEMPT => {parameters => 1, timezone => 1, version => 1,};
parameters => 1,
timezone => 1,
version => 1,
};
use constant READ_ONLY => qw( use constant READ_ONLY => qw(
extensions extensions
...@@ -92,36 +88,12 @@ use constant PARAMETERS_LOGGED_IN => qw( ...@@ -92,36 +88,12 @@ use constant PARAMETERS_LOGGED_IN => qw(
sub REST_RESOURCES { sub REST_RESOURCES {
my $rest_resources = [ my $rest_resources = [
qr{^/version$}, { qr{^/version$}, {GET => {method => 'version'}},
GET => { qr{^/extensions$}, {GET => {method => 'extensions'}},
method => 'version' qr{^/timezone$}, {GET => {method => 'timezone'}},
} qr{^/time$}, {GET => {method => 'time'}},
}, qr{^/last_audit_time$}, {GET => {method => 'last_audit_time'}},
qr{^/extensions$}, { qr{^/parameters$}, {GET => {method => 'parameters'}}
GET => {
method => 'extensions'
}
},
qr{^/timezone$}, {
GET => {
method => 'timezone'
}
},
qr{^/time$}, {
GET => {
method => 'time'
}
},
qr{^/last_audit_time$}, {
GET => {
method => 'last_audit_time'
}
},
qr{^/parameters$}, {
GET => {
method => 'parameters'
}
}
]; ];
return $rest_resources; return $rest_resources;
} }
...@@ -132,29 +104,31 @@ sub REST_RESOURCES { ...@@ -132,29 +104,31 @@ sub REST_RESOURCES {
sub version { sub version {
my $self = shift; my $self = shift;
return { version => as_string(BUGZILLA_VERSION) }; return {version => as_string(BUGZILLA_VERSION)};
} }
sub extensions { sub extensions {
my $self = shift; my $self = shift;
my %retval; my %retval;
foreach my $extension (@{ Bugzilla->extensions }) { foreach my $extension (@{Bugzilla->extensions}) {
my $version = $extension->VERSION || 0; my $version = $extension->VERSION || 0;
my $name = $extension->NAME; my $name = $extension->NAME;
$retval{$name}->{version} = as_string($version); $retval{$name}->{version} = as_string($version);
} }
return { extensions => \%retval }; return {extensions => \%retval};
} }
sub timezone { sub timezone {
my $self = shift; my $self = shift;
# All Webservices return times in UTC; Use UTC here for backwards compat. # All Webservices return times in UTC; Use UTC here for backwards compat.
return { timezone => as_string("+0000") }; return {timezone => as_string("+0000")};
} }
sub time { sub time {
my ($self) = @_; my ($self) = @_;
# All Webservices return times in UTC; Use UTC here for backwards compat. # All Webservices return times in UTC; Use UTC here for backwards compat.
# Hardcode values where appropriate # Hardcode values where appropriate
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
...@@ -163,10 +137,7 @@ sub time { ...@@ -163,10 +137,7 @@ sub time {
$db_time = datetime_from($db_time, 'UTC'); $db_time = datetime_from($db_time, 'UTC');
my $now_utc = DateTime->now(); my $now_utc = DateTime->now();
return { return {db_time => as_datetime($db_time), web_time => as_datetime($now_utc),};
db_time => as_datetime($db_time),
web_time => as_datetime($now_utc),
};
} }
sub last_audit_time { sub last_audit_time {
...@@ -177,7 +148,7 @@ sub last_audit_time { ...@@ -177,7 +148,7 @@ sub last_audit_time {
my $class_values = $params->{class}; my $class_values = $params->{class};
my @class_values_quoted; my @class_values_quoted;
foreach my $class_value (@$class_values) { foreach my $class_value (@$class_values) {
push (@class_values_quoted, $dbh->quote($class_value)) push(@class_values_quoted, $dbh->quote($class_value))
if $class_value =~ /^Bugzilla(::[a-zA-Z0-9_]+)*$/; if $class_value =~ /^Bugzilla(::[a-zA-Z0-9_]+)*$/;
} }
...@@ -191,9 +162,7 @@ sub last_audit_time { ...@@ -191,9 +162,7 @@ sub last_audit_time {
# Hardcode values where appropriate # Hardcode values where appropriate
$last_audit_time = datetime_from($last_audit_time, 'UTC'); $last_audit_time = datetime_from($last_audit_time, 'UTC');
return { return {last_audit_time => as_datetime($last_audit_time)};
last_audit_time => as_datetime($last_audit_time)
};
} }
sub parameters { sub parameters {
...@@ -202,9 +171,10 @@ sub parameters { ...@@ -202,9 +171,10 @@ sub parameters {
my $params = Bugzilla->params; my $params = Bugzilla->params;
$args ||= {}; $args ||= {};
my @params_list = $user->in_group('tweakparams') my @params_list
? keys(%$params) = $user->in_group('tweakparams') ? keys(%$params)
: $user->id ? PARAMETERS_LOGGED_IN : PARAMETERS_LOGGED_OUT; : $user->id ? PARAMETERS_LOGGED_IN
: PARAMETERS_LOGGED_OUT;
my %parameters; my %parameters;
foreach my $param (@params_list) { foreach my $param (@params_list) {
...@@ -212,7 +182,7 @@ sub parameters { ...@@ -212,7 +182,7 @@ sub parameters {
$parameters{$param} = as_string($params->{$param}); $parameters{$param} = as_string($params->{$param});
} }
return { parameters => \%parameters }; return {parameters => \%parameters};
} }
1; 1;
......
...@@ -34,12 +34,13 @@ use constant PUBLIC_METHODS => qw( ...@@ -34,12 +34,13 @@ use constant PUBLIC_METHODS => qw(
sub REST_RESOURCES { sub REST_RESOURCES {
my $rest_resources = [ my $rest_resources = [
qr{^/classification/([^/]+)$}, { qr{^/classification/([^/]+)$},
{
GET => { GET => {
method => 'get', method => 'get',
params => sub { params => sub {
my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names'; my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
return { $param => [ $_[0] ] }; return {$param => [$_[0]]};
} }
} }
} }
...@@ -54,9 +55,10 @@ sub REST_RESOURCES { ...@@ -54,9 +55,10 @@ sub REST_RESOURCES {
sub get { sub get {
my ($self, $params) = validate(@_, 'names', 'ids'); my ($self, $params) = validate(@_, 'names', 'ids');
defined $params->{names} || defined $params->{ids} defined $params->{names}
|| ThrowCodeError('params_required', { function => 'Classification.get', || defined $params->{ids}
params => ['names', 'ids'] }); || ThrowCodeError('params_required',
{function => 'Classification.get', params => ['names', 'ids']});
my $user = Bugzilla->user; my $user = Bugzilla->user;
...@@ -66,43 +68,53 @@ sub get { ...@@ -66,43 +68,53 @@ sub get {
Bugzilla->switch_to_shadow_db; Bugzilla->switch_to_shadow_db;
my @classification_objs = @{ params_to_objects($params, 'Bugzilla::Classification') }; my @classification_objs
= @{params_to_objects($params, 'Bugzilla::Classification')};
unless ($user->in_group('editclassifications')) { unless ($user->in_group('editclassifications')) {
my %selectable_class = map { $_->id => 1 } @{$user->get_selectable_classifications}; my %selectable_class
= map { $_->id => 1 } @{$user->get_selectable_classifications};
@classification_objs = grep { $selectable_class{$_->id} } @classification_objs; @classification_objs = grep { $selectable_class{$_->id} } @classification_objs;
} }
my @classifications = map { $self->_classification_to_hash($_, $params) } @classification_objs; my @classifications
= map { $self->_classification_to_hash($_, $params) } @classification_objs;
return { classifications => \@classifications }; return {classifications => \@classifications};
} }
sub _classification_to_hash { sub _classification_to_hash {
my ($self, $classification, $params) = @_; my ($self, $classification, $params) = @_;
my $user = Bugzilla->user; my $user = Bugzilla->user;
return unless (Bugzilla->params->{'useclassification'} || $user->in_group('editclassifications')); return
unless (Bugzilla->params->{'useclassification'}
|| $user->in_group('editclassifications'));
my $products = $user->in_group('editclassifications') ? my $products
$classification->products : $user->get_selectable_products($classification->id); = $user->in_group('editclassifications')
? $classification->products
: $user->get_selectable_products($classification->id);
return filter $params, { return filter $params,
{
id => as_int($classification->id), id => as_int($classification->id),
name => as_string($classification->name), name => as_string($classification->name),
description => as_string($classification->description), description => as_string($classification->description),
sort_key => as_int($classification->sortkey), sort_key => as_int($classification->sortkey),
products => [ map { $self->_product_to_hash($_, $params) } @$products ], products => [map { $self->_product_to_hash($_, $params) } @$products],
}; };
} }
sub _product_to_hash { sub _product_to_hash {
my ($self, $product, $params) = @_; my ($self, $product, $params) = @_;
return filter $params, { return filter $params,
{
id => as_int($product->id), id => as_int($product->id),
name => as_string($product->name), name => as_string($product->name),
description => as_string($product->description), description => as_string($product->description),
}, undef, 'products'; },
undef, 'products';
} }
1; 1;
......
...@@ -39,9 +39,7 @@ use constant CREATE_MAPPED_FIELDS => { ...@@ -39,9 +39,7 @@ use constant CREATE_MAPPED_FIELDS => {
is_open => 'isactive', is_open => 'isactive',
}; };
use constant MAPPED_FIELDS => { use constant MAPPED_FIELDS => {is_open => 'is_active',};
is_open => 'is_active',
};
use constant MAPPED_RETURNS => { use constant MAPPED_RETURNS => {
initialowner => 'default_assignee', initialowner => 'default_assignee',
...@@ -52,37 +50,35 @@ use constant MAPPED_RETURNS => { ...@@ -52,37 +50,35 @@ use constant MAPPED_RETURNS => {
sub REST_RESOURCES { sub REST_RESOURCES {
my $rest_resources = [ my $rest_resources = [
qr{^/component$}, { qr{^/component$},
POST => { {POST => {method => 'create', success_code => STATUS_CREATED}},
method => 'create', qr{^/component/(\d+)$},
success_code => STATUS_CREATED {
}
},
qr{^/component/(\d+)$}, {
PUT => { PUT => {
method => 'update', method => 'update',
params => sub { params => sub {
return { ids => [ $_[0] ] }; return {ids => [$_[0]]};
} }
}, },
DELETE => { DELETE => {
method => 'delete', method => 'delete',
params => sub { params => sub {
return { ids => [ $_[0] ] }; return {ids => [$_[0]]};
} }
}, },
}, },
qr{^/component/([^/]+)/([^/]+)$}, { qr{^/component/([^/]+)/([^/]+)$},
{
PUT => { PUT => {
method => 'update', method => 'update',
params => sub { params => sub {
return { names => [ { product => $_[0], component => $_[1] } ] }; return {names => [{product => $_[0], component => $_[1]}]};
} }
}, },
DELETE => { DELETE => {
method => 'delete', method => 'delete',
params => sub { params => sub {
return { names => [ { product => $_[0], component => $_[1] } ] }; return {names => [{product => $_[0], component => $_[1]}]};
} }
}, },
}, },
...@@ -100,10 +96,9 @@ sub create { ...@@ -100,10 +96,9 @@ sub create {
my $user = Bugzilla->login(LOGIN_REQUIRED); my $user = Bugzilla->login(LOGIN_REQUIRED);
$user->in_group('editcomponents') $user->in_group('editcomponents')
|| scalar @{ $user->get_products_by_permission('editcomponents') } || scalar @{$user->get_products_by_permission('editcomponents')}
|| ThrowUserError('auth_failure', { group => 'editcomponents', || ThrowUserError('auth_failure',
action => 'edit', {group => 'editcomponents', action => 'edit', object => 'components'});
object => 'components' });
my $product = $user->check_can_admin_product($params->{product}); my $product = $user->check_can_admin_product($params->{product});
...@@ -113,10 +108,11 @@ sub create { ...@@ -113,10 +108,11 @@ sub create {
# Create the component and return the newly created id. # Create the component and return the newly created id.
my $component = Bugzilla::Component->create($values); my $component = Bugzilla::Component->create($values);
return { id => as_int($component->id) }; return {id => as_int($component->id)};
} }
sub _component_params_to_objects { sub _component_params_to_objects {
# We can't use Util's _param_to_objects since name is a hash # We can't use Util's _param_to_objects since name is a hash
my $params = shift; my $params = shift;
my $user = Bugzilla->user; my $user = Bugzilla->user;
...@@ -124,17 +120,18 @@ sub _component_params_to_objects { ...@@ -124,17 +120,18 @@ sub _component_params_to_objects {
my @components = (); my @components = ();
if (defined $params->{ids}) { if (defined $params->{ids}) {
push @components, @{ Bugzilla::Component->new_from_list($params->{ids}) }; push @components, @{Bugzilla::Component->new_from_list($params->{ids})};
} }
if (defined $params->{names}) { if (defined $params->{names}) {
# To get the component objects for product/component combination # To get the component objects for product/component combination
# first obtain the product object from the passed product name # first obtain the product object from the passed product name
foreach my $name_hash (@{$params->{names}}) { foreach my $name_hash (@{$params->{names}}) {
my $product = $user->check_can_admin_product($name_hash->{product}); my $product = $user->check_can_admin_product($name_hash->{product});
push @components, @{ Bugzilla::Component->match({ push @components,
product_id => $product->id, @{Bugzilla::Component->match({
name => $name_hash->{component} product_id => $product->id, name => $name_hash->{component}
})}; })};
} }
} }
...@@ -143,6 +140,7 @@ sub _component_params_to_objects { ...@@ -143,6 +140,7 @@ sub _component_params_to_objects {
my @accessible_components; my @accessible_components;
foreach my $component (@components) { foreach my $component (@components) {
# Skip if we already included this component # Skip if we already included this component
next if $seen_component_ids{$component->id}++; next if $seen_component_ids{$component->id}++;
...@@ -162,14 +160,14 @@ sub update { ...@@ -162,14 +160,14 @@ sub update {
my $user = Bugzilla->login(LOGIN_REQUIRED); my $user = Bugzilla->login(LOGIN_REQUIRED);
$user->in_group('editcomponents') $user->in_group('editcomponents')
|| scalar @{ $user->get_products_by_permission('editcomponents') } || scalar @{$user->get_products_by_permission('editcomponents')}
|| ThrowUserError("auth_failure", { group => "editcomponents", || ThrowUserError("auth_failure",
action => "edit", {group => "editcomponents", action => "edit", object => "components"});
object => "components" });
defined($params->{names}) || defined($params->{ids}) defined($params->{names})
|| defined($params->{ids})
|| ThrowCodeError('params_required', || ThrowCodeError('params_required',
{ function => 'Component.update', params => ['ids', 'names'] }); {function => 'Component.update', params => ['ids', 'names']});
my $component_objects = _component_params_to_objects($params); my $component_objects = _component_params_to_objects($params);
...@@ -178,7 +176,7 @@ sub update { ...@@ -178,7 +176,7 @@ sub update {
if ($params->{name}) { if ($params->{name}) {
my %unique_product_comps; my %unique_product_comps;
foreach my $comp (@$component_objects) { foreach my $comp (@$component_objects) {
if($unique_product_comps{$comp->product_id}) { if ($unique_product_comps{$comp->product_id}) {
ThrowUserError("multiple_components_update_not_allowed"); ThrowUserError("multiple_components_update_not_allowed");
} }
else { else {
...@@ -207,26 +205,23 @@ sub update { ...@@ -207,26 +205,23 @@ sub update {
my @result; my @result;
foreach my $component (@$component_objects) { foreach my $component (@$component_objects) {
my %hash = ( my %hash = (id => $component->id, changes => {},);
id => $component->id,
changes => {},
);
foreach my $field (keys %{ $changes{$component->id} }) { foreach my $field (keys %{$changes{$component->id}}) {
my $change = $changes{$component->id}->{$field}; my $change = $changes{$component->id}->{$field};
if ($field eq 'default_assignee' if ( $field eq 'default_assignee'
|| $field eq 'default_qa_contact' || $field eq 'default_qa_contact'
|| $field eq 'default_cc' || $field eq 'default_cc')
) { {
# We need to convert user ids to login names # We need to convert user ids to login names
my @old_user_ids = split(/[,\s]+/, $change->[0]); my @old_user_ids = split(/[,\s]+/, $change->[0]);
my @new_user_ids = split(/[,\s]+/, $change->[1]); my @new_user_ids = split(/[,\s]+/, $change->[1]);
my @old_users = map { $_->login } my @old_users
@{Bugzilla::User->new_from_list(\@old_user_ids)}; = map { $_->login } @{Bugzilla::User->new_from_list(\@old_user_ids)};
my @new_users = map { $_->login } my @new_users
@{Bugzilla::User->new_from_list(\@new_user_ids)}; = map { $_->login } @{Bugzilla::User->new_from_list(\@new_user_ids)};
$hash{changes}{$field} = { $hash{changes}{$field} = {
removed => as_string(join(', ', @old_users)), removed => as_string(join(', ', @old_users)),
...@@ -234,17 +229,15 @@ sub update { ...@@ -234,17 +229,15 @@ sub update {
}; };
} }
else { else {
$hash{changes}{$field} = { $hash{changes}{$field}
removed => as_string($change->[0]), = {removed => as_string($change->[0]), added => as_string($change->[1])};
added => as_string($change->[1])
};
} }
} }
push(@result, \%hash); push(@result, \%hash);
} }
return { components => \@result }; return {components => \@result};
} }
sub delete { sub delete {
...@@ -253,14 +246,14 @@ sub delete { ...@@ -253,14 +246,14 @@ sub delete {
my $user = Bugzilla->login(LOGIN_REQUIRED); my $user = Bugzilla->login(LOGIN_REQUIRED);
$user->in_group('editcomponents') $user->in_group('editcomponents')
|| scalar @{ $user->get_products_by_permission('editcomponents') } || scalar @{$user->get_products_by_permission('editcomponents')}
|| ThrowUserError("auth_failure", { group => "editcomponents", || ThrowUserError("auth_failure",
action => "edit", {group => "editcomponents", action => "edit", object => "components"});
object => "components" });
defined($params->{names}) || defined($params->{ids}) defined($params->{names})
|| defined($params->{ids})
|| ThrowCodeError('params_required', || ThrowCodeError('params_required',
{ function => 'Component.delete', params => ['ids', 'names'] }); {function => 'Component.delete', params => ['ids', 'names']});
my $component_objects = _component_params_to_objects($params); my $component_objects = _component_params_to_objects($params);
...@@ -273,10 +266,10 @@ sub delete { ...@@ -273,10 +266,10 @@ sub delete {
my @result; my @result;
foreach my $component (@$component_objects) { foreach my $component (@$component_objects) {
push @result, { id => $component->id }; push @result, {id => $component->id};
} }
return { components => \@result }; return {components => \@result};
} }
1; 1;
......
...@@ -31,28 +31,23 @@ use constant PUBLIC_METHODS => qw( ...@@ -31,28 +31,23 @@ use constant PUBLIC_METHODS => qw(
update update
); );
use constant MAPPED_RETURNS => { use constant MAPPED_RETURNS =>
userregexp => 'user_regexp', {userregexp => 'user_regexp', isactive => 'is_active'};
isactive => 'is_active'
};
sub REST_RESOURCES { sub REST_RESOURCES {
my $rest_resources = [ my $rest_resources = [
qr{^/group$}, { qr{^/group$},
GET => { {
method => 'get' GET => {method => 'get'},
POST => {method => 'create', success_code => STATUS_CREATED}
}, },
POST => { qr{^/group/([^/]+)$},
method => 'create', {
success_code => STATUS_CREATED
}
},
qr{^/group/([^/]+)$}, {
PUT => { PUT => {
method => 'update', method => 'update',
params => sub { params => sub {
my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names'; my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
return { $param => [ $_[0] ] }; return {$param => [$_[0]]};
} }
} }
} }
...@@ -69,9 +64,9 @@ sub create { ...@@ -69,9 +64,9 @@ sub create {
Bugzilla->login(LOGIN_REQUIRED); Bugzilla->login(LOGIN_REQUIRED);
Bugzilla->user->in_group('creategroups') Bugzilla->user->in_group('creategroups')
|| ThrowUserError("auth_failure", { group => "creategroups", || ThrowUserError("auth_failure",
action => "add", {group => "creategroups", action => "add", object => "group"});
object => "group"});
# Create group # Create group
my $group = Bugzilla::Group->create({ my $group = Bugzilla::Group->create({
name => $params->{name}, name => $params->{name},
...@@ -81,7 +76,7 @@ sub create { ...@@ -81,7 +76,7 @@ sub create {
isbuggroup => 1, isbuggroup => 1,
icon_url => $params->{icon_url} icon_url => $params->{icon_url}
}); });
return { id => as_int($group->id) }; return {id => as_int($group->id)};
} }
sub update { sub update {
...@@ -91,13 +86,13 @@ sub update { ...@@ -91,13 +86,13 @@ sub update {
Bugzilla->login(LOGIN_REQUIRED); Bugzilla->login(LOGIN_REQUIRED);
Bugzilla->user->in_group('creategroups') Bugzilla->user->in_group('creategroups')
|| ThrowUserError("auth_failure", { group => "creategroups", || ThrowUserError("auth_failure",
action => "edit", {group => "creategroups", action => "edit", object => "group"});
object => "group" });
defined($params->{names}) || defined($params->{ids}) defined($params->{names})
|| defined($params->{ids})
|| ThrowCodeError('params_required', || ThrowCodeError('params_required',
{ function => 'Group.update', params => ['ids', 'names'] }); {function => 'Group.update', params => ['ids', 'names']});
my $group_objects = params_to_objects($params, 'Bugzilla::Group'); my $group_objects = params_to_objects($params, 'Bugzilla::Group');
...@@ -121,21 +116,16 @@ sub update { ...@@ -121,21 +116,16 @@ sub update {
my @result; my @result;
foreach my $group (@$group_objects) { foreach my $group (@$group_objects) {
my %hash = ( my %hash = (id => $group->id, changes => {},);
id => $group->id, foreach my $field (keys %{$changes{$group->id}}) {
changes => {},
);
foreach my $field (keys %{ $changes{$group->id} }) {
my $change = $changes{$group->id}->{$field}; my $change = $changes{$group->id}->{$field};
$hash{changes}{$field} = { $hash{changes}{$field}
removed => as_string($change->[0]), = {removed => as_string($change->[0]), added => as_string($change->[1])};
added => as_string($change->[1])
};
} }
push(@result, \%hash); push(@result, \%hash);
} }
return { groups => \@result }; return {groups => \@result};
} }
sub get { sub get {
...@@ -145,7 +135,8 @@ sub get { ...@@ -145,7 +135,8 @@ sub get {
# Reject access if there is no sense in continuing. # Reject access if there is no sense in continuing.
my $user = Bugzilla->user; my $user = Bugzilla->user;
my $all_groups = $user->in_group('editusers') || $user->in_group('creategroups'); my $all_groups
= $user->in_group('editusers') || $user->in_group('creategroups');
if (!$all_groups && !$user->can_bless) { if (!$all_groups && !$user->can_bless) {
ThrowUserError('group_cannot_view'); ThrowUserError('group_cannot_view');
} }
...@@ -155,17 +146,20 @@ sub get { ...@@ -155,17 +146,20 @@ sub get {
my $groups = []; my $groups = [];
if (defined $params->{ids}) { if (defined $params->{ids}) {
# Get the groups by id # Get the groups by id
$groups = Bugzilla::Group->new_from_list($params->{ids}); $groups = Bugzilla::Group->new_from_list($params->{ids});
} }
if (defined $params->{names}) { if (defined $params->{names}) {
# Get the groups by name. Check will throw an error if a bad name is given # Get the groups by name. Check will throw an error if a bad name is given
foreach my $name (@{$params->{names}}) { foreach my $name (@{$params->{names}}) {
# Skip if we got this from params->{id} # Skip if we got this from params->{id}
next if grep { $_->name eq $name } @$groups; next if grep { $_->name eq $name } @$groups;
push @$groups, Bugzilla::Group->check({ name => $name }); push @$groups, Bugzilla::Group->check({name => $name});
} }
} }
...@@ -181,7 +175,7 @@ sub get { ...@@ -181,7 +175,7 @@ sub get {
# Now create a result entry for each. # Now create a result entry for each.
my @groups = map { $self->_group_to_hash($params, $_) } @$groups; my @groups = map { $self->_group_to_hash($params, $_) } @$groups;
return { groups => \@groups }; return {groups => \@groups};
} }
sub _group_to_hash { sub _group_to_hash {
...@@ -218,6 +212,7 @@ sub _get_group_membership { ...@@ -218,6 +212,7 @@ sub _get_group_membership {
my $visibleGroups; my $visibleGroups;
if (!$editusers && Bugzilla->params->{'usevisibilitygroups'}) { if (!$editusers && Bugzilla->params->{'usevisibilitygroups'}) {
# Show only users in visible groups. # Show only users in visible groups.
$visibleGroups = $user->visible_groups_inherited; $visibleGroups = $user->visible_groups_inherited;
...@@ -227,7 +222,11 @@ sub _get_group_membership { ...@@ -227,7 +222,11 @@ sub _get_group_membership {
AND ugm.isbless = 0 AND ugm.isbless = 0
AND } . $dbh->sql_in('ugm.group_id', $visibleGroups); AND } . $dbh->sql_in('ugm.group_id', $visibleGroups);
} }
} elsif ($editusers || $user->can_bless($group->id) || $user->in_group('creategroups')) { }
elsif ($editusers
|| $user->can_bless($group->id)
|| $user->in_group('creategroups'))
{
$visibleGroups = 1; $visibleGroups = 1;
$query .= qq{, user_group_map AS ugm $query .= qq{, user_group_map AS ugm
WHERE ugm.user_id = profiles.userid WHERE ugm.user_id = profiles.userid
...@@ -235,7 +234,7 @@ sub _get_group_membership { ...@@ -235,7 +234,7 @@ sub _get_group_membership {
}; };
} }
if (!$visibleGroups) { if (!$visibleGroups) {
ThrowUserError('group_not_visible', { group => $group }); ThrowUserError('group_not_visible', {group => $group});
} }
my $grouplist = Bugzilla::Group->flatten_group_membership($group->id); my $grouplist = Bugzilla::Group->flatten_group_membership($group->id);
...@@ -243,8 +242,7 @@ sub _get_group_membership { ...@@ -243,8 +242,7 @@ sub _get_group_membership {
my $userids = $dbh->selectcol_arrayref($query); my $userids = $dbh->selectcol_arrayref($query);
my $user_objects = Bugzilla::User->new_from_list($userids); my $user_objects = Bugzilla::User->new_from_list($userids);
my @users = my @users = map { {
map {{
id => as_int($_->id), id => as_int($_->id),
real_name => as_string($_->name), real_name => as_string($_->name),
name => as_login($_->login), name => as_login($_->login),
...@@ -252,7 +250,7 @@ sub _get_group_membership { ...@@ -252,7 +250,7 @@ sub _get_group_membership {
can_login => as_boolean($_->is_enabled), can_login => as_boolean($_->is_enabled),
email_enabled => as_boolean($_->email_enabled), email_enabled => as_boolean($_->email_enabled),
login_denied_text => as_string($_->disabledtext), login_denied_text => as_string($_->disabledtext),
}} @$user_objects; } } @$user_objects;
return \@users; return \@users;
} }
......
...@@ -71,17 +71,18 @@ sub extract_flags { ...@@ -71,17 +71,18 @@ sub extract_flags {
if ($id) { if ($id) {
my $flag_obj = grep($id == $_->id, @$current_flags); my $flag_obj = grep($id == $_->id, @$current_flags);
$flag_obj || ThrowUserError('object_does_not_exist', $flag_obj
{ class => 'Bugzilla::Flag', id => $id }); || ThrowUserError('object_does_not_exist',
{class => 'Bugzilla::Flag', id => $id});
} }
elsif ($type_id) { elsif ($type_id) {
my $type_obj = grep($type_id == $_->id, @$flag_types); my $type_obj = grep($type_id == $_->id, @$flag_types);
$type_obj || ThrowUserError('object_does_not_exist', $type_obj
{ class => 'Bugzilla::FlagType', id => $type_id }); || ThrowUserError('object_does_not_exist',
{class => 'Bugzilla::FlagType', id => $type_id});
if (!$new) { if (!$new) {
my @flag_matches = grep($type_id == $_->type->id, @$current_flags); my @flag_matches = grep($type_id == $_->type->id, @$current_flags);
@flag_matches > 1 && ThrowUserError('flag_not_unique', @flag_matches > 1 && ThrowUserError('flag_not_unique', {value => $type_id});
{ value => $type_id });
if (!@flag_matches) { if (!@flag_matches) {
delete $flag->{id}; delete $flag->{id};
} }
...@@ -93,17 +94,17 @@ sub extract_flags { ...@@ -93,17 +94,17 @@ sub extract_flags {
} }
elsif ($name) { elsif ($name) {
my @type_matches = grep($name eq $_->name, @$flag_types); my @type_matches = grep($name eq $_->name, @$flag_types);
@type_matches > 1 && ThrowUserError('flag_type_not_unique', @type_matches > 1 && ThrowUserError('flag_type_not_unique', {value => $name});
{ value => $name }); @type_matches
@type_matches || ThrowUserError('object_does_not_exist', || ThrowUserError('object_does_not_exist',
{ class => 'Bugzilla::FlagType', name => $name }); {class => 'Bugzilla::FlagType', name => $name});
if ($new) { if ($new) {
delete $flag->{id}; delete $flag->{id};
$flag->{type_id} = $type_matches[0]->id; $flag->{type_id} = $type_matches[0]->id;
} }
else { else {
my @flag_matches = grep($name eq $_->type->name, @$current_flags); my @flag_matches = grep($name eq $_->type->name, @$current_flags);
@flag_matches > 1 && ThrowUserError('flag_not_unique', { value => $name }); @flag_matches > 1 && ThrowUserError('flag_not_unique', {value => $name});
if (@flag_matches) { if (@flag_matches) {
$flag->{id} = $flag_matches[0]->id; $flag->{id} = $flag_matches[0]->id;
} }
...@@ -149,10 +150,11 @@ sub filter_wants($$;$$) { ...@@ -149,10 +150,11 @@ sub filter_wants($$;$$) {
} }
# Mimic old behavior if no types provided # Mimic old behavior if no types provided
my %field_types = map { $_ => 1 } (ref $types ? @$types : ($types || 'default')); my %field_types
= map { $_ => 1 } (ref $types ? @$types : ($types || 'default'));
my %include = map { $_ => 1 } @{ $params->{'include_fields'} || [] }; my %include = map { $_ => 1 } @{$params->{'include_fields'} || []};
my %exclude = map { $_ => 1 } @{ $params->{'exclude_fields'} || [] }; my %exclude = map { $_ => 1 } @{$params->{'exclude_fields'} || []};
my %include_types; my %include_types;
my %exclude_types; my %exclude_types;
...@@ -189,6 +191,7 @@ sub filter_wants($$;$$) { ...@@ -189,6 +191,7 @@ sub filter_wants($$;$$) {
my $wants = 0; my $wants = 0;
if ($prefix) { if ($prefix) {
# Include the field if the parent is include (and this one is not excluded) # Include the field if the parent is include (and this one is not excluded)
$wants = 1 if $include{$prefix}; $wants = 1 if $include{$prefix};
} }
...@@ -205,6 +208,7 @@ sub filter_wants($$;$$) { ...@@ -205,6 +208,7 @@ sub filter_wants($$;$$) {
sub taint_data { sub taint_data {
my @params = @_; my @params = @_;
return if !@params; return if !@params;
# Though this is a private function, it hasn't changed since 2004 and # Though this is a private function, it hasn't changed since 2004 and
# should be safe to use, and prevents us from having to write it ourselves # should be safe to use, and prevents us from having to write it ourselves
# or require another module to do it. # or require another module to do it.
...@@ -216,6 +220,7 @@ sub _delete_bad_keys { ...@@ -216,6 +220,7 @@ sub _delete_bad_keys {
foreach my $item (@_) { foreach my $item (@_) {
next if ref $item ne 'HASH'; next if ref $item ne 'HASH';
foreach my $key (keys %$item) { foreach my $key (keys %$item) {
# Making something a hash key always untaints it, in Perl. # Making something a hash key always untaints it, in Perl.
# However, we need to validate our argument names in some way. # However, we need to validate our argument names in some way.
# We know that all hash keys passed in to the WebService wil # We know that all hash keys passed in to the WebService wil
...@@ -233,10 +238,10 @@ sub api_include_exclude { ...@@ -233,10 +238,10 @@ sub api_include_exclude {
my ($params) = @_; my ($params) = @_;
if ($params->{'include_fields'} && !ref $params->{'include_fields'}) { if ($params->{'include_fields'} && !ref $params->{'include_fields'}) {
$params->{'include_fields'} = [ split(/[\s+,]/, $params->{'include_fields'}) ]; $params->{'include_fields'} = [split(/[\s+,]/, $params->{'include_fields'})];
} }
if ($params->{'exclude_fields'} && !ref $params->{'exclude_fields'}) { if ($params->{'exclude_fields'} && !ref $params->{'exclude_fields'}) {
$params->{'exclude_fields'} = [ split(/[\s+,]/, $params->{'exclude_fields'}) ]; $params->{'exclude_fields'} = [split(/[\s+,]/, $params->{'exclude_fields'})];
} }
return $params; return $params;
...@@ -251,16 +256,17 @@ sub validate { ...@@ -251,16 +256,17 @@ sub validate {
return ($self, undef) if (defined $params and !ref $params); return ($self, undef) if (defined $params and !ref $params);
my @id_params = qw(ids comment_ids); my @id_params = qw(ids comment_ids);
# If @keys is not empty then we convert any named # If @keys is not empty then we convert any named
# parameters that have scalar values to arrayrefs # parameters that have scalar values to arrayrefs
# that match. # that match.
foreach my $key (@keys) { foreach my $key (@keys) {
if (exists $params->{$key}) { if (exists $params->{$key}) {
$params->{$key} = [ $params->{$key} ] unless ref $params->{$key}; $params->{$key} = [$params->{$key}] unless ref $params->{$key};
if (any { $key eq $_ } @id_params) { if (any { $key eq $_ } @id_params) {
my $ids = $params->{$key}; my $ids = $params->{$key};
ThrowCodeError('param_scalar_array_required', { param => $key }) ThrowCodeError('param_scalar_array_required', {param => $key})
unless ref($ids) eq 'ARRAY' && none { ref $_ } @$ids; unless ref($ids) eq 'ARRAY' && none { ref $_ } @$ids;
} }
} }
...@@ -272,7 +278,7 @@ sub validate { ...@@ -272,7 +278,7 @@ sub validate {
sub translate { sub translate {
my ($params, $mapped) = @_; my ($params, $mapped) = @_;
my %changes; my %changes;
while (my ($key,$value) = each (%$params)) { while (my ($key, $value) = each(%$params)) {
my $new_field = $mapped->{$key} || $key; my $new_field = $mapped->{$key} || $key;
$changes{$new_field} = $value; $changes{$new_field} = $value;
} }
...@@ -283,11 +289,10 @@ sub params_to_objects { ...@@ -283,11 +289,10 @@ sub params_to_objects {
my ($params, $class) = @_; my ($params, $class) = @_;
my (@objects, @objects_by_ids); my (@objects, @objects_by_ids);
@objects = map { $class->check($_) } @objects = map { $class->check($_) } @{$params->{names}} if $params->{names};
@{ $params->{names} } if $params->{names};
@objects_by_ids = map { $class->check({ id => $_ }) } @objects_by_ids = map { $class->check({id => $_}) } @{$params->{ids}}
@{ $params->{ids} } if $params->{ids}; if $params->{ids};
push(@objects, @objects_by_ids); push(@objects, @objects_by_ids);
my %seen; my %seen;
...@@ -302,8 +307,10 @@ sub fix_credentials { ...@@ -302,8 +307,10 @@ sub fix_credentials {
# Allow user to pass in authentication details in X-Headers # Allow user to pass in authentication details in X-Headers
# This allows callers to keep credentials out of GET request query-strings # This allows callers to keep credentials out of GET request query-strings
if ($cgi) { if ($cgi) {
foreach my $field (keys %{ API_AUTH_HEADERS() }) { foreach my $field (keys %{API_AUTH_HEADERS()}) {
next if exists $params->{API_AUTH_HEADERS->{$field}} || ($cgi->http($field) // '') eq ''; next
if exists $params->{API_AUTH_HEADERS->{$field}}
|| ($cgi->http($field) // '') eq '';
$params->{API_AUTH_HEADERS->{$field}} = uri_unescape($cgi->http($field)); $params->{API_AUTH_HEADERS->{$field}} = uri_unescape($cgi->http($field));
} }
} }
...@@ -315,11 +322,13 @@ sub fix_credentials { ...@@ -315,11 +322,13 @@ sub fix_credentials {
$params->{'Bugzilla_login'} = delete $params->{'login'}; $params->{'Bugzilla_login'} = delete $params->{'login'};
$params->{'Bugzilla_password'} = delete $params->{'password'}; $params->{'Bugzilla_password'} = delete $params->{'password'};
} }
# Allow user to pass api_key=12345678 as a convenience which becomes # Allow user to pass api_key=12345678 as a convenience which becomes
# "Bugzilla_api_key" which is what the auth code looks for. # "Bugzilla_api_key" which is what the auth code looks for.
if (exists $params->{api_key}) { if (exists $params->{api_key}) {
$params->{Bugzilla_api_key} = delete $params->{api_key}; $params->{Bugzilla_api_key} = delete $params->{api_key};
} }
# Allow user to pass token=12345678 as a convenience which becomes # Allow user to pass token=12345678 as a convenience which becomes
# "Bugzilla_token" which is what the auth code looks for. # "Bugzilla_token" which is what the auth code looks for.
if (exists $params->{'token'}) { if (exists $params->{'token'}) {
...@@ -327,7 +336,7 @@ sub fix_credentials { ...@@ -327,7 +336,7 @@ sub fix_credentials {
} }
# Allow extensions to modify the credential data before login # Allow extensions to modify the credential data before login
Bugzilla::Hook::process('webservice_fix_credentials', { params => $params }); Bugzilla::Hook::process('webservice_fix_credentials', {params => $params});
} }
sub datetime_format_inbound { sub datetime_format_inbound {
...@@ -335,10 +344,10 @@ sub datetime_format_inbound { ...@@ -335,10 +344,10 @@ sub datetime_format_inbound {
my $converted = datetime_from($time, Bugzilla->local_timezone); my $converted = datetime_from($time, Bugzilla->local_timezone);
if (!defined $converted) { if (!defined $converted) {
ThrowUserError('illegal_date', { date => $time }); ThrowUserError('illegal_date', {date => $time});
} }
$time = $converted->ymd() . ' ' . $converted->hms(); $time = $converted->ymd() . ' ' . $converted->hms();
return $time return $time;
} }
sub datetime_format_outbound { sub datetime_format_outbound {
...@@ -348,9 +357,11 @@ sub datetime_format_outbound { ...@@ -348,9 +357,11 @@ sub datetime_format_outbound {
my $time = $date; my $time = $date;
if (blessed($date)) { if (blessed($date)) {
# We expect this to mean we were sent a datetime object # We expect this to mean we were sent a datetime object
$time->set_time_zone('UTC'); $time->set_time_zone('UTC');
} else { }
else {
# We always send our time in UTC, for consistency. # We always send our time in UTC, for consistency.
# passed in value is likely a string, create a datetime object # passed in value is likely a string, create a datetime object
$time = datetime_from($date, 'UTC'); $time = datetime_from($date, 'UTC');
...@@ -368,27 +379,27 @@ sub as_string { defined $_[0] ? $_[0] . '' : JSON::null } ...@@ -368,27 +379,27 @@ sub as_string { defined $_[0] ? $_[0] . '' : JSON::null }
# array types # array types
sub as_int_array { [ map { as_int($_) } @{ $_[0] // [] } ] } sub as_int_array { [map { as_int($_) } @{$_[0] // []}] }
sub as_login_array { [ map { as_login($_) } @{ $_[0] // [] } ] } sub as_login_array { [map { as_login($_) } @{$_[0] // []}] }
sub as_name_array { [ map { as_string($_->name) } @{ $_[0] // [] } ] } sub as_name_array { [map { as_string($_->name) } @{$_[0] // []}] }
sub as_string_array { [ map { as_string($_) } @{ $_[0] // [] } ] } sub as_string_array { [map { as_string($_) } @{$_[0] // []}] }
# complex types # complex types
sub as_datetime { sub as_datetime {
return defined $_[0] return
? datetime_from($_[0], 'UTC')->iso8601() . 'Z' defined $_[0] ? datetime_from($_[0], 'UTC')->iso8601() . 'Z' : JSON::null;
: JSON::null;
} }
sub as_login { sub as_login {
defined $_[0] defined $_[0]
? ( Bugzilla->params->{use_email_as_login} ? email_filter($_[0]) : $_[0] . '' ) ? (Bugzilla->params->{use_email_as_login} ? email_filter($_[0]) : $_[0] . '')
: JSON::null; : JSON::null;
} }
sub as_email { sub as_email {
defined($_[0]) && Bugzilla->user->in_group('editusers') ? $_[0] . '' : JSON::null; defined($_[0])
&& Bugzilla->user->in_group('editusers') ? $_[0] . '' : JSON::null;
} }
sub as_base64 { sub as_base64 {
......
...@@ -70,6 +70,7 @@ sub server { ...@@ -70,6 +70,7 @@ sub server {
# If we do not match /<namespace>/<version>/ then we assume legacy calls # If we do not match /<namespace>/<version>/ then we assume legacy calls
# and use the default namespace and version. # and use the default namespace and version.
if ($path_info =~ m|^/([^/]+)/(\d+\.\d+(?:\.\d+)?)/|) { if ($path_info =~ m|^/([^/]+)/(\d+\.\d+(?:\.\d+)?)/|) {
# First figure out the namespace we are accessing (core is native) # First figure out the namespace we are accessing (core is native)
$api_namespace = $1 if $path_info =~ s|^/([^/]+)||; $api_namespace = $1 if $path_info =~ s|^/([^/]+)||;
$api_namespace = $self->_check_namespace($api_namespace); $api_namespace = $self->_check_namespace($api_namespace);
...@@ -119,11 +120,13 @@ sub constants { ...@@ -119,11 +120,13 @@ sub constants {
sub response_header { sub response_header {
my ($self, $code, $result) = @_; my ($self, $code, $result) = @_;
# The HTTP body needs to be bytes (not a utf8 string) for recent # The HTTP body needs to be bytes (not a utf8 string) for recent
# versions of HTTP::Message, but JSON::RPC::Server doesn't handle this # versions of HTTP::Message, but JSON::RPC::Server doesn't handle this
# properly. $_[1] is the HTTP body content we're going to be sending. # properly. $_[1] is the HTTP body content we're going to be sending.
if (utf8::is_utf8($result)) { if (utf8::is_utf8($result)) {
utf8::encode($result); utf8::encode($result);
# Since we're going to just be sending raw bytes, we need to # Since we're going to just be sending raw bytes, we need to
# set STDOUT to not expect utf8. # set STDOUT to not expect utf8.
disable_utf8(); disable_utf8();
...@@ -149,11 +152,8 @@ sub handle_login { } ...@@ -149,11 +152,8 @@ sub handle_login { }
sub return_error { sub return_error {
my ($self, $status_code, $message, $error_code) = @_; my ($self, $status_code, $message, $error_code) = @_;
if ($status_code && $message) { if ($status_code && $message) {
$self->{_return_error} = { $self->{_return_error}
status_code => $status_code, = {status_code => $status_code, error => JSON::true, message => $message};
error => JSON::true,
message => $message
};
$self->{_return_error}->{code} = $error_code if $error_code; $self->{_return_error}->{code} = $error_code if $error_code;
} }
return $self->{_return_error}; return $self->{_return_error};
...@@ -163,11 +163,13 @@ sub callback { ...@@ -163,11 +163,13 @@ sub callback {
my ($self, $value) = @_; my ($self, $value) = @_;
if (defined $value) { if (defined $value) {
$value = trim($value); $value = trim($value);
# We don't use \w because we don't want to allow Unicode here. # We don't use \w because we don't want to allow Unicode here.
if ($value !~ /^[A-Za-z0-9_\.\[\]]+$/) { if ($value !~ /^[A-Za-z0-9_\.\[\]]+$/) {
ThrowUserError('json_rpc_invalid_callback', { callback => $value }); ThrowUserError('json_rpc_invalid_callback', {callback => $value});
} }
$self->{_callback} = $value; $self->{_callback} = $value;
# JSONP needs to be parsed by a JS parser, not by a JSON parser. # JSONP needs to be parsed by a JS parser, not by a JSON parser.
$self->content_type('text/javascript'); $self->content_type('text/javascript');
} }
...@@ -179,6 +181,7 @@ sub etag { ...@@ -179,6 +181,7 @@ sub etag {
my ($self, $data) = @_; my ($self, $data) = @_;
my $cache = Bugzilla->request_cache; my $cache = Bugzilla->request_cache;
if (defined $data) { if (defined $data) {
# Serialize the data if passed a reference # Serialize the data if passed a reference
local $Storable::canonical = 1; local $Storable::canonical = 1;
$data = freeze($data) if ref $data; $data = freeze($data) if ref $data;
...@@ -202,16 +205,12 @@ sub etag { ...@@ -202,16 +205,12 @@ sub etag {
# HACK: Allow error tag checking to work with t/012throwables.t # HACK: Allow error tag checking to work with t/012throwables.t
sub ThrowUserError { sub ThrowUserError {
my ($error, $self, $vars) = @_; my ($error, $self, $vars) = @_;
$self->load_error({ type => 'user', $self->load_error({type => 'user', error => $error, vars => $vars});
error => $error,
vars => $vars });
} }
sub ThrowCodeError { sub ThrowCodeError {
my ($error, $self, $vars) = @_; my ($error, $self, $vars) = @_;
$self->load_error({ type => 'code', $self->load_error({type => 'code', error => $error, vars => $vars});
error => $error,
vars => $vars });
} }
################### ###################
...@@ -223,12 +222,11 @@ sub _build_cgi { ...@@ -223,12 +222,11 @@ sub _build_cgi {
} }
sub _build_json { sub _build_json {
# This may seem a little backwards to set utf8(0), but what this really # This may seem a little backwards to set utf8(0), but what this really
# means is "don't convert our utf8 into byte strings, just leave it as a # means is "don't convert our utf8 into byte strings, just leave it as a
# utf8 string." # utf8 string."
return JSON->new->utf8(0) return JSON->new->utf8(0)->allow_blessed(1)->convert_blessed(1);
->allow_blessed(1)
->convert_blessed(1);
} }
sub _build_request { sub _build_request {
...@@ -243,13 +241,13 @@ sub _check_namespace { ...@@ -243,13 +241,13 @@ sub _check_namespace {
# Check if namespace matches an extension name # Check if namespace matches an extension name
my $found = 0; my $found = 0;
foreach my $extension (@{ Bugzilla->extensions }) { foreach my $extension (@{Bugzilla->extensions}) {
$found = 1 if lc($extension->NAME) eq lc($namespace); $found = 1 if lc($extension->NAME) eq lc($namespace);
} }
# Make sure we have this namespace available # Make sure we have this namespace available
if (!$found) { if (!$found) {
ThrowUserError('unknown_api_namespace', $self, ThrowUserError('unknown_api_namespace', $self, {api_namespace => $namespace});
{ api_namespace => $namespace });
return DEFAULT_API_NAMESPACE; return DEFAULT_API_NAMESPACE;
} }
...@@ -276,8 +274,7 @@ sub _check_version { ...@@ -276,8 +274,7 @@ sub _check_version {
# Make sure we actual have this version installed # Make sure we actual have this version installed
if (!-d $version_dir) { if (!-d $version_dir) {
ThrowUserError('unknown_api_version', $self, ThrowUserError('unknown_api_version', $self,
{ api_version => $old_version, {api_version => $old_version, api_namespace => $namespace});
api_namespace => $namespace });
return DEFAULT_API_VERSION; return DEFAULT_API_VERSION;
} }
...@@ -285,11 +282,9 @@ sub _check_version { ...@@ -285,11 +282,9 @@ sub _check_version {
# the Core API it was written for. # the Core API it was written for.
if (lc($namespace) ne 'core') { if (lc($namespace) ne 'core') {
my $core_api_version; my $core_api_version;
foreach my $extension (@{ Bugzilla->extensions }) { foreach my $extension (@{Bugzilla->extensions}) {
next if lc($extension->NAME) ne lc($namespace); next if lc($extension->NAME) ne lc($namespace);
if ($extension->API_VERSION_MAP if ($extension->API_VERSION_MAP && $extension->API_VERSION_MAP->{$version}) {
&& $extension->API_VERSION_MAP->{$version})
{
$self->api_ext_version($version); $self->api_ext_version($version);
$version = $extension->API_VERSION_MAP->{$version}; $version = $extension->API_VERSION_MAP->{$version};
} }
...@@ -302,12 +297,13 @@ sub _check_version { ...@@ -302,12 +297,13 @@ sub _check_version {
sub _best_content_type { sub _best_content_type {
my ($self, @types) = @_; my ($self, @types) = @_;
my @accept_types = $self->_get_content_prefs(); my @accept_types = $self->_get_content_prefs();
# Return the types as-is if no accept header sent, since sorting will be a no-op. # Return the types as-is if no accept header sent, since sorting will be a no-op.
if (!@accept_types) { if (!@accept_types) {
return $types[0]; return $types[0];
} }
my $score = sub { $self->_score_type(shift, @accept_types) }; my $score = sub { $self->_score_type(shift, @accept_types) };
my @scored_types = sort {$score->($b) <=> $score->($a)} @types; my @scored_types = sort { $score->($b) <=> $score->($a) } @types;
return $scored_types[0] || '*/*'; return $scored_types[0] || '*/*';
} }
...@@ -333,18 +329,19 @@ sub _get_content_prefs { ...@@ -333,18 +329,19 @@ sub _get_content_prefs {
my ($weight) = ($accept_type =~ /q=(\d\.\d+|\d+)/); my ($weight) = ($accept_type =~ /q=(\d\.\d+|\d+)/);
my ($name) = ($accept_type =~ m#(\S+/[^;]+)#); my ($name) = ($accept_type =~ m#(\S+/[^;]+)#);
next unless $name; next unless $name;
push @prefs, { name => $name, order => $order++}; push @prefs, {name => $name, order => $order++};
if (defined $weight) { if (defined $weight) {
$prefs[-1]->{score} = $weight; $prefs[-1]->{score} = $weight;
} else { }
else {
$prefs[-1]->{score} = $default_weight; $prefs[-1]->{score} = $default_weight;
$default_weight -= 0.001; $default_weight -= 0.001;
} }
} }
# Sort the types by score, subscore by order, and pull out just the name # Sort the types by score, subscore by order, and pull out just the name
@prefs = map {$_->{name}} sort {$b->{score} <=> $a->{score} || @prefs = map { $_->{name} }
$a->{order} <=> $b->{order}} @prefs; sort { $b->{score} <=> $a->{score} || $a->{order} <=> $b->{order} } @prefs;
return @prefs; return @prefs;
} }
......
...@@ -35,6 +35,7 @@ sub process_diff { ...@@ -35,6 +35,7 @@ sub process_diff {
if ($format eq 'raw') { if ($format eq 'raw') {
require PatchReader::DiffPrinter::raw; require PatchReader::DiffPrinter::raw;
$reader->sends_data_to(new PatchReader::DiffPrinter::raw()); $reader->sends_data_to(new PatchReader::DiffPrinter::raw());
# Actually print out the patch. # Actually print out the patch.
print $cgi->header(-type => 'text/plain'); print $cgi->header(-type => 'text/plain');
disable_utf8(); disable_utf8();
...@@ -43,11 +44,14 @@ sub process_diff { ...@@ -43,11 +44,14 @@ sub process_diff {
else { else {
my @other_patches = (); my @other_patches = ();
if ($lc->{interdiffbin} && $lc->{diffpath}) { if ($lc->{interdiffbin} && $lc->{diffpath}) {
# Get the list of attachments that the user can view in this bug. # Get the list of attachments that the user can view in this bug.
my @attachments = my @attachments
@{Bugzilla::Attachment->get_attachments_by_bug($attachment->bug)}; = @{Bugzilla::Attachment->get_attachments_by_bug($attachment->bug)};
# Extract patches only. # Extract patches only.
@attachments = grep {$_->ispatch == 1} @attachments; @attachments = grep { $_->ispatch == 1 } @attachments;
# We want them sorted from newer to older. # We want them sorted from newer to older.
@attachments = sort { $b->id <=> $a->id } @attachments; @attachments = sort { $b->id <=> $a->id } @attachments;
...@@ -59,9 +63,14 @@ sub process_diff { ...@@ -59,9 +63,14 @@ sub process_diff {
$select_next_patch = 1; $select_next_patch = 1;
} }
else { else {
push(@other_patches, { 'id' => $attach->id, push(
@other_patches,
{
'id' => $attach->id,
'desc' => $attach->description, 'desc' => $attach->description,
'selected' => $select_next_patch }); 'selected' => $select_next_patch
}
);
$select_next_patch = 0; $select_next_patch = 0;
} }
} }
...@@ -73,6 +82,7 @@ sub process_diff { ...@@ -73,6 +82,7 @@ sub process_diff {
$vars->{'other_patches'} = \@other_patches; $vars->{'other_patches'} = \@other_patches;
setup_template_patch_reader($reader, $vars); setup_template_patch_reader($reader, $vars);
# The patch is going to be displayed in a HTML page, so we have # The patch is going to be displayed in a HTML page, so we have
# to encode attachment data as utf8. # to encode attachment data as utf8.
$attachment->data; # Populate ->{data} $attachment->data; # Populate ->{data}
...@@ -99,6 +109,7 @@ sub process_interdiff { ...@@ -99,6 +109,7 @@ sub process_interdiff {
# Get old patch data. # Get old patch data.
my ($old_filename, $old_file_list) = get_unified_diff($old_attachment, $format); my ($old_filename, $old_file_list) = get_unified_diff($old_attachment, $format);
# Get new patch data. # Get new patch data.
my ($new_filename, $new_file_list) = get_unified_diff($new_attachment, $format); my ($new_filename, $new_file_list) = get_unified_diff($new_attachment, $format);
...@@ -116,21 +127,23 @@ sub process_interdiff { ...@@ -116,21 +127,23 @@ sub process_interdiff {
require Apache2::RequestUtil; require Apache2::RequestUtil;
require Apache2::SubProcess; require Apache2::SubProcess;
my $request = Apache2::RequestUtil->request; my $request = Apache2::RequestUtil->request;
(undef, $interdiff_stdout, $interdiff_stderr) = $request->spawn_proc_prog( (undef, $interdiff_stdout, $interdiff_stderr)
$lc->{interdiffbin}, [$old_filename, $new_filename] = $request->spawn_proc_prog($lc->{interdiffbin},
); [$old_filename, $new_filename]);
$use_select = !PERLIO_IS_ENABLED; $use_select = !PERLIO_IS_ENABLED;
} else { }
else {
$interdiff_stderr = gensym; $interdiff_stderr = gensym;
$pid = open3(gensym, $interdiff_stdout, $interdiff_stderr, $pid = open3(gensym, $interdiff_stdout, $interdiff_stderr, $lc->{interdiffbin},
$lc->{interdiffbin}, $old_filename, $new_filename); $old_filename, $new_filename);
$use_select = 1; $use_select = 1;
} }
if ($format ne 'raw') { if ($format ne 'raw') {
binmode $interdiff_stdout, ':utf8'; binmode $interdiff_stdout, ':utf8';
binmode $interdiff_stderr, ':utf8'; binmode $interdiff_stderr, ':utf8';
} else { }
else {
binmode $interdiff_stdout; binmode $interdiff_stdout;
binmode $interdiff_stderr; binmode $interdiff_stderr;
} }
...@@ -147,14 +160,16 @@ sub process_interdiff { ...@@ -147,14 +160,16 @@ sub process_interdiff {
} }
if ($handle == $interdiff_stdout) { if ($handle == $interdiff_stdout) {
$stdout .= $line; $stdout .= $line;
} else { }
else {
$stderr .= $line; $stderr .= $line;
} }
} }
} }
waitpid($pid, 0) if $pid; waitpid($pid, 0) if $pid;
} else { }
else {
local $/ = undef; local $/ = undef;
$stdout = <$interdiff_stdout>; $stdout = <$interdiff_stdout>;
$stdout //= ''; $stdout //= '';
...@@ -162,8 +177,7 @@ sub process_interdiff { ...@@ -162,8 +177,7 @@ sub process_interdiff {
$stderr //= ''; $stderr //= '';
} }
close($interdiff_stdout), close($interdiff_stdout), close($interdiff_stderr);
close($interdiff_stderr);
# Tidy up # Tidy up
unlink($old_filename) or warn "Could not unlink $old_filename: $!"; unlink($old_filename) or warn "Could not unlink $old_filename: $!";
...@@ -182,6 +196,7 @@ sub process_interdiff { ...@@ -182,6 +196,7 @@ sub process_interdiff {
if ($format eq 'raw') { if ($format eq 'raw') {
require PatchReader::DiffPrinter::raw; require PatchReader::DiffPrinter::raw;
$reader->sends_data_to(new PatchReader::DiffPrinter::raw()); $reader->sends_data_to(new PatchReader::DiffPrinter::raw());
# Actually print out the patch. # Actually print out the patch.
print $cgi->header(-type => 'text/plain'); print $cgi->header(-type => 'text/plain');
disable_utf8(); disable_utf8();
...@@ -196,8 +211,8 @@ sub process_interdiff { ...@@ -196,8 +211,8 @@ sub process_interdiff {
setup_template_patch_reader($reader, $vars); setup_template_patch_reader($reader, $vars);
} }
$reader->iterate_string('interdiff #' . $old_attachment->id . $reader->iterate_string(
' #' . $new_attachment->id, $stdout); 'interdiff #' . $old_attachment->id . ' #' . $new_attachment->id, $stdout);
} }
###################### ######################
...@@ -214,7 +229,7 @@ sub get_unified_diff { ...@@ -214,7 +229,7 @@ sub get_unified_diff {
require File::Temp; require File::Temp;
$attachment->ispatch $attachment->ispatch
|| ThrowCodeError('must_be_patch', { 'attach_id' => $attachment->id }); || ThrowCodeError('must_be_patch', {'attach_id' => $attachment->id});
# Reads in the patch, converting to unified diff in a temp file. # Reads in the patch, converting to unified diff in a temp file.
my $reader = new PatchReader::Raw; my $reader = new PatchReader::Raw;
...@@ -228,6 +243,7 @@ sub get_unified_diff { ...@@ -228,6 +243,7 @@ sub get_unified_diff {
# Prints out to temporary file. # Prints out to temporary file.
my ($fh, $filename) = File::Temp::tempfile(); my ($fh, $filename) = File::Temp::tempfile();
if ($format ne 'raw') { if ($format ne 'raw') {
# The HTML page will be displayed with the UTF-8 encoding. # The HTML page will be displayed with the UTF-8 encoding.
binmode $fh, ':utf8'; binmode $fh, ':utf8';
} }
...@@ -247,15 +263,14 @@ sub warn_if_interdiff_might_fail { ...@@ -247,15 +263,14 @@ sub warn_if_interdiff_might_fail {
# Verify that the list of files diffed is the same. # Verify that the list of files diffed is the same.
my @old_files = sort keys %{$old_file_list}; my @old_files = sort keys %{$old_file_list};
my @new_files = sort keys %{$new_file_list}; my @new_files = sort keys %{$new_file_list};
if (@old_files != @new_files if (@old_files != @new_files || join(' ', @old_files) ne join(' ', @new_files))
|| join(' ', @old_files) ne join(' ', @new_files))
{ {
return 'interdiff1'; return 'interdiff1';
} }
# Verify that the revisions in the files are the same. # Verify that the revisions in the files are the same.
foreach my $file (keys %{$old_file_list}) { foreach my $file (keys %{$old_file_list}) {
if (exists $old_file_list->{$file}{old_revision} if ( exists $old_file_list->{$file}{old_revision}
&& exists $new_file_list->{$file}{old_revision} && exists $new_file_list->{$file}{old_revision}
&& $old_file_list->{$file}{old_revision} ne && $old_file_list->{$file}{old_revision} ne
$new_file_list->{$file}{old_revision}) $new_file_list->{$file}{old_revision})
...@@ -286,11 +301,11 @@ sub setup_template_patch_reader { ...@@ -286,11 +301,11 @@ sub setup_template_patch_reader {
# Print everything out. # Print everything out.
print $cgi->header(-type => 'text/html'); print $cgi->header(-type => 'text/html');
$last_reader->sends_data_to(new PatchReader::DiffPrinter::template($template, $last_reader->sends_data_to(new PatchReader::DiffPrinter::template(
'attachment/diff-header.html.tmpl', $template, 'attachment/diff-header.html.tmpl',
'attachment/diff-file.html.tmpl', 'attachment/diff-file.html.tmpl', 'attachment/diff-footer.html.tmpl',
'attachment/diff-footer.html.tmpl', $vars
$vars)); ));
} }
1; 1;
......
...@@ -37,6 +37,7 @@ sub new { ...@@ -37,6 +37,7 @@ sub new {
$self->{_info_getter} = new Bugzilla::Auth::Login::Stack($params->{Login}); $self->{_info_getter} = new Bugzilla::Auth::Login::Stack($params->{Login});
$self->{_verifier} = new Bugzilla::Auth::Verify::Stack($params->{Verify}); $self->{_verifier} = new Bugzilla::Auth::Verify::Stack($params->{Verify});
# If we ever have any other login persistence methods besides cookies, # If we ever have any other login persistence methods besides cookies,
# this could become more configurable. # this could become more configurable.
$self->{_persister} = new Bugzilla::Auth::Persist::Cookie(); $self->{_persister} = new Bugzilla::Auth::Persist::Cookie();
...@@ -60,8 +61,8 @@ sub login { ...@@ -60,8 +61,8 @@ sub login {
if ($login_info->{failure}) { if ($login_info->{failure}) {
return $self->_handle_login_result($login_info, $type); return $self->_handle_login_result($login_info, $type);
} }
$login_info = $login_info
$self->{_verifier}->{successful}->create_or_update_user($login_info); = $self->{_verifier}->{successful}->create_or_update_user($login_info);
} }
else { else {
$login_info = $self->{_verifier}->create_or_update_user($login_info); $login_info = $self->{_verifier}->create_or_update_user($login_info);
...@@ -74,8 +75,8 @@ sub login { ...@@ -74,8 +75,8 @@ sub login {
# Make sure the user isn't disabled. # Make sure the user isn't disabled.
my $user = $login_info->{user}; my $user = $login_info->{user};
if (!$user->is_enabled) { if (!$user->is_enabled) {
return $self->_handle_login_result({ failure => AUTH_DISABLED, return $self->_handle_login_result({failure => AUTH_DISABLED, user => $user},
user => $user }, $type); $type);
} }
$user->set_authorizer($self); $user->set_authorizer($self);
...@@ -89,8 +90,7 @@ sub can_change_password { ...@@ -89,8 +90,7 @@ sub can_change_password {
my $getter = $self->{_info_getter}->{successful}; my $getter = $self->{_info_getter}->{successful};
$getter = $self->{_info_getter} $getter = $self->{_info_getter}
if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie')); if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
return $verifier->can_change_password && return $verifier->can_change_password && $getter->user_can_create_account;
$getter->user_can_create_account;
} }
sub can_login { sub can_login {
...@@ -104,6 +104,7 @@ sub can_login { ...@@ -104,6 +104,7 @@ sub can_login {
sub can_logout { sub can_logout {
my ($self) = @_; my ($self) = @_;
my $getter = $self->{_info_getter}->{successful}; my $getter = $self->{_info_getter}->{successful};
# If there's no successful getter, we're not logged in, so of # If there's no successful getter, we're not logged in, so of
# course we can't log out! # course we can't log out!
return 0 unless $getter; return 0 unless $getter;
...@@ -126,8 +127,7 @@ sub user_can_create_account { ...@@ -126,8 +127,7 @@ sub user_can_create_account {
my $getter = $self->{_info_getter}->{successful}; my $getter = $self->{_info_getter}->{successful};
$getter = $self->{_info_getter} $getter = $self->{_info_getter}
if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie')); if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
return $verifier->user_can_create_account return $verifier->user_can_create_account && $getter->user_can_create_account;
&& $getter->user_can_create_account;
} }
sub extern_id_used { sub extern_id_used {
...@@ -152,6 +152,7 @@ sub _handle_login_result { ...@@ -152,6 +152,7 @@ sub _handle_login_result {
my $fail_code = $result->{failure}; my $fail_code = $result->{failure};
if (!$fail_code) { if (!$fail_code) {
# We don't persist logins over GET requests in the WebService, # We don't persist logins over GET requests in the WebService,
# because the persistence information can't be re-used again. # because the persistence information can't be re-used again.
# (See Bugzilla::WebService::Server::JSONRPC for more info.) # (See Bugzilla::WebService::Server::JSONRPC for more info.)
...@@ -170,27 +171,28 @@ sub _handle_login_result { ...@@ -170,27 +171,28 @@ sub _handle_login_result {
} }
} }
elsif ($fail_code == AUTH_NODATA) { elsif ($fail_code == AUTH_NODATA) {
$self->{_info_getter}->fail_nodata($self) $self->{_info_getter}->fail_nodata($self) if $login_type == LOGIN_REQUIRED;
if $login_type == LOGIN_REQUIRED;
# If we're not LOGIN_REQUIRED, we just return the default user. # If we're not LOGIN_REQUIRED, we just return the default user.
$user = Bugzilla->user; $user = Bugzilla->user;
} }
# The username/password may be wrong # The username/password may be wrong
# Don't let the user know whether the username exists or whether # Don't let the user know whether the username exists or whether
# the password was just wrong. (This makes it harder for a cracker # the password was just wrong. (This makes it harder for a cracker
# to find account names by brute force) # to find account names by brute force)
elsif ($fail_code == AUTH_LOGINFAILED or $fail_code == AUTH_NO_SUCH_USER) { elsif ($fail_code == AUTH_LOGINFAILED or $fail_code == AUTH_NO_SUCH_USER) {
my $remaining_attempts = MAX_LOGIN_ATTEMPTS my $remaining_attempts = MAX_LOGIN_ATTEMPTS - ($result->{failure_count} || 0);
- ($result->{failure_count} || 0); ThrowUserError("invalid_login_or_password", {remaining => $remaining_attempts});
ThrowUserError("invalid_login_or_password",
{ remaining => $remaining_attempts });
} }
# The account may be disabled # The account may be disabled
elsif ($fail_code == AUTH_DISABLED) { elsif ($fail_code == AUTH_DISABLED) {
$self->{_persister}->logout(); $self->{_persister}->logout();
# XXX This is NOT a good way to do this, architecturally. # XXX This is NOT a good way to do this, architecturally.
$self->{_persister}->clear_browser_cookies(); $self->{_persister}->clear_browser_cookies();
# and throw a user error # and throw a user error
ThrowUserError("account_disabled", ThrowUserError("account_disabled",
{'disabled_reason' => $result->{user}->disabledtext}); {'disabled_reason' => $result->{user}->disabledtext});
...@@ -202,25 +204,27 @@ sub _handle_login_result { ...@@ -202,25 +204,27 @@ sub _handle_login_result {
# determined by the 5th-from-last login failure (or more/less than # determined by the 5th-from-last login failure (or more/less than
# 5th, if MAX_LOGIN_ATTEMPTS is not 5). # 5th, if MAX_LOGIN_ATTEMPTS is not 5).
my $determiner = $attempts->[scalar(@$attempts) - MAX_LOGIN_ATTEMPTS]; my $determiner = $attempts->[scalar(@$attempts) - MAX_LOGIN_ATTEMPTS];
my $unlock_at = datetime_from($determiner->{login_time}, my $unlock_at
Bugzilla->local_timezone); = datetime_from($determiner->{login_time}, Bugzilla->local_timezone);
$unlock_at->add(minutes => LOGIN_LOCKOUT_INTERVAL); $unlock_at->add(minutes => LOGIN_LOCKOUT_INTERVAL);
# If we were *just* locked out, notify the maintainer about the # If we were *just* locked out, notify the maintainer about the
# lockout. # lockout.
if ($result->{just_locked_out}) { if ($result->{just_locked_out}) {
# We're sending to the maintainer, who may be not a Bugzilla # We're sending to the maintainer, who may be not a Bugzilla
# account, but just an email address. So we use the # account, but just an email address. So we use the
# installation's default language for sending the email. # installation's default language for sending the email.
my $default_settings = Bugzilla::User::Setting::get_defaults(); my $default_settings = Bugzilla::User::Setting::get_defaults();
my $template = Bugzilla->template_inner( my $template
$default_settings->{lang}->{default_value}); = Bugzilla->template_inner($default_settings->{lang}->{default_value});
my $address = $attempts->[0]->{ip_addr}; my $address = $attempts->[0]->{ip_addr};
# Note: inet_aton will only resolve IPv4 addresses. # Note: inet_aton will only resolve IPv4 addresses.
# For IPv6 we'll need to use inet_pton which requires Perl 5.12. # For IPv6 we'll need to use inet_pton which requires Perl 5.12.
my $n = inet_aton($address); my $n = inet_aton($address);
if ($n) { if ($n) {
$address = gethostbyaddr($n, AF_INET) . " ($address)" $address = gethostbyaddr($n, AF_INET) . " ($address)";
} }
my $vars = { my $vars = {
locked_user => $user, locked_user => $user,
...@@ -236,11 +240,12 @@ sub _handle_login_result { ...@@ -236,11 +240,12 @@ sub _handle_login_result {
$unlock_at->set_time_zone($user->timezone); $unlock_at->set_time_zone($user->timezone);
ThrowUserError('account_locked', ThrowUserError('account_locked',
{ ip_addr => $determiner->{ip_addr}, unlock_at => $unlock_at }); {ip_addr => $determiner->{ip_addr}, unlock_at => $unlock_at});
} }
# If we get here, then we've run out of options, which shouldn't happen. # If we get here, then we've run out of options, which shouldn't happen.
else { else {
ThrowCodeError("authres_unhandled", { value => $fail_code }); ThrowCodeError("authres_unhandled", {value => $fail_code});
} }
return $user; return $user;
......
...@@ -32,12 +32,13 @@ sub get_login_info { ...@@ -32,12 +32,13 @@ sub get_login_info {
my $api_key_text = trim(delete $params->{'Bugzilla_api_key'}); my $api_key_text = trim(delete $params->{'Bugzilla_api_key'});
if (!i_am_webservice() || !$api_key_text) { if (!i_am_webservice() || !$api_key_text) {
return { failure => AUTH_NODATA }; return {failure => AUTH_NODATA};
} }
my $api_key = Bugzilla::User::APIKey->new({ name => $api_key_text }); my $api_key = Bugzilla::User::APIKey->new({name => $api_key_text});
if (!$api_key or $api_key->api_key ne $api_key_text) { if (!$api_key or $api_key->api_key ne $api_key_text) {
# The second part checks the correct capitalisation. Silly MySQL # The second part checks the correct capitalisation. Silly MySQL
ThrowUserError("api_key_not_valid"); ThrowUserError("api_key_not_valid");
} }
...@@ -47,7 +48,7 @@ sub get_login_info { ...@@ -47,7 +48,7 @@ sub get_login_info {
$api_key->update_last_used(); $api_key->update_last_used();
return { user_id => $api_key->user_id }; return {user_id => $api_key->user_id};
} }
1; 1;
...@@ -27,24 +27,29 @@ sub get_login_info { ...@@ -27,24 +27,29 @@ sub get_login_info {
my $login = trim(delete $params->{'Bugzilla_login'}); my $login = trim(delete $params->{'Bugzilla_login'});
my $password = delete $params->{'Bugzilla_password'}; my $password = delete $params->{'Bugzilla_password'};
# The token must match the cookie to authenticate the request. # The token must match the cookie to authenticate the request.
my $login_token = delete $params->{'Bugzilla_login_token'}; my $login_token = delete $params->{'Bugzilla_login_token'};
my $login_cookie = $cgi->cookie('Bugzilla_login_request_cookie'); my $login_cookie = $cgi->cookie('Bugzilla_login_request_cookie');
my $valid = 0; my $valid = 0;
# If the web browser accepts cookies, use them. # If the web browser accepts cookies, use them.
if ($login_token && $login_cookie) { if ($login_token && $login_cookie) {
my ($time, undef) = split(/-/, $login_token); my ($time, undef) = split(/-/, $login_token);
# Regenerate the token based on the information we have. # Regenerate the token based on the information we have.
my $expected_token = issue_hash_token(['login_request', $login_cookie], $time); my $expected_token = issue_hash_token(['login_request', $login_cookie], $time);
$valid = 1 if $expected_token eq $login_token; $valid = 1 if $expected_token eq $login_token;
$cgi->remove_cookie('Bugzilla_login_request_cookie'); $cgi->remove_cookie('Bugzilla_login_request_cookie');
} }
# WebServices and other local scripts can bypass this check. # WebServices and other local scripts can bypass this check.
# This is safe because we won't store a login cookie in this case. # This is safe because we won't store a login cookie in this case.
elsif (Bugzilla->usage_mode != USAGE_MODE_BROWSER) { elsif (Bugzilla->usage_mode != USAGE_MODE_BROWSER) {
$valid = 1; $valid = 1;
} }
# Else falls back to the Referer header and accept local URLs. # Else falls back to the Referer header and accept local URLs.
# Attachments are served from a separate host (ideally), and so # Attachments are served from a separate host (ideally), and so
# an evil attachment cannot abuse this check with a redirect. # an evil attachment cannot abuse this check with a redirect.
...@@ -52,18 +57,19 @@ sub get_login_info { ...@@ -52,18 +57,19 @@ sub get_login_info {
my $urlbase = correct_urlbase(); my $urlbase = correct_urlbase();
$valid = 1 if $referer =~ /^\Q$urlbase\E/; $valid = 1 if $referer =~ /^\Q$urlbase\E/;
} }
# If the web browser doesn't accept cookies and the Referer header # If the web browser doesn't accept cookies and the Referer header
# is missing, we have no way to make sure that the authentication # is missing, we have no way to make sure that the authentication
# request comes from the user. # request comes from the user.
elsif ($login && $password) { elsif ($login && $password) {
ThrowUserError('auth_untrusted_request', { login => $login }); ThrowUserError('auth_untrusted_request', {login => $login});
} }
if (!defined($login) || !defined($password) || !$valid) { if (!defined($login) || !defined($password) || !$valid) {
return { failure => AUTH_NODATA }; return {failure => AUTH_NODATA};
} }
return { username => $login, password => $password }; return {username => $login, password => $password};
} }
sub fail_nodata { sub fail_nodata {
...@@ -77,7 +83,7 @@ sub fail_nodata { ...@@ -77,7 +83,7 @@ sub fail_nodata {
print $cgi->header(); print $cgi->header();
$template->process("account/auth/login.html.tmpl", $template->process("account/auth/login.html.tmpl",
{ 'target' => $cgi->url(-relative=>1) }) {'target' => $cgi->url(-relative => 1)})
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
exit; exit;
} }
......
...@@ -42,12 +42,12 @@ sub get_login_info { ...@@ -42,12 +42,12 @@ sub get_login_info {
# If cookies cannot be found, this could mean that they haven't # If cookies cannot be found, this could mean that they haven't
# been made available yet. In this case, look at Bugzilla_cookie_list. # been made available yet. In this case, look at Bugzilla_cookie_list.
unless ($login_cookie) { unless ($login_cookie) {
my $cookie = first {$_->name eq 'Bugzilla_logincookie'} my $cookie = first { $_->name eq 'Bugzilla_logincookie' }
@{$cgi->{'Bugzilla_cookie_list'}}; @{$cgi->{'Bugzilla_cookie_list'}};
$login_cookie = $cookie->value if $cookie; $login_cookie = $cookie->value if $cookie;
} }
unless ($user_id) { unless ($user_id) {
my $cookie = first {$_->name eq 'Bugzilla_login'} my $cookie = first { $_->name eq 'Bugzilla_login' }
@{$cgi->{'Bugzilla_cookie_list'}}; @{$cgi->{'Bugzilla_cookie_list'}};
$user_id = $cookie->value if $cookie; $user_id = $cookie->value if $cookie;
} }
...@@ -59,14 +59,15 @@ sub get_login_info { ...@@ -59,14 +59,15 @@ sub get_login_info {
my $api_token = Bugzilla->input_params->{Bugzilla_api_token}; my $api_token = Bugzilla->input_params->{Bugzilla_api_token};
my ($token_user_id, undef, undef, $token_type) my ($token_user_id, undef, undef, $token_type)
= Bugzilla::Token::GetTokenData($api_token); = Bugzilla::Token::GetTokenData($api_token);
if (!defined $token_type if ( !defined $token_type
|| $token_type ne 'api_token' || $token_type ne 'api_token'
|| $user_id != $token_user_id) || $user_id != $token_user_id)
{ {
ThrowUserError('auth_invalid_token', { token => $api_token }); ThrowUserError('auth_invalid_token', {token => $api_token});
} }
} }
elsif ($login_cookie && Bugzilla->usage_mode == USAGE_MODE_REST) { elsif ($login_cookie && Bugzilla->usage_mode == USAGE_MODE_REST) {
# REST requires an api-token when using cookie authentication # REST requires an api-token when using cookie authentication
# fall back to a non-authenticated request # fall back to a non-authenticated request
$login_cookie = ''; $login_cookie = '';
...@@ -84,29 +85,34 @@ sub get_login_info { ...@@ -84,29 +85,34 @@ sub get_login_info {
my $ip_addr = remote_ip(); my $ip_addr = remote_ip();
if ($login_cookie && $user_id) { if ($login_cookie && $user_id) {
# Anything goes for these params - they're just strings which # Anything goes for these params - they're just strings which
# we're going to verify against the db # we're going to verify against the db
trick_taint($ip_addr); trick_taint($ip_addr);
trick_taint($login_cookie); trick_taint($login_cookie);
detaint_natural($user_id); detaint_natural($user_id);
my $db_cookie = my $db_cookie = $dbh->selectrow_array(
$dbh->selectrow_array('SELECT cookie 'SELECT cookie
FROM logincookies FROM logincookies
WHERE cookie = ? WHERE cookie = ?
AND userid = ? AND userid = ?
AND (ipaddr = ? OR ipaddr IS NULL)', AND (ipaddr = ? OR ipaddr IS NULL)', undef,
undef, ($login_cookie, $user_id, $ip_addr)); ($login_cookie, $user_id, $ip_addr)
);
# If the cookie or token is valid, return a valid username. # If the cookie or token is valid, return a valid username.
# If they were not valid and we are using a webservice, then # If they were not valid and we are using a webservice, then
# throw an error notifying the client. # throw an error notifying the client.
if (defined $db_cookie && $login_cookie eq $db_cookie) { if (defined $db_cookie && $login_cookie eq $db_cookie) {
# If we logged in successfully, then update the lastused # If we logged in successfully, then update the lastused
# time on the login cookie # time on the login cookie
$dbh->do("UPDATE logincookies SET lastused = NOW() $dbh->do(
WHERE cookie = ?", undef, $login_cookie); "UPDATE logincookies SET lastused = NOW()
return { user_id => $user_id }; WHERE cookie = ?", undef, $login_cookie
);
return {user_id => $user_id};
} }
elsif (i_am_webservice()) { elsif (i_am_webservice()) {
ThrowUserError('invalid_cookies_or_token'); ThrowUserError('invalid_cookies_or_token');
...@@ -118,7 +124,7 @@ sub get_login_info { ...@@ -118,7 +124,7 @@ sub get_login_info {
# want to ever return AUTH_LOGINFAILED, because we don't want Bugzilla to # want to ever return AUTH_LOGINFAILED, because we don't want Bugzilla to
# actually throw an error when it gets a bad cookie or token. It should just # actually throw an error when it gets a bad cookie or token. It should just
# look like there was no cookie or token to begin with. # look like there was no cookie or token to begin with.
return { failure => AUTH_NODATA }; return {failure => AUTH_NODATA};
} }
sub login_token { sub login_token {
...@@ -141,10 +147,8 @@ sub login_token { ...@@ -141,10 +147,8 @@ sub login_token {
return $self->{'_login_token'} = undef; return $self->{'_login_token'} = undef;
} }
return $self->{'_login_token'} = { return $self->{'_login_token'}
user_id => $user_id, = {user_id => $user_id, login_token => $login_token};
login_token => $login_token
};
} }
1; 1;
...@@ -30,10 +30,13 @@ sub get_login_info { ...@@ -30,10 +30,13 @@ sub get_login_info {
my $env_email = $ENV{Bugzilla->params->{"auth_env_email"}} || ''; my $env_email = $ENV{Bugzilla->params->{"auth_env_email"}} || '';
my $env_realname = $ENV{Bugzilla->params->{"auth_env_realname"}} || ''; my $env_realname = $ENV{Bugzilla->params->{"auth_env_realname"}} || '';
return { failure => AUTH_NODATA } if !$env_email; return {failure => AUTH_NODATA} if !$env_email;
return { username => $env_email, extern_id => $env_id, return {
realname => $env_realname }; username => $env_email,
extern_id => $env_id,
realname => $env_realname
};
} }
sub fail_nodata { sub fail_nodata {
......
...@@ -27,7 +27,7 @@ sub new { ...@@ -27,7 +27,7 @@ sub new {
my $list = shift; my $list = shift;
my %methods = map { $_ => "Bugzilla/Auth/Login/$_.pm" } split(',', $list); my %methods = map { $_ => "Bugzilla/Auth/Login/$_.pm" } split(',', $list);
lock_keys(%methods); lock_keys(%methods);
Bugzilla::Hook::process('auth_login_methods', { modules => \%methods }); Bugzilla::Hook::process('auth_login_methods', {modules => \%methods});
$self->{_stack} = []; $self->{_stack} = [];
foreach my $login_method (split(',', $list)) { foreach my $login_method (split(',', $list)) {
...@@ -44,6 +44,7 @@ sub get_login_info { ...@@ -44,6 +44,7 @@ sub get_login_info {
my $self = shift; my $self = shift;
my $result; my $result;
foreach my $object (@{$self->{_stack}}) { foreach my $object (@{$self->{_stack}}) {
# See Bugzilla::WebService::Server::JSONRPC for where and why # See Bugzilla::WebService::Server::JSONRPC for where and why
# auth_no_automatic_login is used. # auth_no_automatic_login is used.
if (Bugzilla->request_cache->{auth_no_automatic_login}) { if (Bugzilla->request_cache->{auth_no_automatic_login}) {
...@@ -53,8 +54,9 @@ sub get_login_info { ...@@ -53,8 +54,9 @@ sub get_login_info {
$self->{successful} = $object; $self->{successful} = $object;
# We only carry on down the stack if this method denied all knowledge. # We only carry on down the stack if this method denied all knowledge.
last unless ($result->{failure} last
&& ($result->{failure} eq AUTH_NODATA unless ($result->{failure}
&& ( $result->{failure} eq AUTH_NODATA
|| $result->{failure} eq AUTH_NO_SUCH_USER)); || $result->{failure} eq AUTH_NO_SUCH_USER));
# If none of the methods succeed, it's undef. # If none of the methods succeed, it's undef.
...@@ -65,9 +67,11 @@ sub get_login_info { ...@@ -65,9 +67,11 @@ sub get_login_info {
sub fail_nodata { sub fail_nodata {
my $self = shift; my $self = shift;
# We fail from the bottom of the stack. # We fail from the bottom of the stack.
my @reverse_stack = reverse @{$self->{_stack}}; my @reverse_stack = reverse @{$self->{_stack}};
foreach my $object (@reverse_stack) { foreach my $object (@reverse_stack) {
# We pick the first object that actually has the method # We pick the first object that actually has the method
# implemented. # implemented.
if ($object->can('fail_nodata')) { if ($object->can('fail_nodata')) {
...@@ -78,6 +82,7 @@ sub fail_nodata { ...@@ -78,6 +82,7 @@ sub fail_nodata {
sub can_login { sub can_login {
my ($self) = @_; my ($self) = @_;
# We return true if any method can log in. # We return true if any method can log in.
foreach my $object (@{$self->{_stack}}) { foreach my $object (@{$self->{_stack}}) {
return 1 if $object->can_login; return 1 if $object->can_login;
...@@ -87,6 +92,7 @@ sub can_login { ...@@ -87,6 +92,7 @@ sub can_login {
sub user_can_create_account { sub user_can_create_account {
my ($self) = @_; my ($self) = @_;
# We return true if any method allows users to create accounts. # We return true if any method allows users to create accounts.
foreach my $object (@{$self->{_stack}}) { foreach my $object (@{$self->{_stack}}) {
return 1 if $object->user_can_create_account; return 1 if $object->user_can_create_account;
...@@ -96,7 +102,7 @@ sub user_can_create_account { ...@@ -96,7 +102,7 @@ sub user_can_create_account {
sub extern_id_used { sub extern_id_used {
my ($self) = @_; my ($self) = @_;
return any { $_->extern_id_used } @{ $self->{_stack} }; return any { $_->extern_id_used } @{$self->{_stack}};
} }
1; 1;
...@@ -34,6 +34,7 @@ sub persist_login { ...@@ -34,6 +34,7 @@ sub persist_login {
my $ip_addr; my $ip_addr;
if ($input_params->{'Bugzilla_restrictlogin'}) { if ($input_params->{'Bugzilla_restrictlogin'}) {
$ip_addr = remote_ip(); $ip_addr = remote_ip();
# The IP address is valid, at least for comparing with itself in a # The IP address is valid, at least for comparing with itself in a
# subsequent login # subsequent login
trick_taint($ip_addr); trick_taint($ip_addr);
...@@ -41,18 +42,18 @@ sub persist_login { ...@@ -41,18 +42,18 @@ sub persist_login {
$dbh->bz_start_transaction(); $dbh->bz_start_transaction();
my $login_cookie = my $login_cookie
Bugzilla::Token::GenerateUniqueToken('logincookies', 'cookie'); = Bugzilla::Token::GenerateUniqueToken('logincookies', 'cookie');
$dbh->do("INSERT INTO logincookies (cookie, userid, ipaddr, lastused) $dbh->do(
VALUES (?, ?, ?, NOW())", "INSERT INTO logincookies (cookie, userid, ipaddr, lastused)
undef, $login_cookie, $user->id, $ip_addr); VALUES (?, ?, ?, NOW())", undef, $login_cookie, $user->id, $ip_addr
);
# Issuing a new cookie is a good time to clean up the old # Issuing a new cookie is a good time to clean up the old
# cookies. # cookies.
$dbh->do("DELETE FROM logincookies WHERE lastused < " $dbh->do("DELETE FROM logincookies WHERE lastused < "
. $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', MAX_LOGINCOOKIE_AGE, 'DAY'));
MAX_LOGINCOOKIE_AGE, 'DAY'));
$dbh->bz_commit_transaction(); $dbh->bz_commit_transaction();
...@@ -65,26 +66,29 @@ sub persist_login { ...@@ -65,26 +66,29 @@ sub persist_login {
# Remember cookie only if admin has told so # Remember cookie only if admin has told so
# or admin didn't forbid it and user told to remember. # or admin didn't forbid it and user told to remember.
if ( Bugzilla->params->{'rememberlogin'} eq 'on' || if (
(Bugzilla->params->{'rememberlogin'} ne 'off' && Bugzilla->params->{'rememberlogin'} eq 'on'
$input_params->{'Bugzilla_remember'} && || ( Bugzilla->params->{'rememberlogin'} ne 'off'
$input_params->{'Bugzilla_remember'} eq 'on') ) && $input_params->{'Bugzilla_remember'}
&& $input_params->{'Bugzilla_remember'} eq 'on')
)
{ {
# Not a session cookie, so set an infinite expiry # Not a session cookie, so set an infinite expiry
$cookieargs{'-expires'} = 'Fri, 01-Jan-2038 00:00:00 GMT'; $cookieargs{'-expires'} = 'Fri, 01-Jan-2038 00:00:00 GMT';
} }
if (Bugzilla->params->{'ssl_redirect'}) { if (Bugzilla->params->{'ssl_redirect'}) {
# Make these cookies only be sent to us by the browser during # Make these cookies only be sent to us by the browser during
# HTTPS sessions, if we're using SSL. # HTTPS sessions, if we're using SSL.
$cookieargs{'-secure'} = 1; $cookieargs{'-secure'} = 1;
} }
$cgi->send_cookie(-name => 'Bugzilla_login', $cgi->send_cookie(-name => 'Bugzilla_login', -value => $user->id, %cookieargs);
-value => $user->id, $cgi->send_cookie(
%cookieargs); -name => 'Bugzilla_logincookie',
$cgi->send_cookie(-name => 'Bugzilla_logincookie',
-value => $login_cookie, -value => $login_cookie,
%cookieargs); %cookieargs
);
} }
sub logout { sub logout {
...@@ -98,8 +102,7 @@ sub logout { ...@@ -98,8 +102,7 @@ sub logout {
my $type = $param->{type} || LOGOUT_ALL; my $type = $param->{type} || LOGOUT_ALL;
if ($type == LOGOUT_ALL) { if ($type == LOGOUT_ALL) {
$dbh->do("DELETE FROM logincookies WHERE userid = ?", $dbh->do("DELETE FROM logincookies WHERE userid = ?", undef, $user->id);
undef, $user->id);
$dbh->do("DELETE FROM tokens WHERE userid = ? AND tokentype = 'sudo'", $dbh->do("DELETE FROM tokens WHERE userid = ? AND tokentype = 'sudo'",
undef, $user->id); undef, $user->id);
return; return;
...@@ -109,7 +112,7 @@ sub logout { ...@@ -109,7 +112,7 @@ sub logout {
# If a new cookie has been issued during this run, that's the current one. # If a new cookie has been issued during this run, that's the current one.
# If not, it's the one we've received. # If not, it's the one we've received.
my @login_cookies; my @login_cookies;
my $cookie = first {$_->name eq 'Bugzilla_logincookie'} my $cookie = first { $_->name eq 'Bugzilla_logincookie' }
@{$cgi->{'Bugzilla_cookie_list'}}; @{$cgi->{'Bugzilla_cookie_list'}};
if ($cookie) { if ($cookie) {
push(@login_cookies, $cookie->value); push(@login_cookies, $cookie->value);
...@@ -137,18 +140,24 @@ sub logout { ...@@ -137,18 +140,24 @@ sub logout {
map { trick_taint($_) } @login_cookies; map { trick_taint($_) } @login_cookies;
@login_cookies = map { $dbh->quote($_) } @login_cookies; @login_cookies = map { $dbh->quote($_) } @login_cookies;
if ($type == LOGOUT_KEEP_CURRENT) { if ($type == LOGOUT_KEEP_CURRENT) {
$dbh->do("DELETE FROM logincookies WHERE " . $dbh->do(
$dbh->sql_in('cookie', \@login_cookies, 1) . "DELETE FROM logincookies WHERE "
" AND userid = ?", . $dbh->sql_in('cookie', \@login_cookies, 1)
undef, $user->id); . " AND userid = ?",
} elsif ($type == LOGOUT_CURRENT) { undef, $user->id
$dbh->do("DELETE FROM logincookies WHERE " . );
$dbh->sql_in('cookie', \@login_cookies) . }
" AND userid = ?", elsif ($type == LOGOUT_CURRENT) {
undef, $user->id); $dbh->do(
"DELETE FROM logincookies WHERE "
. $dbh->sql_in('cookie', \@login_cookies)
. " AND userid = ?",
undef, $user->id
);
my $token = $cgi->cookie('sudo'); my $token = $cgi->cookie('sudo');
delete_token($token); delete_token($token);
} else { }
else {
die("Invalid type $type supplied to logout()"); die("Invalid type $type supplied to logout()");
} }
......
...@@ -49,30 +49,34 @@ sub create_or_update_user { ...@@ -49,30 +49,34 @@ sub create_or_update_user {
my $extern_user_id; my $extern_user_id;
if ($extern_id) { if ($extern_id) {
trick_taint($extern_id); trick_taint($extern_id);
$extern_user_id = $dbh->selectrow_array('SELECT userid $extern_user_id = $dbh->selectrow_array(
FROM profiles WHERE extern_id = ?', undef, $extern_id); 'SELECT userid
FROM profiles WHERE extern_id = ?', undef, $extern_id
);
} }
# If we have both a valid extern_id and a valid login, and they are # If we have both a valid extern_id and a valid login, and they are
# not the same id, then we have a conflict. # not the same id, then we have a conflict.
if ($login_user_id && $extern_user_id if ($login_user_id && $extern_user_id && $login_user_id ne $extern_user_id) {
&& $login_user_id ne $extern_user_id)
{
my $extern_name = Bugzilla::User->new($extern_user_id)->login; my $extern_name = Bugzilla::User->new($extern_user_id)->login;
return { failure => AUTH_ERROR, error => "extern_id_conflict", return {
details => {extern_id => $extern_id, failure => AUTH_ERROR,
extern_user => $extern_name, error => "extern_id_conflict",
username => $login} }; details =>
{extern_id => $extern_id, extern_user => $extern_name, username => $login}
};
} }
# If we have a valid login, but no valid id, # If we have a valid login, but no valid id,
# then we have to create the user. This happens when we're # then we have to create the user. This happens when we're
# passed only a login, and that login doesn't exist already. # passed only a login, and that login doesn't exist already.
if ($login && !$login_user_id && !$extern_user_id) { if ($login && !$login_user_id && !$extern_user_id) {
validate_email_syntax($email) validate_email_syntax($email) || return {
|| return { failure => AUTH_ERROR, failure => AUTH_ERROR,
error => 'auth_invalid_email', error => 'auth_invalid_email',
details => {addr => $email} }; details => {addr => $email}
};
# Usually we'd call validate_password, but external authentication # Usually we'd call validate_password, but external authentication
# systems might follow different standards than ours. So in this # systems might follow different standards than ours. So in this
# place here, we call trick_taint without checks. # place here, we call trick_taint without checks.
...@@ -84,7 +88,8 @@ sub create_or_update_user { ...@@ -84,7 +88,8 @@ sub create_or_update_user {
login_name => $login, login_name => $login,
email => $email, email => $email,
cryptpassword => $password, cryptpassword => $password,
realname => $real_name}); realname => $real_name
});
$login_user_id = $user->id; $login_user_id = $user->id;
} }
...@@ -93,7 +98,7 @@ sub create_or_update_user { ...@@ -93,7 +98,7 @@ sub create_or_update_user {
if ($extern_id && $login_user_id && !$extern_user_id) { if ($extern_id && $login_user_id && !$extern_user_id) {
$dbh->do('UPDATE profiles SET extern_id = ? WHERE userid = ?', $dbh->do('UPDATE profiles SET extern_id = ? WHERE userid = ?',
undef, $extern_id, $login_user_id); undef, $extern_id, $login_user_id);
Bugzilla->memcached->clear({ table => 'profiles', id => $login_user_id }); Bugzilla->memcached->clear({table => 'profiles', id => $login_user_id});
} }
# Finally, at this point, one of these will give us a valid user id. # Finally, at this point, one of these will give us a valid user id.
...@@ -102,9 +107,13 @@ sub create_or_update_user { ...@@ -102,9 +107,13 @@ sub create_or_update_user {
# If we still don't have a valid user_id, then we weren't passed # If we still don't have a valid user_id, then we weren't passed
# enough information in $params, and we should die right here. # enough information in $params, and we should die right here.
ThrowCodeError('bad_arg', {argument => 'params', function => ThrowCodeError(
'Bugzilla::Auth::Verify::create_or_update_user'}) 'bad_arg',
unless $user_id; {
argument => 'params',
function => 'Bugzilla::Auth::Verify::create_or_update_user'
}
) unless $user_id;
my $user = new Bugzilla::User($user_id); my $user = new Bugzilla::User($user_id);
...@@ -112,9 +121,11 @@ sub create_or_update_user { ...@@ -112,9 +121,11 @@ sub create_or_update_user {
my $changed = 0; my $changed = 0;
if ($email && lc($user->email) ne lc($email)) { if ($email && lc($user->email) ne lc($email)) {
validate_email_syntax($email) validate_email_syntax($email) || return {
|| return { failure => AUTH_ERROR, error => 'auth_invalid_email', failure => AUTH_ERROR,
details => {addr => $email} }; error => 'auth_invalid_email',
details => {addr => $email}
};
$user->set_email($email); $user->set_email($email);
$changed = 1; $changed = 1;
} }
...@@ -123,6 +134,7 @@ sub create_or_update_user { ...@@ -123,6 +134,7 @@ sub create_or_update_user {
$changed = 1; $changed = 1;
} }
if ($real_name && $user->name ne $real_name) { if ($real_name && $user->name ne $real_name) {
# $real_name is more than likely tainted, but we only use it # $real_name is more than likely tainted, but we only use it
# in a placeholder and we never use it after this. # in a placeholder and we never use it after this.
trick_taint($real_name); trick_taint($real_name);
...@@ -131,7 +143,7 @@ sub create_or_update_user { ...@@ -131,7 +143,7 @@ sub create_or_update_user {
} }
$user->update() if $changed; $user->update() if $changed;
return { user => $user }; return {user => $user};
} }
1; 1;
......
...@@ -23,15 +23,15 @@ sub check_credentials { ...@@ -23,15 +23,15 @@ sub check_credentials {
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $username = $login_data->{username}; my $username = $login_data->{username};
my $user = new Bugzilla::User({ name => $username }); my $user = new Bugzilla::User({name => $username});
return { failure => AUTH_NO_SUCH_USER } unless $user; return {failure => AUTH_NO_SUCH_USER} unless $user;
$login_data->{user} = $user; $login_data->{user} = $user;
$login_data->{bz_username} = $user->login; $login_data->{bz_username} = $user->login;
if ($user->account_is_locked_out) { if ($user->account_is_locked_out) {
return { failure => AUTH_LOCKOUT, user => $user }; return {failure => AUTH_LOCKOUT, user => $user};
} }
my $password = $login_data->{password}; my $password = $login_data->{password};
...@@ -42,32 +42,33 @@ sub check_credentials { ...@@ -42,32 +42,33 @@ sub check_credentials {
my $entered_password_crypted = bz_crypt($password, $real_password_crypted); my $entered_password_crypted = bz_crypt($password, $real_password_crypted);
if ($entered_password_crypted ne $real_password_crypted) { if ($entered_password_crypted ne $real_password_crypted) {
# Record the login failure # Record the login failure
$user->note_login_failure(); $user->note_login_failure();
# Immediately check if we are locked out # Immediately check if we are locked out
if ($user->account_is_locked_out) { if ($user->account_is_locked_out) {
return { failure => AUTH_LOCKOUT, user => $user, return {failure => AUTH_LOCKOUT, user => $user, just_locked_out => 1};
just_locked_out => 1 };
} }
return { failure => AUTH_LOGINFAILED, return {
failure_count => scalar(@{ $user->account_ip_login_failures }), failure => AUTH_LOGINFAILED,
failure_count => scalar(@{$user->account_ip_login_failures}),
}; };
} }
# Force the user to change their password if it does not meet the current # Force the user to change their password if it does not meet the current
# criteria. This should usually only happen if the criteria has changed. # criteria. This should usually only happen if the criteria has changed.
if (Bugzilla->usage_mode == USAGE_MODE_BROWSER && if ( Bugzilla->usage_mode == USAGE_MODE_BROWSER
Bugzilla->params->{password_check_on_login}) && Bugzilla->params->{password_check_on_login})
{ {
my $check = validate_password_check($password); my $check = validate_password_check($password);
if ($check) { if ($check) {
return { return {
failure => AUTH_ERROR, failure => AUTH_ERROR,
user_error => $check, user_error => $check,
details => { locked_user => $user } details => {locked_user => $user}
} };
} }
} }
...@@ -92,6 +93,7 @@ sub check_credentials { ...@@ -92,6 +93,7 @@ sub check_credentials {
# If needed, update the user's password. # If needed, update the user's password.
if ($update_password) { if ($update_password) {
# We can't call $user->set_password because we don't want the password # We can't call $user->set_password because we don't want the password
# complexity rules to apply here. # complexity rules to apply here.
$user->{cryptpassword} = bz_crypt($password); $user->{cryptpassword} = bz_crypt($password);
...@@ -107,7 +109,7 @@ sub change_password { ...@@ -107,7 +109,7 @@ sub change_password {
my $cryptpassword = bz_crypt($password); my $cryptpassword = bz_crypt($password);
$dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?", $dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?",
undef, $cryptpassword, $user->id); undef, $cryptpassword, $user->id);
Bugzilla->memcached->clear({ table => 'profiles', id => $user->id }); Bugzilla->memcached->clear({table => 'profiles', id => $user->id});
} }
1; 1;
...@@ -43,19 +43,22 @@ sub check_credentials { ...@@ -43,19 +43,22 @@ sub check_credentials {
# Now, we verify that the user exists, and get a LDAP Distinguished # Now, we verify that the user exists, and get a LDAP Distinguished
# Name for the user. # Name for the user.
my $username = $params->{username}; my $username = $params->{username};
my $dn_result = $self->ldap->search(_bz_search_params($username), my $dn_result
attrs => ['dn']); = $self->ldap->search(_bz_search_params($username), attrs => ['dn']);
return { failure => AUTH_ERROR, error => "ldap_search_error", return {
failure => AUTH_ERROR,
error => "ldap_search_error",
details => {errstr => $dn_result->error, username => $username} details => {errstr => $dn_result->error, username => $username}
} if $dn_result->code; }
if $dn_result->code;
return { failure => AUTH_NO_SUCH_USER } if !$dn_result->count; return {failure => AUTH_NO_SUCH_USER} if !$dn_result->count;
my $dn = $dn_result->shift_entry->dn; my $dn = $dn_result->shift_entry->dn;
# Check the password. # Check the password.
my $pw_result = $self->ldap->bind($dn, password => $params->{password}); my $pw_result = $self->ldap->bind($dn, password => $params->{password});
return { failure => AUTH_LOGINFAILED } if $pw_result->code; return {failure => AUTH_LOGINFAILED} if $pw_result->code;
# And now we fill in the user's details. # And now we fill in the user's details.
...@@ -64,9 +67,11 @@ sub check_credentials { ...@@ -64,9 +67,11 @@ sub check_credentials {
my $error_string; my $error_string;
my $detail_result = $self->ldap->search(_bz_search_params($username)); my $detail_result = $self->ldap->search(_bz_search_params($username));
if ($detail_result->code) { if ($detail_result->code) {
# Stash away the original error, just in case # Stash away the original error, just in case
$error_string = $detail_result->error; $error_string = $detail_result->error;
} else { }
else {
$user_entry = $detail_result->shift_entry; $user_entry = $detail_result->shift_entry;
} }
...@@ -83,17 +88,22 @@ sub check_credentials { ...@@ -83,17 +88,22 @@ sub check_credentials {
} }
# If we *still* don't have anything in $user_entry then give up. # If we *still* don't have anything in $user_entry then give up.
return { failure => AUTH_ERROR, error => "ldap_search_error", return {
failure => AUTH_ERROR,
error => "ldap_search_error",
details => {errstr => $error_string, username => $username} details => {errstr => $error_string, username => $username}
} if !$user_entry; }
if !$user_entry;
my $mail_attr = Bugzilla->params->{"LDAPmailattribute"}; my $mail_attr = Bugzilla->params->{"LDAPmailattribute"};
if ($mail_attr) { if ($mail_attr) {
if (!$user_entry->exists($mail_attr)) { if (!$user_entry->exists($mail_attr)) {
return { failure => AUTH_ERROR, return {
failure => AUTH_ERROR,
error => "ldap_cannot_retreive_attr", error => "ldap_cannot_retreive_attr",
details => {attr => $mail_attr} }; details => {attr => $mail_attr}
};
} }
my @emails = $user_entry->get_value($mail_attr); my @emails = $user_entry->get_value($mail_attr);
...@@ -102,10 +112,11 @@ sub check_credentials { ...@@ -102,10 +112,11 @@ sub check_credentials {
$params->{email} = $emails[0]; $params->{email} = $emails[0];
if (@emails > 1) { if (@emails > 1) {
# Cycle through the addresses and check if they're Bugzilla logins. # Cycle through the addresses and check if they're Bugzilla logins.
# Use the first one that returns a valid id. # Use the first one that returns a valid id.
foreach my $email (@emails) { foreach my $email (@emails) {
if ( email_to_id($email) ) { if (email_to_id($email)) {
$params->{email} = $email; $params->{email} = $email;
last; last;
} }
...@@ -125,21 +136,22 @@ sub check_credentials { ...@@ -125,21 +136,22 @@ sub check_credentials {
sub _bz_search_params { sub _bz_search_params {
my ($username) = @_; my ($username) = @_;
$username = escape_filter_value($username); $username = escape_filter_value($username);
return (base => Bugzilla->params->{"LDAPBaseDN"}, return (
base => Bugzilla->params->{"LDAPBaseDN"},
scope => "sub", scope => "sub",
filter => '(&(' . Bugzilla->params->{"LDAPuidattribute"} filter => '(&('
. Bugzilla->params->{"LDAPuidattribute"}
. "=$username)" . "=$username)"
. Bugzilla->params->{"LDAPfilter"} . ')'); . Bugzilla->params->{"LDAPfilter"} . ')'
);
} }
sub _bind_ldap_for_search { sub _bind_ldap_for_search {
my ($self) = @_; my ($self) = @_;
my $bind_result; my $bind_result;
if (Bugzilla->params->{"LDAPbinddn"}) { if (Bugzilla->params->{"LDAPbinddn"}) {
my ($LDAPbinddn,$LDAPbindpass) = my ($LDAPbinddn, $LDAPbindpass) = split(":", Bugzilla->params->{"LDAPbinddn"});
split(":",Bugzilla->params->{"LDAPbinddn"}); $bind_result = $self->ldap->bind($LDAPbinddn, password => $LDAPbindpass);
$bind_result =
$self->ldap->bind($LDAPbinddn, password => $LDAPbindpass);
} }
else { else {
$bind_result = $self->ldap->bind(); $bind_result = $self->ldap->bind();
...@@ -164,13 +176,13 @@ sub ldap { ...@@ -164,13 +176,13 @@ sub ldap {
$self->{ldap} = new Net::LDAP(trim($_)); $self->{ldap} = new Net::LDAP(trim($_));
last if $self->{ldap}; last if $self->{ldap};
} }
ThrowCodeError("ldap_connect_failed", { server => join(", ", @servers) }) ThrowCodeError("ldap_connect_failed", {server => join(", ", @servers)})
unless $self->{ldap}; unless $self->{ldap};
# try to start TLS if needed # try to start TLS if needed
if (Bugzilla->params->{"LDAPstarttls"}) { if (Bugzilla->params->{"LDAPstarttls"}) {
my $mesg = $self->{ldap}->start_tls(); my $mesg = $self->{ldap}->start_tls();
ThrowCodeError("ldap_start_tls_failed", { error => $mesg->error() }) ThrowCodeError("ldap_start_tls_failed", {error => $mesg->error()})
if $mesg->code(); if $mesg->code();
} }
......
...@@ -35,16 +35,20 @@ sub check_credentials { ...@@ -35,16 +35,20 @@ sub check_credentials {
} }
# Create RADIUS object. # Create RADIUS object.
my $radius = my $radius = new Authen::Radius(
new Authen::Radius(Host => Bugzilla->params->{'RADIUS_server'}, Host => Bugzilla->params->{'RADIUS_server'},
Secret => Bugzilla->params->{'RADIUS_secret'}) Secret => Bugzilla->params->{'RADIUS_secret'}
|| return { failure => AUTH_ERROR, error => 'radius_preparation_error', )
details => {errstr => Authen::Radius::strerror() } }; || return {
failure => AUTH_ERROR,
error => 'radius_preparation_error',
details => {errstr => Authen::Radius::strerror()}
};
# Check the password. # Check the password.
$radius->check_pwd($username, $params->{password}, $radius->check_pwd($username, $params->{password},
Bugzilla->params->{'RADIUS_NAS_IP'} || undef) Bugzilla->params->{'RADIUS_NAS_IP'} || undef)
|| return { failure => AUTH_LOGINFAILED }; || return {failure => AUTH_LOGINFAILED};
$params->{bz_username} = $username; $params->{bz_username} = $username;
......
...@@ -28,7 +28,7 @@ sub new { ...@@ -28,7 +28,7 @@ sub new {
my $self = $class->SUPER::new(@_); my $self = $class->SUPER::new(@_);
my %methods = map { $_ => "Bugzilla/Auth/Verify/$_.pm" } split(',', $list); my %methods = map { $_ => "Bugzilla/Auth/Verify/$_.pm" } split(',', $list);
lock_keys(%methods); lock_keys(%methods);
Bugzilla::Hook::process('auth_verify_methods', { modules => \%methods }); Bugzilla::Hook::process('auth_verify_methods', {modules => \%methods});
$self->{_stack} = []; $self->{_stack} = [];
foreach my $verify_method (split(',', $list)) { foreach my $verify_method (split(',', $list)) {
...@@ -43,6 +43,7 @@ sub new { ...@@ -43,6 +43,7 @@ sub new {
sub can_change_password { sub can_change_password {
my ($self) = @_; my ($self) = @_;
# We return true if any method can change passwords. # We return true if any method can change passwords.
foreach my $object (@{$self->{_stack}}) { foreach my $object (@{$self->{_stack}}) {
return 1 if $object->can_change_password; return 1 if $object->can_change_password;
...@@ -57,9 +58,11 @@ sub check_credentials { ...@@ -57,9 +58,11 @@ sub check_credentials {
$result = $object->check_credentials(@_); $result = $object->check_credentials(@_);
$self->{successful} = $object; $self->{successful} = $object;
last if !$result->{failure}; last if !$result->{failure};
# So that if none of them succeed, it's undef. # So that if none of them succeed, it's undef.
$self->{successful} = undef; $self->{successful} = undef;
} }
# Returns the result at the bottom of the stack if they all fail. # Returns the result at the bottom of the stack if they all fail.
return $result; return $result;
} }
...@@ -71,12 +74,14 @@ sub create_or_update_user { ...@@ -71,12 +74,14 @@ sub create_or_update_user {
$result = $object->create_or_update_user(@_); $result = $object->create_or_update_user(@_);
last if !$result->{failure}; last if !$result->{failure};
} }
# Returns the result at the bottom of the stack if they all fail. # Returns the result at the bottom of the stack if they all fail.
return $result; return $result;
} }
sub user_can_create_account { sub user_can_create_account {
my ($self) = @_; my ($self) = @_;
# We return true if any method allows the user to create an account. # We return true if any method allows the user to create an account.
foreach my $object (@{$self->{_stack}}) { foreach my $object (@{$self->{_stack}}) {
return 1 if $object->user_can_create_account; return 1 if $object->user_can_create_account;
...@@ -86,7 +91,7 @@ sub user_can_create_account { ...@@ -86,7 +91,7 @@ sub user_can_create_account {
sub extern_id_used { sub extern_id_used {
my ($self) = @_; my ($self) = @_;
return any { $_->extern_id_used } @{ $self->{_stack} }; return any { $_->extern_id_used } @{$self->{_stack}};
} }
1; 1;
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -28,6 +28,7 @@ use URI::QueryParam; ...@@ -28,6 +28,7 @@ use URI::QueryParam;
use constant DB_TABLE => 'bug_see_also'; use constant DB_TABLE => 'bug_see_also';
use constant NAME_FIELD => 'value'; use constant NAME_FIELD => 'value';
use constant LIST_ORDER => 'id'; use constant LIST_ORDER => 'id';
# See Also is tracked in bugs_activity. # See Also is tracked in bugs_activity.
use constant AUDIT_CREATES => 0; use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0; use constant AUDIT_UPDATES => 0;
...@@ -84,19 +85,15 @@ sub new { ...@@ -84,19 +85,15 @@ sub new {
my $bug_id = $param->{bug_id}; my $bug_id = $param->{bug_id};
my $name = $param->{name} || $param->{value}; my $name = $param->{name} || $param->{value};
if (!defined $bug_id) { if (!defined $bug_id) {
ThrowCodeError('bad_arg', ThrowCodeError('bad_arg', {argument => 'bug_id', function => "${class}::new"});
{ argument => 'bug_id',
function => "${class}::new" });
} }
if (!defined $name) { if (!defined $name) {
ThrowCodeError('bad_arg', ThrowCodeError('bad_arg', {argument => 'name', function => "${class}::new"});
{ argument => 'name',
function => "${class}::new" });
} }
my $condition = 'bug_id = ? AND value = ?'; my $condition = 'bug_id = ? AND value = ?';
my @values = ($bug_id, $name); my @values = ($bug_id, $name);
$param = { condition => $condition, values => \@values }; $param = {condition => $condition, values => \@values};
} }
unshift @_, $param; unshift @_, $param;
...@@ -109,27 +106,26 @@ sub _do_list_select { ...@@ -109,27 +106,26 @@ sub _do_list_select {
foreach my $object (@$objects) { foreach my $object (@$objects) {
eval "use " . $object->class; eval "use " . $object->class;
# If the class cannot be loaded, then we build a generic object. # If the class cannot be loaded, then we build a generic object.
bless $object, ($@ ? 'Bugzilla::BugUrl' : $object->class); bless $object, ($@ ? 'Bugzilla::BugUrl' : $object->class);
} }
return $objects return $objects;
} }
# This is an abstract method. It must be overridden # This is an abstract method. It must be overridden
# in every subclass. # in every subclass.
sub should_handle { sub should_handle {
my ($class, $input) = @_; my ($class, $input) = @_;
ThrowCodeError('unknown_method', ThrowCodeError('unknown_method', {method => "${class}::should_handle"});
{ method => "${class}::should_handle" });
} }
sub class_for { sub class_for {
my ($class, $value) = @_; my ($class, $value) = @_;
my @sub_classes = $class->SUB_CLASSES; my @sub_classes = $class->SUB_CLASSES;
Bugzilla::Hook::process("bug_url_sub_classes", Bugzilla::Hook::process("bug_url_sub_classes", {sub_classes => \@sub_classes});
{ sub_classes => \@sub_classes });
my $uri = URI->new($value); my $uri = URI->new($value);
foreach my $subclass (@sub_classes) { foreach my $subclass (@sub_classes) {
...@@ -139,12 +135,13 @@ sub class_for { ...@@ -139,12 +135,13 @@ sub class_for {
if $subclass->should_handle($uri); if $subclass->should_handle($uri);
} }
ThrowUserError('bug_url_invalid', { url => $value }); ThrowUserError('bug_url_invalid', {url => $value});
} }
sub _check_class { sub _check_class {
my ($class, $subclass) = @_; my ($class, $subclass) = @_;
eval "use $subclass"; die $@ if $@; eval "use $subclass";
die $@ if $@;
return $subclass; return $subclass;
} }
...@@ -153,13 +150,14 @@ sub _check_bug_id { ...@@ -153,13 +150,14 @@ sub _check_bug_id {
my $bug; my $bug;
if (blessed $bug_id) { if (blessed $bug_id) {
# We got a bug object passed in, use it # We got a bug object passed in, use it
$bug = $bug_id; $bug = $bug_id;
$bug->check_is_visible; $bug->check_is_visible;
} }
else { else {
# We got a bug id passed in, check it and get the bug object # We got a bug id passed in, check it and get the bug object
$bug = Bugzilla::Bug->check({ id => $bug_id }); $bug = Bugzilla::Bug->check({id => $bug_id});
} }
return $bug->id; return $bug->id;
...@@ -172,19 +170,20 @@ sub _check_value { ...@@ -172,19 +170,20 @@ sub _check_value {
if (!$value) { if (!$value) {
ThrowCodeError('param_required', ThrowCodeError('param_required',
{ function => 'add_see_also', param => '$value' }); {function => 'add_see_also', param => '$value'});
} }
# We assume that the URL is an HTTP URL if there is no (something):// # We assume that the URL is an HTTP URL if there is no (something)://
# in front. # in front.
if (!$uri->scheme) { if (!$uri->scheme) {
# This works better than setting $uri->scheme('http'), because # This works better than setting $uri->scheme('http'), because
# that creates URLs like "http:domain.com" and doesn't properly # that creates URLs like "http:domain.com" and doesn't properly
# differentiate the path from the domain. # differentiate the path from the domain.
$uri = new URI("http://$value"); $uri = new URI("http://$value");
} }
elsif ($uri->scheme ne 'http' && $uri->scheme ne 'https') { elsif ($uri->scheme ne 'http' && $uri->scheme ne 'https') {
ThrowUserError('bug_url_invalid', { url => $value, reason => 'http' }); ThrowUserError('bug_url_invalid', {url => $value, reason => 'http'});
} }
# This stops the following edge cases from being accepted: # This stops the following edge cases from being accepted:
...@@ -192,12 +191,11 @@ sub _check_value { ...@@ -192,12 +191,11 @@ sub _check_value {
# * /show_bug.cgi?id=1 # * /show_bug.cgi?id=1
# * http:///show_bug.cgi?id=1 # * http:///show_bug.cgi?id=1
if (!$uri->authority or $uri->path !~ m{/}) { if (!$uri->authority or $uri->path !~ m{/}) {
ThrowUserError('bug_url_invalid', ThrowUserError('bug_url_invalid', {url => $value, reason => 'path_only'});
{ url => $value, reason => 'path_only' });
} }
if (length($uri->path) > MAX_BUG_URL_LENGTH) { if (length($uri->path) > MAX_BUG_URL_LENGTH) {
ThrowUserError('bug_url_too_long', { url => $uri->path }); ThrowUserError('bug_url_too_long', {url => $uri->path});
} }
return $uri; return $uri;
......
...@@ -31,6 +31,7 @@ sub _check_value { ...@@ -31,6 +31,7 @@ sub _check_value {
$uri = $class->SUPER::_check_value($uri); $uri = $class->SUPER::_check_value($uri);
my $bug_id = $uri->query_param('id'); my $bug_id = $uri->query_param('id');
# We don't currently allow aliases, because we can't check to see # We don't currently allow aliases, because we can't check to see
# if somebody's putting both an alias link and a numeric ID link. # if somebody's putting both an alias link and a numeric ID link.
# When we start validating the URL by accessing the other Bugzilla, # When we start validating the URL by accessing the other Bugzilla,
...@@ -38,11 +39,12 @@ sub _check_value { ...@@ -38,11 +39,12 @@ sub _check_value {
detaint_natural($bug_id); detaint_natural($bug_id);
if (!$bug_id) { if (!$bug_id) {
my $value = $uri->as_string; my $value = $uri->as_string;
ThrowUserError('bug_url_invalid', { url => $value, reason => 'id' }); ThrowUserError('bug_url_invalid', {url => $value, reason => 'id'});
} }
# Make sure that "id" is the only query parameter. # Make sure that "id" is the only query parameter.
$uri->query("id=$bug_id"); $uri->query("id=$bug_id");
# And remove any # part if there is one. # And remove any # part if there is one.
$uri->fragment(undef); $uri->fragment(undef);
......
...@@ -20,9 +20,7 @@ use Bugzilla::Util; ...@@ -20,9 +20,7 @@ use Bugzilla::Util;
#### Initialization #### #### Initialization ####
############################### ###############################
use constant VALIDATOR_DEPENDENCIES => { use constant VALIDATOR_DEPENDENCIES => {value => ['bug_id'],};
value => ['bug_id'],
};
############################### ###############################
#### Methods #### #### Methods ####
...@@ -35,9 +33,8 @@ sub ref_bug_url { ...@@ -35,9 +33,8 @@ sub ref_bug_url {
my $ref_bug_id = new URI($self->name)->query_param('id'); my $ref_bug_id = new URI($self->name)->query_param('id');
my $ref_bug = Bugzilla::Bug->check($ref_bug_id); my $ref_bug = Bugzilla::Bug->check($ref_bug_id);
my $ref_value = $self->local_uri($self->bug_id); my $ref_value = $self->local_uri($self->bug_id);
$self->{ref_bug_url} = $self->{ref_bug_url} = new Bugzilla::BugUrl::Bugzilla::Local(
new Bugzilla::BugUrl::Bugzilla::Local({ bug_id => $ref_bug->id, {bug_id => $ref_bug->id, value => $ref_value});
value => $ref_value });
} }
return $self->{ref_bug_url}; return $self->{ref_bug_url};
} }
...@@ -52,7 +49,7 @@ sub should_handle { ...@@ -52,7 +49,7 @@ sub should_handle {
# Bugzilla::BugUrl::Bugzilla to check if it's a valid Bugzilla # Bugzilla::BugUrl::Bugzilla to check if it's a valid Bugzilla
# see also url. # see also url.
my $canonical_local = URI->new($class->local_uri)->canonical; my $canonical_local = URI->new($class->local_uri)->canonical;
if ($canonical_local->authority eq $uri->canonical->authority if ( $canonical_local->authority eq $uri->canonical->authority
and $canonical_local->path eq $uri->canonical->path) and $canonical_local->path eq $uri->canonical->path)
{ {
return $class->SUPER::should_handle($uri); return $class->SUPER::should_handle($uri);
...@@ -69,7 +66,8 @@ sub _check_value { ...@@ -69,7 +66,8 @@ sub _check_value {
my $value = $uri->as_string; my $value = $uri->as_string;
if ($value =~ m/^\w+$/) { if ($value =~ m/^\w+$/) {
$uri = new URI($class->local_uri($value)); $uri = new URI($class->local_uri($value));
} else { }
else {
# It's not a word, then we have to check # It's not a word, then we have to check
# if it's a valid Bugzilla url. # if it's a valid Bugzilla url.
$uri = $class->SUPER::_check_value($uri); $uri = $class->SUPER::_check_value($uri);
......
...@@ -25,11 +25,15 @@ sub should_handle { ...@@ -25,11 +25,15 @@ sub should_handle {
# http://bugs.debian.org/1234 # http://bugs.debian.org/1234
# https://debbugs.gnu.org/cgi/bugreport.cgi?bug=123 # https://debbugs.gnu.org/cgi/bugreport.cgi?bug=123
# https://debbugs.gnu.org/123 # https://debbugs.gnu.org/123
return ((lc($uri->authority) eq 'bugs.debian.org' return (
or lc($uri->authority) eq 'debbugs.gnu.org') (
and (($uri->path =~ /bugreport\.cgi$/ lc($uri->authority) eq 'bugs.debian.org'
and $uri->query_param('bug') =~ m|^\d+$|a) or lc($uri->authority) eq 'debbugs.gnu.org'
or $uri->path =~ m|^/\d+$|a)) ? 1 : 0; )
and
(($uri->path =~ /bugreport\.cgi$/ and $uri->query_param('bug') =~ m|^\d+$|a)
or $uri->path =~ m|^/\d+$|a)
) ? 1 : 0;
} }
sub _check_value { sub _check_value {
......
...@@ -20,10 +20,10 @@ use parent qw(Bugzilla::BugUrl); ...@@ -20,10 +20,10 @@ use parent qw(Bugzilla::BugUrl);
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# GitHub issue URLs have only one form: # GitHub issue URLs have only one form:
# https://github.com/USER_OR_TEAM_OR_ORGANIZATION_NAME/REPOSITORY_NAME/issues/111 # https://github.com/USER_OR_TEAM_OR_ORGANIZATION_NAME/REPOSITORY_NAME/issues/111
# GitHub pull request URLs have only one form: # GitHub pull request URLs have only one form:
# https://github.com/USER_OR_TEAM_OR_ORGANIZATION_NAME/REPOSITORY_NAME/pull/111 # https://github.com/USER_OR_TEAM_OR_ORGANIZATION_NAME/REPOSITORY_NAME/pull/111
return (lc($uri->authority) eq 'github.com' return (lc($uri->authority) eq 'github.com'
and $uri->path =~ m!^/[^/]+/[^/]+/(?:issues|pull)/\d+$!) ? 1 : 0; and $uri->path =~ m!^/[^/]+/[^/]+/(?:issues|pull)/\d+$!) ? 1 : 0;
} }
......
...@@ -33,6 +33,7 @@ sub _check_value { ...@@ -33,6 +33,7 @@ sub _check_value {
# Make sure there are no query parameters. # Make sure there are no query parameters.
$uri->query(undef); $uri->query(undef);
# And remove any # part if there is one. # And remove any # part if there is one.
$uri->fragment(undef); $uri->fragment(undef);
......
...@@ -24,8 +24,9 @@ sub should_handle { ...@@ -24,8 +24,9 @@ sub should_handle {
# https://bugs.launchpad.net/ubuntu/+bug/1234 # https://bugs.launchpad.net/ubuntu/+bug/1234
# https://launchpad.net/bugs/1234 # https://launchpad.net/bugs/1234
# All variations end with either "/bugs/1234" or "/+bug/1234" # All variations end with either "/bugs/1234" or "/+bug/1234"
return ($uri->authority =~ /launchpad\.net$/ return ($uri->authority =~ /launchpad\.net$/ and $uri->path =~ m|bugs?/\d+$|a)
and $uri->path =~ m|bugs?/\d+$|a) ? 1 : 0; ? 1
: 0;
} }
sub _check_value { sub _check_value {
......
...@@ -27,13 +27,20 @@ sub should_handle { ...@@ -27,13 +27,20 @@ sub should_handle {
# http://sourceforge.net/p/project/feature-requests/12345/ # http://sourceforge.net/p/project/feature-requests/12345/
# http://sourceforge.net/p/project/patches/12345/ # http://sourceforge.net/p/project/patches/12345/
# http://sourceforge.net/p/project/support-requests/12345/ # http://sourceforge.net/p/project/support-requests/12345/
return (lc($uri->authority) eq 'sourceforge.net' return (
and (($uri->path eq '/tracker/' lc($uri->authority) eq 'sourceforge.net'
and (
(
$uri->path eq '/tracker/'
and $uri->query_param('func') eq 'detail' and $uri->query_param('func') eq 'detail'
and $uri->query_param('aid') and $uri->query_param('aid')
and $uri->query_param('group_id') and $uri->query_param('group_id')
and $uri->query_param('atid')) and $uri->query_param('atid')
or $uri->path =~ m!^/p/[^/]+/(?:bugs|feature-requests|patches|support-requests)/\d+/?$!)) ? 1 : 0; )
or $uri->path
=~ m!^/p/[^/]+/(?:bugs|feature-requests|patches|support-requests)/\d+/?$!
)
) ? 1 : 0;
} }
sub _check_value { sub _check_value {
......
...@@ -33,6 +33,7 @@ sub _check_value { ...@@ -33,6 +33,7 @@ sub _check_value {
# Make sure there are no query parameters. # Make sure there are no query parameters.
$uri->query(undef); $uri->query(undef);
# And remove any # part if there is one. # And remove any # part if there is one.
$uri->fragment(undef); $uri->fragment(undef);
......
...@@ -25,10 +25,12 @@ use constant LIST_ORDER => 'id'; ...@@ -25,10 +25,12 @@ use constant LIST_ORDER => 'id';
use constant NAME_FIELD => 'id'; use constant NAME_FIELD => 'id';
# turn off auditing and exclude these objects from memcached # turn off auditing and exclude these objects from memcached
use constant { AUDIT_CREATES => 0, use constant {
AUDIT_CREATES => 0,
AUDIT_UPDATES => 0, AUDIT_UPDATES => 0,
AUDIT_REMOVES => 0, AUDIT_REMOVES => 0,
USE_MEMCACHED => 0 }; USE_MEMCACHED => 0
};
##################################################################### #####################################################################
# Provide accessors for our columns # Provide accessors for our columns
...@@ -42,7 +44,7 @@ sub last_visit_ts { return $_[0]->{last_visit_ts} } ...@@ -42,7 +44,7 @@ sub last_visit_ts { return $_[0]->{last_visit_ts} }
sub user { sub user {
my $self = shift; my $self = shift;
$self->{user} //= Bugzilla::User->new({ id => $self->user_id, cache => 1 }); $self->{user} //= Bugzilla::User->new({id => $self->user_id, cache => 1});
return $self->{user}; return $self->{user};
} }
......
...@@ -17,7 +17,8 @@ use Type::Utils; ...@@ -17,7 +17,8 @@ use Type::Utils;
use Bugzilla::Util qw(generate_random_password); use Bugzilla::Util qw(generate_random_password);
my $SRC_KEYWORD = enum['none', 'self', 'unsafe-inline', 'unsafe-eval', 'nonce']; my $SRC_KEYWORD
= enum ['none', 'self', 'unsafe-inline', 'unsafe-eval', 'nonce'];
my $SRC_URI = declare as Str, where { my $SRC_URI = declare as Str, where {
$_ =~ m{ $_ =~ m{
^(?: https?:// )? # optional http:// or https:// ^(?: https?:// )? # optional http:// or https://
...@@ -26,11 +27,13 @@ my $SRC_URI = declare as Str, where { ...@@ -26,11 +27,13 @@ my $SRC_URI = declare as Str, where {
}x; }x;
}; };
my $SRC = $SRC_KEYWORD | $SRC_URI; my $SRC = $SRC_KEYWORD | $SRC_URI;
my $SOURCE_LIST = ArrayRef[$SRC]; my $SOURCE_LIST = ArrayRef [$SRC];
my $REFERRER_KEYWORD = enum [qw( my $REFERRER_KEYWORD = enum [
qw(
no-referrer no-referrer-when-downgrade no-referrer no-referrer-when-downgrade
origin origin-when-cross-origin unsafe-url origin origin-when-cross-origin unsafe-url
)]; )
];
my @ALL_BOOL = qw( sandbox upgrade_insecure_requests ); my @ALL_BOOL = qw( sandbox upgrade_insecure_requests );
my @ALL_SRC = qw( my @ALL_SRC = qw(
...@@ -39,15 +42,15 @@ my @ALL_SRC = qw( ...@@ -39,15 +42,15 @@ my @ALL_SRC = qw(
object_src script_src style_src object_src script_src style_src
); );
has \@ALL_SRC => ( is => 'ro', isa => $SOURCE_LIST, predicate => 1 ); has \@ALL_SRC => (is => 'ro', isa => $SOURCE_LIST, predicate => 1);
has \@ALL_BOOL => ( is => 'ro', isa => Bool, default => 0 ); has \@ALL_BOOL => (is => 'ro', isa => Bool, default => 0);
has 'report_uri' => ( is => 'ro', isa => Str, predicate => 1 ); has 'report_uri' => (is => 'ro', isa => Str, predicate => 1);
has 'base_uri' => ( is => 'ro', isa => Str, predicate => 1 ); has 'base_uri' => (is => 'ro', isa => Str, predicate => 1);
has 'report_only' => ( is => 'ro', isa => Bool ); has 'report_only' => (is => 'ro', isa => Bool);
has 'referrer' => ( is => 'ro', isa => $REFERRER_KEYWORD, predicate => 1 ); has 'referrer' => (is => 'ro', isa => $REFERRER_KEYWORD, predicate => 1);
has 'value' => ( is => 'lazy' ); has 'value' => (is => 'lazy');
has 'nonce' => ( is => 'lazy', init_arg => undef, predicate => 1 ); has 'nonce' => (is => 'lazy', init_arg => undef, predicate => 1);
has 'disable' => ( is => 'ro', isa => Bool, default => 0 ); has 'disable' => (is => 'ro', isa => Bool, default => 0);
sub _has_directive { sub _has_directive {
my ($self, $directive) = @_; my ($self, $directive) = @_;
...@@ -57,7 +60,8 @@ sub _has_directive { ...@@ -57,7 +60,8 @@ sub _has_directive {
sub header_names { sub header_names {
my ($self) = @_; my ($self) = @_;
my @names = ('Content-Security-Policy', 'X-Content-Security-Policy', 'X-WebKit-CSP'); my @names
= ('Content-Security-Policy', 'X-Content-Security-Policy', 'X-WebKit-CSP');
if ($self->report_only) { if ($self->report_only) {
return map { $_ . '-Report-Only' } @names; return map { $_ . '-Report-Only' } @names;
} }
...@@ -84,7 +88,7 @@ sub _build_value { ...@@ -84,7 +88,7 @@ sub _build_value {
foreach my $directive (@list_directives) { foreach my $directive (@list_directives) {
next unless $self->_has_directive($directive); next unless $self->_has_directive($directive);
my @values = map { $self->_quote($_) } @{ $self->$directive }; my @values = map { $self->_quote($_) } @{$self->$directive};
if (@values) { if (@values) {
push @result, join(' ', _name($directive), @values); push @result, join(' ', _name($directive), @values);
} }
...@@ -132,7 +136,6 @@ sub _quote { ...@@ -132,7 +136,6 @@ sub _quote {
} }
1; 1;
__END__ __END__
......
...@@ -34,6 +34,7 @@ sub new { ...@@ -34,6 +34,7 @@ sub new {
bless($self, $class); bless($self, $class);
if ($#_ == 0) { if ($#_ == 0) {
# Construct from a CGI object. # Construct from a CGI object.
$self->init($_[0]); $self->init($_[0]);
} }
...@@ -58,11 +59,11 @@ sub init { ...@@ -58,11 +59,11 @@ sub init {
# &cumulate=1&datefrom=2002-02-03&dateto=2002-04-04&ctype=html... # &cumulate=1&datefrom=2002-02-03&dateto=2002-04-04&ctype=html...
# &gt=1&labelgt=Grand+Total # &gt=1&labelgt=Grand+Total
foreach my $param ($cgi->multi_param()) { foreach my $param ($cgi->multi_param()) {
# Store all the lines # Store all the lines
if ($param =~ /^line(\d+)$/a) { if ($param =~ /^line(\d+)$/a) {
foreach my $series_id ($cgi->multi_param($param)) { foreach my $series_id ($cgi->multi_param($param)) {
detaint_natural($series_id) detaint_natural($series_id) || ThrowCodeError("invalid_series_id");
|| ThrowCodeError("invalid_series_id");
my $series = new Bugzilla::Series($series_id); my $series = new Bugzilla::Series($series_id);
push(@{$self->{'lines'}[$1]}, $series) if $series; push(@{$self->{'lines'}[$1]}, $series) if $series;
} }
...@@ -88,16 +89,22 @@ sub init { ...@@ -88,16 +89,22 @@ sub init {
foreach my $date ('datefrom', 'dateto') { foreach my $date ('datefrom', 'dateto') {
if ($self->{$date}) { if ($self->{$date}) {
$self->{$date} = str2time($self->{$date}) $self->{$date} = str2time($self->{$date})
|| ThrowUserError("illegal_date", { date => $self->{$date}}); || ThrowUserError("illegal_date", {date => $self->{$date}});
} }
} }
# datefrom can't be after dateto # datefrom can't be after dateto
if ($self->{'datefrom'} && $self->{'dateto'} && if ( $self->{'datefrom'}
$self->{'datefrom'} > $self->{'dateto'}) && $self->{'dateto'}
&& $self->{'datefrom'} > $self->{'dateto'})
{
ThrowUserError(
'misarranged_dates',
{ {
ThrowUserError('misarranged_dates', { 'datefrom' => scalar $cgi->param('datefrom'), 'datefrom' => scalar $cgi->param('datefrom'),
'dateto' => scalar $cgi->param('dateto') }); 'dateto' => scalar $cgi->param('dateto')
}
);
} }
} }
...@@ -111,6 +118,7 @@ sub add { ...@@ -111,6 +118,7 @@ sub add {
# Count the number of added series # Count the number of added series
my $added = 0; my $added = 0;
# Create new Series and push them on to the list of lines. # Create new Series and push them on to the list of lines.
# Note that new lines have no label; the display template is responsible # Note that new lines have no label; the display template is responsible
# for inventing something sensible. # for inventing something sensible.
...@@ -125,9 +133,7 @@ sub add { ...@@ -125,9 +133,7 @@ sub add {
# If we are going from < 2 to >= 2 series, add the Grand Total line. # If we are going from < 2 to >= 2 series, add the Grand Total line.
if (!$self->{'gt'}) { if (!$self->{'gt'}) {
if ($current_size < 2 && if ($current_size < 2 && $current_size + $added >= 2) {
$current_size + $added >= 2)
{
$self->{'gt'} = 1; $self->{'gt'} = 1;
} }
} }
...@@ -140,6 +146,7 @@ sub remove { ...@@ -140,6 +146,7 @@ sub remove {
foreach my $line_id (@line_ids) { foreach my $line_id (@line_ids) {
if ($line_id == 65536) { if ($line_id == 65536) {
# Magic value - delete Grand Total. # Magic value - delete Grand Total.
$self->{'gt'} = 0; $self->{'gt'} = 0;
} }
...@@ -209,18 +216,20 @@ sub readData { ...@@ -209,18 +216,20 @@ sub readData {
# The date used is the one given if it's in a sensible range; otherwise, # The date used is the one given if it's in a sensible range; otherwise,
# it's the earliest or latest date in the database as appropriate. # it's the earliest or latest date in the database as appropriate.
my $datefrom = $dbh->selectrow_array("SELECT MIN(series_date) " . my $datefrom
"FROM series_data " . = $dbh->selectrow_array("SELECT MIN(series_date) "
"WHERE series_id IN ($series_ids)"); . "FROM series_data "
. "WHERE series_id IN ($series_ids)");
$datefrom = str2time($datefrom); $datefrom = str2time($datefrom);
if ($self->{'datefrom'} && $self->{'datefrom'} > $datefrom) { if ($self->{'datefrom'} && $self->{'datefrom'} > $datefrom) {
$datefrom = $self->{'datefrom'}; $datefrom = $self->{'datefrom'};
} }
my $dateto = $dbh->selectrow_array("SELECT MAX(series_date) " . my $dateto
"FROM series_data " . = $dbh->selectrow_array("SELECT MAX(series_date) "
"WHERE series_id IN ($series_ids)"); . "FROM series_data "
. "WHERE series_id IN ($series_ids)");
$dateto = str2time($dateto); $dateto = str2time($dateto);
if ($self->{'dateto'} && $self->{'dateto'} < $dateto) { if ($self->{'dateto'} && $self->{'dateto'} < $dateto) {
...@@ -232,11 +241,14 @@ sub readData { ...@@ -232,11 +241,14 @@ sub readData {
my $sql_to = time2str('%Y-%m-%d', $dateto); my $sql_to = time2str('%Y-%m-%d', $dateto);
# Prepare the query which retrieves the data for each series # Prepare the query which retrieves the data for each series
my $query = "SELECT " . $dbh->sql_to_days('series_date') . " - " . my $query
$dbh->sql_to_days('?') . ", series_value " . = "SELECT "
"FROM series_data " . . $dbh->sql_to_days('series_date') . " - "
"WHERE series_id = ? " . . $dbh->sql_to_days('?')
"AND series_date >= ?"; . ", series_value "
. "FROM series_data "
. "WHERE series_id = ? "
. "AND series_date >= ?";
if ($dateto) { if ($dateto) {
$query .= " AND series_date <= ?"; $query .= " AND series_date <= ?";
} }
...@@ -251,6 +263,7 @@ sub readData { ...@@ -251,6 +263,7 @@ sub readData {
my @datediff_total; my @datediff_total;
foreach my $line (@{$self->{'lines'}}) { foreach my $line (@{$self->{'lines'}}) {
# Even if we end up with no data, we need an empty arrayref to prevent # Even if we end up with no data, we need an empty arrayref to prevent
# errors in the PNG-generating code # errors in the PNG-generating code
$data[$line_index] = []; $data[$line_index] = [];
...@@ -294,6 +307,7 @@ sub readData { ...@@ -294,6 +307,7 @@ sub readData {
# calculate maximum y value # calculate maximum y value
if ($self->{'cumulate'}) { if ($self->{'cumulate'}) {
# Make sure we do not try to take the max of an array with undef values # Make sure we do not try to take the max of an array with undef values
my @processed_datediff; my @processed_datediff;
while (@datediff_total) { while (@datediff_total) {
...@@ -317,7 +331,7 @@ sub readData { ...@@ -317,7 +331,7 @@ sub readData {
} }
else { else {
# First, get the # of digits in the y_max_value # First, get the # of digits in the y_max_value
my $num_digits = 1+int(log($self->{'y_max_value'})/log(10)); my $num_digits = 1 + int(log($self->{'y_max_value'}) / log(10));
# We want to zero out all but the top 2 digits # We want to zero out all but the top 2 digits
my $mask_length = $num_digits - 2; my $mask_length = $num_digits - 2;
...@@ -330,7 +344,7 @@ sub readData { ...@@ -330,7 +344,7 @@ sub readData {
# (Throwing in the -1 keeps at least the smallest digit at zero) # (Throwing in the -1 keeps at least the smallest digit at zero)
do { do {
$self->{'y_max_value'} += 10**$mask_length; $self->{'y_max_value'} += 10**$mask_length;
} while ($self->{'y_max_value'} % (8*(10**($mask_length-1))) != 0); } while ($self->{'y_max_value'} % (8 * (10**($mask_length - 1))) != 0);
} }
...@@ -339,6 +353,7 @@ sub readData { ...@@ -339,6 +353,7 @@ sub readData {
unshift(@data, $date_progression); unshift(@data, $date_progression);
if ($self->{'gt'}) { if ($self->{'gt'}) {
# Add Grand Total to label list # Add Grand Total to label list
push(@{$self->{'labels'}}, $self->{'labelgt'}); push(@{$self->{'labels'}}, $self->{'labelgt'});
...@@ -371,20 +386,24 @@ sub getVisibleSeries { ...@@ -371,20 +386,24 @@ sub getVisibleSeries {
# Get all visible series # Get all visible series
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $serieses = $dbh->selectall_arrayref("SELECT cc1.name, cc2.name, " . my $serieses = $dbh->selectall_arrayref(
"series.name, series.series_id " . "SELECT cc1.name, cc2.name, "
"FROM series " . . "series.name, series.series_id "
"INNER JOIN series_categories AS cc1 " . . "FROM series "
" ON series.category = cc1.id " . . "INNER JOIN series_categories AS cc1 "
"INNER JOIN series_categories AS cc2 " . . " ON series.category = cc1.id "
" ON series.subcategory = cc2.id " . . "INNER JOIN series_categories AS cc2 "
"LEFT JOIN category_group_map AS cgm " . . " ON series.subcategory = cc2.id "
" ON series.category = cgm.category_id " . . "LEFT JOIN category_group_map AS cgm "
" AND cgm.group_id NOT IN($grouplist) " . . " ON series.category = cgm.category_id "
"WHERE creator = ? OR (is_public = 1 AND cgm.category_id IS NULL) " . . " AND cgm.group_id NOT IN($grouplist) "
$dbh->sql_group_by('series.series_id', 'cc1.name, cc2.name, ' . . "WHERE creator = ? OR (is_public = 1 AND cgm.category_id IS NULL) "
'series.name'), . $dbh->sql_group_by(
undef, Bugzilla->user->id); 'series.series_id', 'cc1.name, cc2.name, ' . 'series.name'
),
undef,
Bugzilla->user->id
);
foreach my $series (@$serieses) { foreach my $series (@$serieses) {
my ($cat, $subcat, $name, $series_id) = @$series; my ($cat, $subcat, $name, $series_id) = @$series;
$cats{$cat}{$subcat}{$name} = $series_id; $cats{$cat}{$subcat}{$name} = $series_id;
...@@ -408,7 +427,7 @@ sub generateDateProgression { ...@@ -408,7 +427,7 @@ sub generateDateProgression {
$dateto += (2 * $oneday) / 3; $dateto += (2 * $oneday) / 3;
while ($datefrom < $dateto) { while ($datefrom < $dateto) {
push (@progression, time2str("%Y-%m-%d", $datefrom)); push(@progression, time2str("%Y-%m-%d", $datefrom));
$datefrom += $oneday; $datefrom += $oneday;
} }
......
...@@ -61,14 +61,16 @@ sub remove_from_db { ...@@ -61,14 +61,16 @@ sub remove_from_db {
$dbh->bz_start_transaction(); $dbh->bz_start_transaction();
# Reclassify products to the default classification, if needed. # Reclassify products to the default classification, if needed.
my $product_ids = $dbh->selectcol_arrayref( my $product_ids
'SELECT id FROM products WHERE classification_id = ?', undef, $self->id); = $dbh->selectcol_arrayref(
'SELECT id FROM products WHERE classification_id = ?',
undef, $self->id);
if (@$product_ids) { if (@$product_ids) {
$dbh->do('UPDATE products SET classification_id = 1 WHERE ' $dbh->do('UPDATE products SET classification_id = 1 WHERE '
. $dbh->sql_in('id', $product_ids)); . $dbh->sql_in('id', $product_ids));
foreach my $id (@$product_ids) { foreach my $id (@$product_ids) {
Bugzilla->memcached->clear({ table => 'products', id => $id }); Bugzilla->memcached->clear({table => 'products', id => $id});
} }
Bugzilla->memcached->clear_config(); Bugzilla->memcached->clear_config();
} }
...@@ -94,8 +96,10 @@ sub _check_name { ...@@ -94,8 +96,10 @@ sub _check_name {
} }
my $classification = new Bugzilla::Classification({name => $name}); my $classification = new Bugzilla::Classification({name => $name});
if ($classification && (!ref $invocant || $classification->id != $invocant->id)) { if ($classification && (!ref $invocant || $classification->id != $invocant->id))
ThrowUserError("classification_already_exists", { name => $classification->name }); {
ThrowUserError("classification_already_exists",
{name => $classification->name});
} }
return $name; return $name;
} }
...@@ -113,7 +117,8 @@ sub _check_sortkey { ...@@ -113,7 +117,8 @@ sub _check_sortkey {
$sortkey ||= 0; $sortkey ||= 0;
my $stored_sortkey = $sortkey; my $stored_sortkey = $sortkey;
if (!detaint_natural($sortkey) || $sortkey > MAX_SMALLINT) { if (!detaint_natural($sortkey) || $sortkey > MAX_SMALLINT) {
ThrowUserError('classification_invalid_sortkey', { 'sortkey' => $stored_sortkey }); ThrowUserError('classification_invalid_sortkey',
{'sortkey' => $stored_sortkey});
} }
return $sortkey; return $sortkey;
} }
...@@ -139,9 +144,11 @@ sub product_count { ...@@ -139,9 +144,11 @@ sub product_count {
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
if (!defined $self->{'product_count'}) { if (!defined $self->{'product_count'}) {
$self->{'product_count'} = $dbh->selectrow_array(q{ $self->{'product_count'} = $dbh->selectrow_array(
q{
SELECT COUNT(*) FROM products SELECT COUNT(*) FROM products
WHERE classification_id = ?}, undef, $self->id) || 0; WHERE classification_id = ?}, undef, $self->id
) || 0;
} }
return $self->{'product_count'}; return $self->{'product_count'};
} }
...@@ -151,10 +158,12 @@ sub products { ...@@ -151,10 +158,12 @@ sub products {
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
if (!$self->{'products'}) { if (!$self->{'products'}) {
my $product_ids = $dbh->selectcol_arrayref(q{ my $product_ids = $dbh->selectcol_arrayref(
q{
SELECT id FROM products SELECT id FROM products
WHERE classification_id = ? WHERE classification_id = ?
ORDER BY name}, undef, $self->id); ORDER BY name}, undef, $self->id
);
$self->{'products'} = Bugzilla::Product->new_from_list($product_ids); $self->{'products'} = Bugzilla::Product->new_from_list($product_ids);
} }
...@@ -182,17 +191,22 @@ sub sort_products_by_classification { ...@@ -182,17 +191,22 @@ sub sort_products_by_classification {
if (Bugzilla->params->{'useclassification'}) { if (Bugzilla->params->{'useclassification'}) {
my $class = {}; my $class = {};
# Get all classifications with at least one product. # Get all classifications with at least one product.
foreach my $product (@$products) { foreach my $product (@$products) {
$class->{$product->classification_id}->{'object'} ||= $class->{$product->classification_id}->{'object'}
new Bugzilla::Classification($product->classification_id); ||= new Bugzilla::Classification($product->classification_id);
# Nice way to group products per classification, without querying # Nice way to group products per classification, without querying
# the DB again. # the DB again.
push(@{$class->{$product->classification_id}->{'products'}}, $product); push(@{$class->{$product->classification_id}->{'products'}}, $product);
} }
$list = [sort {$a->{'object'}->sortkey <=> $b->{'object'}->sortkey $list = [
|| lc($a->{'object'}->name) cmp lc($b->{'object'}->name)} sort {
(values %$class)]; $a->{'object'}->sortkey <=> $b->{'object'}->sortkey
|| lc($a->{'object'}->name) cmp lc($b->{'object'}->name)
} (values %$class)
];
} }
else { else {
$list = [{object => undef, products => $products}]; $list = [{object => undef, products => $products}];
......
...@@ -34,7 +34,7 @@ use constant DB_TABLE => 'longdescs_tags_weights'; ...@@ -34,7 +34,7 @@ use constant DB_TABLE => 'longdescs_tags_weights';
use constant ID_FIELD => 'id'; use constant ID_FIELD => 'id';
use constant NAME_FIELD => 'tag'; use constant NAME_FIELD => 'tag';
use constant LIST_ORDER => 'weight DESC'; use constant LIST_ORDER => 'weight DESC';
use constant VALIDATORS => { }; use constant VALIDATORS => {};
# There's no gain to caching these objects # There's no gain to caching these objects
use constant USE_MEMCACHED => 0; use constant USE_MEMCACHED => 0;
......
...@@ -26,24 +26,23 @@ use File::Basename; ...@@ -26,24 +26,23 @@ use File::Basename;
# Don't export localvars by default - people should have to explicitly # Don't export localvars by default - people should have to explicitly
# ask for it, as a (probably futile) attempt to stop code using it # ask for it, as a (probably futile) attempt to stop code using it
# when it shouldn't # when it shouldn't
%Bugzilla::Config::EXPORT_TAGS = %Bugzilla::Config::EXPORT_TAGS = (
( admin => [
admin => [qw(update_params qw(update_params
SetParam SetParam
call_param_onchange_handlers call_param_onchange_handlers
write_params)], write_params)
); ],
);
Exporter::export_ok_tags('admin'); Exporter::export_ok_tags('admin');
# new installs get these set of defaults (unless overridden by the answers file) # new installs get these set of defaults (unless overridden by the answers file)
my %NEW_INSTALL_DEFAULT = ( my %NEW_INSTALL_DEFAULT = (or_groups => 1, use_email_as_login => 0,);
or_groups => 1,
use_email_as_login => 0,
);
# INITIALISATION CODE # INITIALISATION CODE
# Perl throws a warning if we use bz_locations() directly after do. # Perl throws a warning if we use bz_locations() directly after do.
our %params; our %params;
# Load in the param definitions # Load in the param definitions
sub _load_params { sub _load_params {
my $panels = param_panels(); my $panels = param_panels();
...@@ -52,12 +51,12 @@ sub _load_params { ...@@ -52,12 +51,12 @@ sub _load_params {
my $module = $panels->{$panel}; my $module = $panels->{$panel};
eval("require $module") || die $@; eval("require $module") || die $@;
my @new_param_list = $module->get_param_list(); my @new_param_list = $module->get_param_list();
$hook_panels{lc($panel)} = { params => \@new_param_list }; $hook_panels{lc($panel)} = {params => \@new_param_list};
} }
# This hook is also called in editparams.cgi. This call here is required # This hook is also called in editparams.cgi. This call here is required
# to make SetParam work. # to make SetParam work.
Bugzilla::Hook::process('config_modify_panels', Bugzilla::Hook::process('config_modify_panels', {panels => \%hook_panels});
{ panels => \%hook_panels });
foreach my $panel (keys %hook_panels) { foreach my $panel (keys %hook_panels) {
foreach my $item (@{$hook_panels{$panel}->{params}}) { foreach my $item (@{$hook_panels{$panel}->{params}}) {
...@@ -65,6 +64,7 @@ sub _load_params { ...@@ -65,6 +64,7 @@ sub _load_params {
} }
} }
} }
# END INIT CODE # END INIT CODE
# Subroutines go here # Subroutines go here
...@@ -75,11 +75,12 @@ sub param_panels { ...@@ -75,11 +75,12 @@ sub param_panels {
foreach my $item ((glob "$libpath/Bugzilla/Config/*.pm")) { foreach my $item ((glob "$libpath/Bugzilla/Config/*.pm")) {
$item =~ m#/([^/]+)\.pm$#; $item =~ m#/([^/]+)\.pm$#;
my $module = $1; my $module = $1;
$param_panels->{$module} = "Bugzilla::Config::$module" unless $module eq 'Common'; $param_panels->{$module} = "Bugzilla::Config::$module"
unless $module eq 'Common';
} }
# Now check for any hooked params # Now check for any hooked params
Bugzilla::Hook::process('config_add_panels', Bugzilla::Hook::process('config_add_panels', {panel_modules => $param_panels});
{ panel_modules => $param_panels });
return $param_panels; return $param_panels;
} }
...@@ -137,7 +138,7 @@ sub update_params { ...@@ -137,7 +138,7 @@ sub update_params {
die "Error evaluating $old_file: $@" if $@; die "Error evaluating $old_file: $@" if $@;
# Now read the param back out from the sandbox. # Now read the param back out from the sandbox.
$param = \%{ $s->varglob('param') }; $param = \%{$s->varglob('param')};
} }
else { else {
# Rename params.js to params.json if checksetup.pl # Rename params.js to params.json if checksetup.pl
...@@ -179,22 +180,20 @@ sub update_params { ...@@ -179,22 +180,20 @@ sub update_params {
} }
# set verify method to whatever loginmethod was # set verify method to whatever loginmethod was
if (exists $param->{'loginmethod'} if (exists $param->{'loginmethod'} && !exists $param->{'user_verify_class'}) {
&& !exists $param->{'user_verify_class'})
{
$new_params{'user_verify_class'} = $param->{'loginmethod'}; $new_params{'user_verify_class'} = $param->{'loginmethod'};
} }
# Remove quip-display control from parameters # Remove quip-display control from parameters
# and give it to users via User Settings (Bug 41972) # and give it to users via User Settings (Bug 41972)
if ( exists $param->{'enablequips'} if (exists $param->{'enablequips'}
&& !exists $param->{'quip_list_entry_control'}) && !exists $param->{'quip_list_entry_control'})
{ {
my $new_value; my $new_value;
($param->{'enablequips'} eq 'on') && do {$new_value = 'open';}; ($param->{'enablequips'} eq 'on') && do { $new_value = 'open'; };
($param->{'enablequips'} eq 'approved') && do {$new_value = 'moderated';}; ($param->{'enablequips'} eq 'approved') && do { $new_value = 'moderated'; };
($param->{'enablequips'} eq 'frozen') && do {$new_value = 'closed';}; ($param->{'enablequips'} eq 'frozen') && do { $new_value = 'closed'; };
($param->{'enablequips'} eq 'off') && do {$new_value = 'closed';}; ($param->{'enablequips'} eq 'off') && do { $new_value = 'closed'; };
$new_params{'quip_list_entry_control'} = $new_value; $new_params{'quip_list_entry_control'} = $new_value;
} }
...@@ -207,11 +206,14 @@ sub update_params { ...@@ -207,11 +206,14 @@ sub update_params {
'smtp' => 'SMTP', 'smtp' => 'SMTP',
'qmail' => 'Qmail', 'qmail' => 'Qmail',
'testfile' => 'Test', 'testfile' => 'Test',
'none' => 'None'); 'none' => 'None'
);
$param->{'mail_delivery_method'} = $translation{$mta}; $param->{'mail_delivery_method'} = $translation{$mta};
} }
# This will force the parameter to be reset to its default value. # This will force the parameter to be reset to its default value.
delete $param->{'mail_delivery_method'} if $param->{'mail_delivery_method'} eq 'Qmail'; delete $param->{'mail_delivery_method'}
if $param->{'mail_delivery_method'} eq 'Qmail';
} }
# Convert the old "ssl" parameter to the new "ssl_redirect" parameter. # Convert the old "ssl" parameter to the new "ssl_redirect" parameter.
...@@ -221,13 +223,15 @@ sub update_params { ...@@ -221,13 +223,15 @@ sub update_params {
$new_params{'ssl_redirect'} = 1; $new_params{'ssl_redirect'} = 1;
} }
# "specific_search_allow_empty_words" has been renamed to "search_allow_no_criteria". # "specific_search_allow_empty_words" has been renamed to "search_allow_no_criteria".
if (exists $param->{'specific_search_allow_empty_words'}) { if (exists $param->{'specific_search_allow_empty_words'}) {
$new_params{'search_allow_no_criteria'} = $param->{'specific_search_allow_empty_words'}; $new_params{'search_allow_no_criteria'}
= $param->{'specific_search_allow_empty_words'};
} }
if (exists $param->{'noresolveonopenblockers'}) { if (exists $param->{'noresolveonopenblockers'}) {
$new_params{'resolution_forbidden_with_open_blockers'} = $param->{'noresolveonopenblockers'} ? 'FIXED' : ""; $new_params{'resolution_forbidden_with_open_blockers'}
= $param->{'noresolveonopenblockers'} ? 'FIXED' : "";
} }
# --- DEFAULTS FOR NEW PARAMS --- # --- DEFAULTS FOR NEW PARAMS ---
...@@ -255,6 +259,7 @@ sub update_params { ...@@ -255,6 +259,7 @@ sub update_params {
# --- REMOVE OLD PARAMS --- # --- REMOVE OLD PARAMS ---
my %oldparams; my %oldparams;
# Remove any old params # Remove any old params
foreach my $item (keys %$param) { foreach my $item (keys %$param) {
if (!exists $params{$item}) { if (!exists $params{$item}) {
...@@ -325,12 +330,15 @@ sub read_param_file { ...@@ -325,12 +330,15 @@ sub read_param_file {
# to all users in its error message, so we have to eval'uate it. # to all users in its error message, so we have to eval'uate it.
$params = eval { JSON::XS->new->decode($data) }; $params = eval { JSON::XS->new->decode($data) };
if ($@) { if ($@) {
my $error_msg = (basename($0) eq 'checksetup.pl') ? my $error_msg
$@ : 'run checksetup.pl to see the details.'; = (basename($0) eq 'checksetup.pl')
? $@
: 'run checksetup.pl to see the details.';
die "Error parsing $file: $error_msg"; die "Error parsing $file: $error_msg";
} }
} }
elsif ($ENV{'SERVER_SOFTWARE'}) { elsif ($ENV{'SERVER_SOFTWARE'}) {
# We're in a CGI, but the params file doesn't exist. We can't # We're in a CGI, but the params file doesn't exist. We can't
# Template Toolkit, or even install_string, since checksetup # Template Toolkit, or even install_string, since checksetup
# might not have thrown an error. Bugzilla::CGI->new # might not have thrown an error. Bugzilla::CGI->new
...@@ -341,7 +349,7 @@ sub read_param_file { ...@@ -341,7 +349,7 @@ sub read_param_file {
CGI::Carp->import('fatalsToBrowser'); CGI::Carp->import('fatalsToBrowser');
} }
die "The $file file does not exist." die "The $file file does not exist."
. ' You probably need to run checksetup.pl.', . ' You probably need to run checksetup.pl.',;
} }
return $params // {}; return $params // {};
} }
......
...@@ -18,30 +18,19 @@ our $sortkey = 200; ...@@ -18,30 +18,19 @@ our $sortkey = 200;
sub get_param_list { sub get_param_list {
my $class = shift; my $class = shift;
my @param_list = ( my @param_list = (
{ {name => 'allowbugdeletion', type => 'b', default => 0},
name => 'allowbugdeletion',
type => 'b',
default => 0
},
{ {name => 'allowemailchange', type => 'b', default => 1},
name => 'allowemailchange',
type => 'b',
default => 1
},
{ {name => 'allowuserdeletion', type => 'b', default => 0},
name => 'allowuserdeletion',
type => 'b',
default => 0
},
{ {
name => 'last_visit_keep_days', name => 'last_visit_keep_days',
type => 't', type => 't',
default => 10, default => 10,
checker => \&check_numeric checker => \&check_numeric
}); }
);
return @param_list; return @param_list;
} }
......
...@@ -23,11 +23,7 @@ use constant get_param_list => ( ...@@ -23,11 +23,7 @@ use constant get_param_list => (
checker => \&check_inbound_proxies checker => \&check_inbound_proxies
}, },
{ {name => 'proxy_url', type => 't', default => ''},
name => 'proxy_url',
type => 't',
default => ''
},
{ {
name => 'strict_transport_security', name => 'strict_transport_security',
......
...@@ -18,11 +18,7 @@ our $sortkey = 400; ...@@ -18,11 +18,7 @@ our $sortkey = 400;
sub get_param_list { sub get_param_list {
my $class = shift; my $class = shift;
my @param_list = ( my @param_list = (
{ {name => 'allow_attachment_display', type => 'b', default => 0},
name => 'allow_attachment_display',
type => 'b',
default => 0
},
{ {
name => 'attachment_base', name => 'attachment_base',
...@@ -31,11 +27,7 @@ sub get_param_list { ...@@ -31,11 +27,7 @@ sub get_param_list {
checker => \&check_urlbase checker => \&check_urlbase
}, },
{ {name => 'allow_attachment_deletion', type => 'b', default => 0},
name => 'allow_attachment_deletion',
type => 'b',
default => 0
},
{ {
name => 'xsendfile_header', name => 'xsendfile_header',
...@@ -65,7 +57,8 @@ sub get_param_list { ...@@ -65,7 +57,8 @@ sub get_param_list {
type => 't', type => 't',
default => '0', default => '0',
checker => \&check_numeric checker => \&check_numeric
} ); }
);
return @param_list; return @param_list;
} }
......
...@@ -18,23 +18,11 @@ our $sortkey = 300; ...@@ -18,23 +18,11 @@ our $sortkey = 300;
sub get_param_list { sub get_param_list {
my $class = shift; my $class = shift;
my @param_list = ( my @param_list = (
{ {name => 'auth_env_id', type => 't', default => '',},
name => 'auth_env_id',
type => 't',
default => '',
},
{ {name => 'auth_env_email', type => 't', default => '',},
name => 'auth_env_email',
type => 't',
default => '',
},
{ {name => 'auth_env_realname', type => 't', default => '',},
name => 'auth_env_realname',
type => 't',
default => '',
},
# XXX in the future: # XXX in the future:
# #
...@@ -47,7 +35,7 @@ sub get_param_list { ...@@ -47,7 +35,7 @@ sub get_param_list {
{ {
name => 'user_info_class', name => 'user_info_class',
type => 's', type => 's',
choices => [ 'CGI', 'Env', 'Env,CGI' ], choices => ['CGI', 'Env', 'Env,CGI'],
default => 'CGI', default => 'CGI',
checker => \&check_multi checker => \&check_multi
}, },
...@@ -55,7 +43,7 @@ sub get_param_list { ...@@ -55,7 +43,7 @@ sub get_param_list {
{ {
name => 'user_verify_class', name => 'user_verify_class',
type => 'o', type => 'o',
choices => [ 'DB', 'RADIUS', 'LDAP' ], choices => ['DB', 'RADIUS', 'LDAP'],
default => 'DB', default => 'DB',
checker => \&check_user_verify_class checker => \&check_user_verify_class
}, },
...@@ -68,11 +56,7 @@ sub get_param_list { ...@@ -68,11 +56,7 @@ sub get_param_list {
checker => \&check_multi checker => \&check_multi
}, },
{ {name => 'requirelogin', type => 'b', default => '0'},
name => 'requirelogin',
type => 'b',
default => '0'
},
{ {
name => 'emailregexp', name => 'emailregexp',
...@@ -84,8 +68,8 @@ sub get_param_list { ...@@ -84,8 +68,8 @@ sub get_param_list {
{ {
name => 'emailregexpdesc', name => 'emailregexpdesc',
type => 'l', type => 'l',
default => 'A legal address must contain exactly one \'@\', and at least ' . default => 'A legal address must contain exactly one \'@\', and at least '
'one \'.\' after the @.' . 'one \'.\' after the @.'
}, },
{ {
...@@ -105,22 +89,16 @@ sub get_param_list { ...@@ -105,22 +89,16 @@ sub get_param_list {
{ {
name => 'password_complexity', name => 'password_complexity',
type => 's', type => 's',
choices => [ 'no_constraints', 'mixed_letters', 'letters_numbers', choices => [
'letters_numbers_specialchars' ], 'no_constraints', 'mixed_letters',
'letters_numbers', 'letters_numbers_specialchars'
],
default => 'no_constraints', default => 'no_constraints',
checker => \&check_multi checker => \&check_multi
}, },
{ {name => 'password_check_on_login', type => 'b', default => '1'},
name => 'password_check_on_login', {name => 'auth_delegation', type => 'b', default => 0,},
type => 'b',
default => '1'
},
{
name => 'auth_delegation',
type => 'b',
default => 0,
},
); );
return @param_list; return @param_list;
} }
......
...@@ -27,7 +27,8 @@ sub get_param_list { ...@@ -27,7 +27,8 @@ sub get_param_list {
# and bug_status.is_open is not yet defined (hence the eval), so we use # and bug_status.is_open is not yet defined (hence the eval), so we use
# the bug statuses above as they are still hardcoded. # the bug statuses above as they are still hardcoded.
eval { eval {
my @current_closed_states = map {$_->name} closed_bug_statuses(); my @current_closed_states = map { $_->name } closed_bug_statuses();
# If no closed state was found, use the default list above. # If no closed state was found, use the default list above.
@closed_bug_statuses = @current_closed_states if scalar(@current_closed_states); @closed_bug_statuses = @current_closed_states if scalar(@current_closed_states);
}; };
...@@ -42,29 +43,13 @@ sub get_param_list { ...@@ -42,29 +43,13 @@ sub get_param_list {
onchange => \&change_duplicate_or_move_bug_status onchange => \&change_duplicate_or_move_bug_status
}, },
{ {name => 'letsubmitterchoosepriority', type => 'b', default => 1},
name => 'letsubmitterchoosepriority',
type => 'b',
default => 1
},
{ {name => 'letsubmitterchoosemilestone', type => 'b', default => 1},
name => 'letsubmitterchoosemilestone',
type => 'b',
default => 1
},
{ {name => 'commentonchange_resolution', type => 'b', default => 0},
name => 'commentonchange_resolution',
type => 'b',
default => 0
},
{ {name => 'commentonduplicate', type => 'b', default => 0},
name => 'commentonduplicate',
type => 'b',
default => 0
},
{ {
name => 'resolution_forbidden_with_open_blockers', name => 'resolution_forbidden_with_open_blockers',
...@@ -72,15 +57,17 @@ sub get_param_list { ...@@ -72,15 +57,17 @@ sub get_param_list {
choices => \&_get_resolutions, choices => \&_get_resolutions,
default => '', default => '',
checker => \&check_resolution, checker => \&check_resolution,
} ); }
);
return @param_list; return @param_list;
} }
sub _get_resolutions { sub _get_resolutions {
my $resolution_field = Bugzilla::Field->new({ name => 'resolution', cache => 1 }); my $resolution_field = Bugzilla::Field->new({name => 'resolution', cache => 1});
# The empty resolution is included - it represents "no value". # The empty resolution is included - it represents "no value".
return [ map { $_->name } @{ $resolution_field->legal_values } ]; return [map { $_->name } @{$resolution_field->legal_values}];
} }
1; 1;
...@@ -25,35 +25,15 @@ sub get_param_list { ...@@ -25,35 +25,15 @@ sub get_param_list {
my @legal_OS = @{get_legal_field_values('op_sys')}; my @legal_OS = @{get_legal_field_values('op_sys')};
my @param_list = ( my @param_list = (
{ {name => 'useclassification', type => 'b', default => 0},
name => 'useclassification',
type => 'b',
default => 0
},
{ {name => 'usetargetmilestone', type => 'b', default => 0},
name => 'usetargetmilestone',
type => 'b',
default => 0
},
{ {name => 'useqacontact', type => 'b', default => 0},
name => 'useqacontact',
type => 'b',
default => 0
},
{ {name => 'usestatuswhiteboard', type => 'b', default => 0},
name => 'usestatuswhiteboard',
type => 'b',
default => 0
},
{ {name => 'use_see_also', type => 'b', default => 1},
name => 'use_see_also',
type => 'b',
default => 1
},
{ {
name => 'defaultpriority', name => 'defaultpriority',
...@@ -87,11 +67,8 @@ sub get_param_list { ...@@ -87,11 +67,8 @@ sub get_param_list {
checker => \&check_opsys checker => \&check_opsys
}, },
{ {name => 'collapsed_comment_tags', type => 't', default => 'obsolete, spam',}
name => 'collapsed_comment_tags', );
type => 't',
default => 'obsolete, spam',
});
return @param_list; return @param_list;
} }
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment